Skip to content

Setting up the Self SDK

The Self SDK provides powerful tools for digital identity management and secure communication.

This guide will walk you through the process of setting up your Self account and creating an inbox, which are essential steps for using the SDK effectively.

Setting up your Self account

1. Import the necessary packages

1
2
3
4
5
import (
    "github.com/joinself/self-go-sdk/account"
    "github.com/joinself/self-go-sdk/event"
    "github.com/charmbracelet/log"
)
1
2
3
4
5
6
import com.joinself.selfsdk.kmp.account.Account
import com.joinself.selfsdk.kmp.account.LogLevel
import com.joinself.selfsdk.kmp.account.Target
import com.joinself.selfsdk.kmp.error.SelfStatus
import com.joinself.selfsdk.kmp.event.*
import com.joinself.selfsdk.kmp.message.*
1
2
3
4
5
6
7
8
import android.content.Context
import com.joinself.common.Environment
import com.joinself.sdk.SelfSDK
import com.joinself.sdk.models.Account
import com.joinself.sdk.models.Message
import com.joinself.sdk.ui.integrateUIFlows
import com.joinself.sdk.ui.openRegistrationFlow
import com.joinself.ui.theme.SelfModifier

2. Configure your account settings

Android and iOS SDK require the integrity check be configured in the Admin app

1
2
3
4
5
6
7
cfg := &account.Config{
    StorageKey: make([]byte, 32), // Replace with a securely generated key
    StoragePath: "./storage", // Local storage path for account state
    Environment: account.TargetSandbox, // Choose between Develop and Sandbox
    LogLevel: account.LogWarn, // Set log level (Error, Warn, Info, Debug, Trace)
    Callbacks: account.Callbacks{},
}
1
2
3
4
5
6
7
8
9
val account = Account()
val status = account.configure(
    storagePath = ":memory:",
    storageKey = ByteArray(32),
    rpcEndpoint = Target.SANDBOX.rpcEndpoint(),
    objectEndpoint = Target.SANDBOX.objectEndpoint(),
    messageEndpoint = Target.SANDBOX.messageEndpoint(),
    logLevel = LogLevel.INFO,
)
SelfSDK.initialize(androidContext, 
    applicationAddress = "<document address from admin app>", // required for non-sandbox environment        
    log = { Log.d("Self", it) }
)

val storagePath = File(androidContext.filesDir.absolutePath + "/account1")
if (!storagePath.exists()) storagePath.mkdirs()

val account = Account.Builder()
    .setContext(applicationContext)
    .setEnvironment(Environment.production)
    .setSandbox(true)
    .setStoragePath(storagePath.absolutePath)
    .setCallbacks(object : Account.Callbacks {
        override fun onMessage(message: Message) {
            Log.d(LOGTAG, "onMessage: ${message.id()}")
        }
        override fun onConnect() {
            Log.d(LOGTAG, "onConnect")
        }
        override fun onDisconnect(errorMessage: String?) {
            Log.d(LOGTAG, "onDisconnect: $errorMessage")
        }
        override fun onAcknowledgement(id: String) {
            Log.d(LOGTAG, "onAcknowledgement: $id")
        }
        override fun onError(id: String, errorMessage: String?) {
            Log.d(LOGTAG, "onError: $errorMessage")
        }
    })
    .build()
1
2
3
4
5
6
7
let account = Account.Builder()
        .withEnvironment(Environment.preview)
        .withSandbox(false) // if true -> production
        .withGroupId("YOUR_APP_GROUP_ID") // ex: com.example.app.your_app_group
        .withDelegate(delegate: self) // Implement delegation of AccountDelegate
        .withStoragePath("ADD_YOUR_STORAGE_PATH_HERE" + "/account1")
        .build()

// Implement account delegation class YourClass: AccountDelegate { func onConnect() { print("onConnect") }

func onAcknowledgement(id: String) { print("onAcknowledgement: (id)") }

func onMessage(message: any self_ios_sdk.Message) { print("onMessage: (message)") switch message { case is CredentialRequest: let credentialRequest = message as! CredentialRequest self.handleCredentialRequest(credentialRequest: credentialRequest) default: print("🎯 ContentView: ❓ Unknown message type: (type(of: message))") break } }

func onDisconnect(errorMessage: String?) {

}

func onError(id: String, errorMessage: String?) {

}

3. Set up event callbacks

cfg.Callbacks = account.Callbacks{
    OnConnect: func(selfAccount *account.Account) {
        log.Info("messaging socket connected")
    },

    OnDisconnect: func(selfAccount *account.Account, err error) {
        if err != nil {
            log.Warn("messaging socket disconnected", "error", err)
        } else {
            log.Info("messaging socket disconnected")
        }
    },

    OnWelcome: func(selfAccount *account.Account, wlc *event.Welcome) {
        groupAddress, err := selfAccount.ConnectionAccept(
            wlc.ToAddress(),
            wlc,
        )
        if err != nil {
            log.Warn("failed to accept connection to encrypted group", "error", err)
            return
        }

        log.Info(
            "accepted connection encrypted group",
            "from", wlc.FromAddress().String(),
            "group", groupAddress.String(),
        )
    },

    OnMessage: func(selfAccount *account.Account, msg *event.Message) {
        switch event.ContentType(msg) {
        case event.TypeChat:
            // Handle chat messages
        case event.TypeDiscoveryResponse:
            // Handle discovery responses
        }
    },
}
account.configure(
    // ... other config options ...
    onConnect = {
        println("KMP connected")
    },
    onDisconnect = { reason: SelfStatus? ->
        println("KMP disconnected")
    },
    onWelcome = { welcome: Welcome ->
        account.connectionAccept(asAddress = welcome.toAddress(), welcome = welcome) { status: SelfStatus, groupAddress: PublicKey ->
            println("accepted connection encrypted group status:${status.code()} - from:${welcome.fromAddress().encodeHex()} - group:${groupAddress.encodeHex()}")
        }
    },
    onMessage = { message: Message ->
        val content = message.content()
        val contentType = content.contentType()
        when (contentType) {
            ContentType.CHAT -> {
                // Handle chat messages
            }
            ContentType.DISCOVERY_RESPONSE -> {
                // Handle discovery responses
            }
        }
    }
)

Configure the account above to set up the callbacks.

.setCallbacks(object : Account.Callbacks {
    override fun onMessage(message: Message) {
        Log.d(LOGTAG, "onMessage: ${message.id()}")
        when (message) {
            is ChatMessage -> println("chat messages")
            is Receipt -> println("receipt message")
            is CredentialRequest -> println("credential request")
            is CredentialMessage -> println("credential message")
            is VerificationRequest -> println("verification request")
            is SigningRequest -> println("signing request")
        }
    }
    override fun onConnect() {
        Log.d(LOGTAG, "onConnect")
    }
    override fun onDisconnect(errorMessage: String?) {
        Log.d(LOGTAG, "onDisconnect: $errorMessage")
    }
    override fun onAcknowledgement(id: String) {
        Log.d(LOGTAG, "onAcknowledgement: $id")
    }
    override fun onError(id: String, errorMessage: String?) {
        Log.d(LOGTAG, "onError: $errorMessage")
    }
})

class YourClass: AccountDelegate {
    func onConnect() {
    print("onConnect")
}

func onAcknowledgement(id: String) {
    print("onAcknowledgement: \(id)")
}

func onMessage(message: any self_ios_sdk.Message) {
    print("onMessage: \(message)")
    switch message {
    case is CredentialRequest:
        let credentialRequest = message as! CredentialRequest
        self.handleCredentialRequest(credentialRequest: credentialRequest)
    default:
        print("🎯 ContentView: ❓ Unknown message type: \(type(of: message))")
        break
    }
}

func onDisconnect(errorMessage: String?) {

}

func onError(id: String, errorMessage: String?) {

}

4. Initialize the Self account

1
2
3
4
selfAccount, err := account.New(cfg)
if err != nil {
    log.Fatal("failed to initialize account", "error", err)
}
// Account is already initialized in the configuration step for Kotlin
println("status: ${status.code()}")
1
2
3
4
// When an account is initialized, the onConnect callback is invoked
override fun onConnect() {
    Log.d(LOGTAG, "account is initialized")
}
1
2
3
4
// Account is already initialized in the configuration step
account.setOnStatusListener { status in
    print("init account status:\(status) -> \(self.account.generateAddress())")
}

Setting up your inbox

1
2
3
4
5
6
inboxAddress, err := selfAccount.InboxOpen()
if err != nil {
    log.Fatal("failed to open account inbox", "error", err)
}

log.Info("initialized account success")
val inboxAddress = runBlocking {
    suspendCoroutine { continuation ->
        account.inboxOpen { status: SelfStatus, address: PublicKey ->
            println("inbox open status:${status.code()} - address:${address.encodeHex()}")
            if (status.success()) {
                continuation.resumeWith(Result.success(address))
            } else {
                continuation.resumeWith(Result.success(null))
            }
        }
    }
}
You don’t need to open the inbox on Android.
It’s managed internally by the Self Android SDK.
You don’t need to open the inbox on iOS.
It’s managed internally by the Self iOS SDK.

Note: Creating an inbox is only necessary for the current version of the SDK. This requirement will be removed in future updates to streamline the setup process.

By following these steps, you'll establish a secure foundation for your Self-enabled application. Let's dive into each step in detail.

Register an account in Android and iOS app

To use any functionality provided by the Android or iOS SDK, users are required to register an account beforehand.

The SDK includes built-in Kotlin Compose for Android and SwiftUI for iOS UI components to facilitate this process, making it easy to integrate account registration into your app's user interface.

See details in these examples

Below are code examples in a simple MainActivity on how to integrate the registration flow in the app.

  1. Configure an account
  2. Integrate Self UI flows into the main app
  3. Open registration flow
import com.joinself.ui.theme.SelfModifier
import com.joinself.sdk.SelfSDK
import com.joinself.sdk.models.Account

class MainActivity : ComponentActivity() {
    val LOGTAG = "SelfSDK"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)            

        // init the sdk
        SelfSDK.initialize(applicationContext,
            log = { Log.d(LOGTAG, it) }
        )

        // the sdk will store data in this directory, make sure it exists.
        val storagePath = File(applicationContext.filesDir.absolutePath + "/account1")
        if (!storagePath.exists()) storagePath.mkdirs()

        val account = Account.Builder()
            .setContext(applicationContext)
            .setEnvironment(Environment.production)
            .setSandbox(true)
            .setStoragePath(storagePath.absolutePath)
            .setCallbacks(object : Account.Callbacks {
                override fun onMessage(message: Message) {
                    Log.d(LOGTAG, "onMessage: ${message.id()}")
                }
                override fun onConnect() {
                    Log.d(LOGTAG, "onConnect")
                }
                override fun onDisconnect(errorMessage: String?) {
                    Log.d(LOGTAG, "onDisconnect: $errorMessage")
                }
                override fun onAcknowledgement(id: String) {
                    Log.d(LOGTAG, "onAcknowledgement: $id")
                }
                override fun onError(id: String, errorMessage: String?) {
                    Log.d(LOGTAG, "onError: $errorMessage")
                }
            })
            .build()

        setContent {
            val navController = rememberNavController()
            val selfModifier = SelfModifier.sdk()

            var isRegistered by remember { mutableStateOf(account.registered()) }

            NavHost(navController = navController,
                startDestination = "main",
                modifier = Modifier.systemBarsPadding(),                    
            ) {
                // integrate self ui flows into your app
                SelfSDK.integrateUIFlows(this,navController, selfModifier = selfModifier)

                composable("main") {
                    Column(
                        verticalArrangement = Arrangement.spacedBy(10.dp),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        modifier = Modifier.padding(start = 8.dp, end = 8.dp).fillMaxWidth()
                    ) {
                        Text(modifier = Modifier.padding(top = 40.dp), text = "Registered: $isRegistered")

                        Button(                                
                            onClick = {
                                // open registration flow to create an account
                                account.openRegistrationFlow { isSuccess, error ->
                                    isRegistered = isSuccess
                                }
                            },
                            enabled = !isRegistered
                        ) {
                            Text(text = "Create Account")
                        }
                    }
                }
            }
        }
    }
}

The onboarding flow is embedded inside the Self SDK. We can register an account via a view like this.

    // 1. Init an account via account builder
    // Initialize SDK
    SelfSDK.initialize()

    let account = Account.Builder()
        .withEnvironment(Environment.production)
        .withSandbox(true) // if true -> production
        .withGroupId("") // ex: com.example.app.your_app_group
        .withDelegate(delegate: self)
        .withStoragePath(FileManager.storagePath)
        .build()

    // 2. Implement account delegation methods

    func onConnect() {
        print("onConnect")
    }

    func onAcknowledgement(id: String) {
        print("onAcknowledgement: \(id)")
    }

    func onMessage(message: any self_ios_sdk.Message) {
        print("onMessage: \(message)")
    }

    func onDisconnect(errorMessage: String?) {
        print("onDisconnect: \(errorMessage)")
    }

    func onError(id: String, errorMessage: String?) {
        print("onError: \(id) - \(errorMessage)")
    }

    // 3. Show UI to register the account above
    RegistrationFlow(account: viewModel.getAccount()) { result in
        switch result {
        case .success:
            print("Register account success.")
            self.setCurrentAppScreen(screen: .serverConnectionSelection)
        case .failure(let error):
            print("Register account error: \(error)")
        }
    }

extension FileManager {
    static var storagePath: String {
        let storagePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("account1")
        createFolderAtURL(url: storagePath) // create folder if needed
        print("StoragePath: \(storagePath)")
        return storagePath.path()
    }

    static func createFolderAtURL(url: URL) {
        if FileManager.default.fileExists(atPath: url.path()) {
            print("Folder already exists: \(url)")

        } else {
            // Create the folder
            do {
                try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
                print("Folder created successfully: \(url)")
            } catch {
                print("Error creating folder: \(error.localizedDescription)")
            }
        }
    }
}