Quickstart
You need Android Studio or Xcode, the SDK zip, and a phone to run your app on. About an hour.
-
Add the SDK to your project: in Android Studio, the Maven folder and the two libraries; in Xcode,
Wallet.xcframework(Embed & Sign),-lsqlite3and thecompose-resourcesfolder. Step by step: Install & versions. -
Create the SDK object once for the process: where credentials come from, and which verifiers to name as trusted.
WalletSetup.kt package samplesimport com.wallet.Walletimport com.wallet.WalletSdkimport com.wallet.config.IssuerConfigimport com.wallet.config.TrustedVerifiers/** The SDK object for a wallet app. Create it once, when the app starts, and keep it. */object WalletSetup {fun create(signedReaderList: ByteArray): WalletSdk = Wallet.createWallet(// Where credentials come from: your OpenID4VCI issuer.issuer = IssuerConfig(baseUrl = "https://issuer.example.com",clientId = "my-wallet",redirectUri = "mywallet://callback",),// The verifiers named on the consent sheet ("Verify is asking for…"); others show as unknown.trustedVerifiers = TrustedVerifiers(signedList = signedReaderList),// Document types you hold besides the built-in driving licence.credentialTypes = listOf(EmiratesIdType.type),// A check before sharing, e.g. the face check (see Prove it's you). Null: share on consent.shareGuard = null,)}/** Shared by the samples: the one SDK instance of the app. */object App {lateinit var sdk: WalletSdk}WalletSetup.swift import Wallet/// The SDK object for a wallet app. Created once, when the app starts, and kept.enum WalletSetup {static func create(signedReaderList: KotlinByteArray) -> WalletSdk {Wallet.shared.createWallet(// Where credentials come from: your OpenID4VCI issuer.issuer: IssuerConfig(baseUrl: "https://issuer.example.com",clientId: "my-wallet",redirectUri: "mywallet://callback",dpopPolicy: .allowBearerFallback),// The verifiers named on the consent sheet ("Verify is asking for…"); others show as unknown.trustedVerifiers: TrustedVerifiers(signedList: signedReaderList),// Document types you hold besides the built-in driving licence.credentialTypes: [EmiratesIdType.type],// A check before sharing, e.g. the face check (see Prove it's you). nil: share on consent.shareGuard: nil)}}/// Shared by the samples: the one SDK instance of the app.enum MyWallet {static let sdk = WalletSetup.create(signedReaderList: KotlinByteArray(size: 0)) // your signed reader list}Every credential’s key is kept in the phone’s secure hardware (the Android Keystore or the Secure Enclave).
-
Show the wallet screen. It is the whole holder UI: cards, details, presenting, scanning offers, permissions.
WalletActivity.kt package samplesimport android.os.Bundleimport androidx.activity.compose.setContentimport androidx.compose.material3.MaterialThemeimport androidx.fragment.app.FragmentActivityimport com.wallet.ui.WalletBrandingimport com.wallet.ui.WalletScreen/** The whole wallet UI: the card list, details, presenting, and scanning credential offers. */class WalletActivity : FragmentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {MaterialTheme(typography = WalletBranding.typography) {WalletScreen(App.sdk)}}}}Host it in a
FragmentActivity.WalletApp.swift import SwiftUIimport Wallet/// The whole wallet UI: the card list, details, presenting, and scanning credential offers.@mainstruct WalletApp: App {init() { WalletBranding.shared.appName = "My Wallet" } // the name on the consent sheetvar body: some Scene {WindowGroup {WalletScreen().ignoresSafeArea()// Credential offers and website requests that open the app (URL schemes, Universal Links)..onOpenURL { url in_ = WalletViewControllersKt.handleWalletUrl(url: url.absoluteString, sameDevice: true)}}}}/// The SDK's wallet screen, hosted in SwiftUI.struct WalletScreen: UIViewControllerRepresentable {func makeUIViewController(context: Context) -> UIViewController {WalletViewControllersKt.WalletViewController(sdk: MyWallet.sdk)}func updateUIViewController(_ controller: UIViewController, context: Context) {}}The screen is a
UIViewController; in SwiftUI, wrap it in aUIViewControllerRepresentable. Pass the links that open your app tohandleWalletUrl, so offers and website requests reach the wallet. -
Describe your document types. The driving licence is built in. For anything else, a
CredentialTypegives every field a label, a format and an icon; the wallet, the consent sheet and a verifier’s result screen all use it.EmiratesIdType.kt package samplesimport com.wallet.model.AttributeKindimport com.wallet.model.CredentialAttributeimport com.wallet.model.CredentialNamespaceimport com.wallet.model.CredentialType/** A document type: labels, formats and icons for every field. The wallet and the verifier both use it. */object EmiratesIdType {val type = CredentialType(docType = "org.iso.23220.photoid.1",displayName = "Emirates ID",namespaces = listOf(CredentialNamespace("org.iso.23220.1",listOf(CredentialAttribute("family_name", "Family name", AttributeKind.Text, mandatory = true, icon = "PERSON"),CredentialAttribute("given_name", "Given names", AttributeKind.Text, mandatory = true, icon = "PERSON"),CredentialAttribute("birth_date", "Date of birth", AttributeKind.Date, icon = "TODAY"),CredentialAttribute("sex", "Sex", AttributeKind.Sex),CredentialAttribute("portrait", "Photo", AttributeKind.Picture, icon = "ACCOUNT_BOX"),CredentialAttribute("expiry_date", "Expiry date", AttributeKind.Date, icon = "EVENT"),),),CredentialNamespace("org.iso.23220.photoid.1",listOf(CredentialAttribute("person_id", "ID number", AttributeKind.Text, mandatory = true, icon = "BADGE")),),),)}EmiratesIdType.swift import Wallet/// A document type: labels, formats and icons for every field. The wallet and the verifier both use it.enum EmiratesIdType {static let type = CredentialType(docType: "org.iso.23220.photoid.1",displayName: "Emirates ID",namespaces: [CredentialNamespace(id: "org.iso.23220.1", attributes: [attribute("family_name", "Family name", .text, mandatory: true, icon: "PERSON"),attribute("given_name", "Given names", .text, mandatory: true, icon: "PERSON"),attribute("birth_date", "Date of birth", .date, icon: "TODAY"),attribute("sex", "Sex", .sex),attribute("portrait", "Photo", .picture, icon: "ACCOUNT_BOX"),attribute("expiry_date", "Expiry date", .date, icon: "EVENT"),]),CredentialNamespace(id: "org.iso.23220.photoid.1", attributes: [attribute("person_id", "ID number", .text, mandatory: true, icon: "BADGE"),]),])/// Swift sees every parameter of CredentialAttribute; this fills in the Kotlin defaults.private static func attribute(_ id: String, _ label: String, _ kind: AttributeKind,mandatory: Bool = false, icon: String? = nil) -> CredentialAttribute {CredentialAttribute(id: id, label: label, kind: kind, description: label,mandatory: mandatory, icon: icon, expiredBadge: nil)}}Kinds:
Text,Date,DateTime,Picture,Boolean,Number,Sex. Card designs are drawing functions per document type, supplied through a brand pack; document types without one get a neutral card. -
Run it on a phone (Android Studio: Run ▶; Xcode: ⌘R): Bluetooth and the camera don’t work in emulators or simulators. Scan a credential offer from your issuer to receive a credential, then present it to the Verify app on a second phone, and open a Hello Mobile link on the same phone.
Next: Hold and show credentials.