From 6656e447dca2dec3fc1ea79794285fec842423a1 Mon Sep 17 00:00:00 2001 From: ManulParihar Date: Sat, 19 Sep 2026 10:26:16 +0530 Subject: [PATCH] Update the SDK docs for kokio-sdk 3.1.0 Batched eSIM wallet deploy, purchase by transfer, optional passkey peer dependency and gas policy, bundler URL, and revert errors by name. Also corrects the USDC and settlementToken descriptions. --- docs/sdk/backend/device-wallet-factory.md | 4 ++- docs/sdk/backend/lazy-wallet-registry.md | 2 +- docs/sdk/backend/payments.md | 6 ++-- docs/sdk/backend/setup.md | 2 ++ docs/sdk/mobile/device-wallet.md | 20 +++++++++++-- docs/sdk/mobile/esim-wallet.md | 36 +++++++++++++++++++++-- docs/sdk/mobile/payments.md | 6 ++-- docs/sdk/mobile/setup.md | 27 ++++++++++------- docs/sdk/mobile/smart-account.md | 12 ++++++++ docs/sdk/overview.md | 16 +++++++--- static/llms.txt | 10 +++---- 11 files changed, 108 insertions(+), 33 deletions(-) diff --git a/docs/sdk/backend/device-wallet-factory.md b/docs/sdk/backend/device-wallet-factory.md index 79944a9..acec28c 100644 --- a/docs/sdk/backend/device-wallet-factory.md +++ b/docs/sdk/backend/device-wallet-factory.md @@ -52,7 +52,9 @@ Returns `Promise`. ## postCreateAccount {#postcreateaccount} -Registers a device wallet with the factory after it deploys. Use it right after [`createAccountWithEOA`](../mobile/device-wallet-factory.md#createaccountwitheoa) on the mobile surface, because a wallet the app deployed is not registered until this runs. +Registers a device wallet with the factory after it deploys. Use it after the app deploys the wallet itself, either with its first sponsored user operation or with [`createAccountWithEOA`](../mobile/device-wallet-factory.md#createaccountwitheoa), because a wallet the app deployed is not registered until this runs. Until then it cannot deploy eSIM wallets. + +On a hosted RPC, wait a few confirmations before telling the app to go ahead. A bundler that simulates the app's next operation against a node one block behind still sees the wallet as unregistered and rejects it. The salt has to match the one the deploy used. The factory recomputes the wallet's address from it to check the wallet is real. diff --git a/docs/sdk/backend/lazy-wallet-registry.md b/docs/sdk/backend/lazy-wallet-registry.md index 271c413..753f61e 100644 --- a/docs/sdk/backend/lazy-wallet-registry.md +++ b/docs/sdk/backend/lazy-wallet-registry.md @@ -20,7 +20,7 @@ const deployment = await admin.lazyWalletRegistry.deployLazyWalletAndSetESIMIden ); ``` -Some errors here are not exported by name, `BatchSizeOutOfRangeError` among them. Catch them with `instanceof KokioError` and read `.code`. +The errors these calls throw are exported by name from both entry points: `BatchSizeOutOfRangeError`, `DepositOnResumeError`, `ESIMWalletNotLazyDeployedError`, `MissingBatchEventError` and `StalledBatchError`. All extend `KokioError`. ## batchPopulateHistory {#batchpopulatehistory} diff --git a/docs/sdk/backend/payments.md b/docs/sdk/backend/payments.md index 7eca620..ff55350 100644 --- a/docs/sdk/backend/payments.md +++ b/docs/sdk/backend/payments.md @@ -29,7 +29,7 @@ const usdcSymbol = stringToHex("USDC", { size: 32 }); // 0x5553444300000000000000000000000000000000000000000000000000000000 ``` -The USDC registered on the current deployment is a test token, not Circle's. Its address is on the [deployed addresses](../../contracts/deployments.md) page, alongside the second registered asset, `USD`, which has no token and stands for a card or bank payment. +`USDC` on the current deployment is Circle's Base Sepolia USDC. `USD` has no token and stands for a card or bank payment. Read any asset's token address with [`resolveAsset`](#resolveasset) rather than hardcoding it. ## registerAsset {#registerasset} @@ -124,10 +124,10 @@ Returns `Promise
`. ## settlementToken {#settlementtoken} -Reads the ERC-20 registered under the `USDC` symbol at configure time. +Reads the token set when the adapter was initialized, the one the vault is meant to end up holding. A purchase does not pay in it: it pays in the token of the asset it names, which `resolveAsset` returns. ```ts -const usdc = await admin.paymentAdapter.settlementToken(); +const token = await admin.paymentAdapter.settlementToken(); ``` Returns `Promise
`. diff --git a/docs/sdk/backend/setup.md b/docs/sdk/backend/setup.md index 07d626e..abc13af 100644 --- a/docs/sdk/backend/setup.md +++ b/docs/sdk/backend/setup.md @@ -39,6 +39,8 @@ const admin = new KokioAdmin(walletClient); Give `http()` a real RPC URL. The SDK reads it to build the public client it uses for reads. +Writes sign locally with the key and go out as raw transactions, so any hosted RPC works and never sees the key. A connected wallet account works too. + ## Which surfaces are ready when {#surfaces} `deviceWalletFactory`, `eSIMWalletFactory`, `registry`, `lazyWalletRegistry`, `paymentAdapter` and `protocolAdmin` work as soon as the instance exists, because they are chain-wide. The two instance-scoped surfaces, `deviceWallet` and `eSIMWallet`, stay `undefined` until bound to an address. diff --git a/docs/sdk/mobile/device-wallet.md b/docs/sdk/mobile/device-wallet.md index d3d2959..237bdc4 100644 --- a/docs/sdk/mobile/device-wallet.md +++ b/docs/sdk/mobile/device-wallet.md @@ -1,6 +1,6 @@ --- title: Device wallet (mobile) -description: kokio.deviceWallet wraps one user's Kokio device wallet, the ERC-4337 smart account owned by a passkey, with methods to attach eSIM wallets, grant fund access, transfer ownership and manage the gas deposit. +description: kokio.deviceWallet wraps one user's Kokio device wallet, the ERC-4337 smart account owned by a passkey, with methods to deploy and attach eSIM wallets, grant fund access, transfer ownership and manage the gas deposit. --- # Device wallet (mobile) {#device-wallet-mobile} @@ -17,7 +17,7 @@ const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash }); if (!receipt.success) throw new Error("operation reverted"); ``` -An operation whose calls revert is still mined and still returns a receipt, so check `success` rather than treating a resolved await as confirmation. +A call the contract would refuse throws `ContractRevertError` before it is sent, with the error name in `err.decoded?.errorName`. If the chain changes between that check and inclusion, the operation can still revert onchain. It is then mined and returns a receipt, so check `success` rather than treating a resolved await as confirmation. ## sendUserOperation {#senduseroperation} @@ -31,6 +31,22 @@ const hash = await kokio.deviceWallet!.sendUserOperation([ Returns `Promise`, the user operation hash. +## deployAndBindESIMWallet {#deployandbindesimwallet} + +Deploys a new eSIM wallet for this device wallet and adds it to the device wallet's list, in one user operation and one passkey prompt. Use it when a user adds a new eSIM. Pass `grantAccessToFunds: true` to also let the new eSIM wallet pull tokens from this device wallet, which [`buyDataBundleWithToken`](./esim-wallet.md#buydatabundlewithtoken) needs when the eSIM wallet holds less than the price. + +The device wallet has to be registered first, by the backend's [`postCreateAccount`](../backend/device-wallet-factory.md#postcreateaccount) or by a backend deploy, or the operation reverts. Each salt gives one address, so use a new salt for each eSIM wallet on the same device. + +```ts +const { userOpHash, eSIMWalletAddress } = await kokio.deviceWallet!.deployAndBindESIMWallet( + 1n, // salt + { grantAccessToFunds: true }, +); +kokio.setESIMWalletAddress(eSIMWalletAddress); +``` + +Returns `Promise<{ userOpHash: Hash; eSIMWalletAddress: Address }>`. The address is worked out before the operation lands, so wait for the receipt before reading from it. + ## addESIMWallet {#addesimwallet} Adds an eSIM wallet this device already owns onto the device wallet's list. Use it after an eSIM wallet transfer lands, once the eSIM wallet's `owner()` already points at this device wallet. diff --git a/docs/sdk/mobile/esim-wallet.md b/docs/sdk/mobile/esim-wallet.md index 793d890..3ca9ba1 100644 --- a/docs/sdk/mobile/esim-wallet.md +++ b/docs/sdk/mobile/esim-wallet.md @@ -1,6 +1,6 @@ --- title: eSIM wallet (mobile) -description: kokio.eSIMWallet buys data bundles for one Kokio eSIM, sets its price cap and moves it between devices, and kokio.eSIMWalletFactory deploys a new one. +description: kokio.eSIMWallet buys data bundles for one Kokio eSIM, with or without access to the device wallet's funds, sets its price cap and moves it between devices, and kokio.eSIMWalletFactory predicts a new one's address. --- # eSIM wallet (mobile) {#esim-wallet-mobile} @@ -26,6 +26,8 @@ Prices are whole US cents as a `bigint`, and a currency is named by a `bytes32` Buys a data bundle for this eSIM, paid for in an ERC-20 the payment adapter accepts, which is USDC on Base Sepolia today. This is the everyday purchase flow. +If this eSIM wallet holds less than the price, the contract pulls the rest from the device wallet. That needs fund access, granted by [`deviceWallet.toggleAccessToFunds`](./device-wallet.md#toggleaccesstofunds) or by `deployAndBindESIMWallet` with `grantAccessToFunds: true`. Without it the purchase reverts with `FundsAccessRevoked`, and [`buyDataBundleWithTransfer`](#buydatabundlewithtransfer) is the one to use. + Check `priceCapUSDCents()` first, because a price above the cap reverts. Read [`kokio.paymentAdapter.quote(asset, priceUSDCents)`](./payments.md#quote) to size `maxAmountIn`, the most of `asset` this purchase may spend, in its smallest unit. Nothing moves the price between the quote and the purchase today, so passing that value straight through is enough. A swap path may show up later, which is why the contract takes a maximum rather than an exact amount. `paymentReference` ties the purchase to its offchain order and is spendable once per eSIM wallet. The backend hands this to the app. The SDK never invents one. @@ -45,6 +47,20 @@ const hash = await kokio.eSIMWallet!.buyDataBundleWithToken( Returns `Promise`, a user operation hash. +## buyDataBundleWithTransfer {#buydatabundlewithtransfer} + +The same purchase as `buyDataBundleWithToken`, for an eSIM wallet with no access to the device wallet's funds. In one user operation the device wallet sends the eSIM wallet whatever it is short of the quote, then the purchase runs. The arguments are the same. + +The device wallet has to hold enough of the asset's token. Only the shortfall is sent, so tokens already on the eSIM wallet are spent first. + +```ts +const hash = await kokio.eSIMWallet!.buyDataBundleWithTransfer( + dataBundleDetails, asset, maxAmountIn, paymentReference, +); +``` + +Returns `Promise`, a user operation hash. + ## sendTokenToDeviceWallet {#sendtokentodevicewallet} Sends an ERC-20 held by this eSIM wallet back to its owning device wallet. Nothing else moves a stray token balance off this wallet, so use this when one is stuck here after a handover or a refund. @@ -143,11 +159,25 @@ Returns `Promise`, shaped `{ id, priceUSDCents, settlement }` `kokio.eSIMWalletFactory` -Deploys a new eSIM wallet for a device wallet. Present as soon as `Kokio` has a `smartAccountClient`, chain-wide like the [device wallet factory](./device-wallet-factory.md). The contract is documented at [eSIM wallet factory](../../contracts/esim-wallet-factory.md). +Predicts and deploys eSIM wallets for a device wallet. Present as soon as `Kokio` has a `smartAccountClient`, chain-wide like the [device wallet factory](./device-wallet-factory.md). The contract is documented at [eSIM wallet factory](../../contracts/esim-wallet-factory.md). + +To deploy a new eSIM wallet, use [`deviceWallet.deployAndBindESIMWallet`](./device-wallet.md#deployandbindesimwallet). It deploys and binds in one user operation and returns the new address. + +### getCounterFactualAddress {#getcounterfactualaddress} + +Works out the address an eSIM wallet will have for a device wallet and salt, before it is deployed. A plain read, no user operation. + +```ts +const eSIMWalletAddress = await kokio.eSIMWalletFactory!.getCounterFactualAddress(deviceWalletAddress, 1n); +``` + +Returns `Promise
`. ### deployESIMWalletWithUserOp {#deployesimwalletwithuserop} -Deploys a new eSIM wallet, owned by the given device wallet. Use it when a user is adding a new eSIM to a device wallet they already have. The device wallet sending the user operation has to be one the registry recognizes. +Deprecated. Use `deviceWallet.deployAndBindESIMWallet` instead. This deploys the eSIM wallet without adding it to the device wallet's list, so the device wallet does not treat it as its own until `addESIMWallet` runs. + +Deploys a new eSIM wallet, owned by the given device wallet. The device wallet sending the user operation has to be one the registry recognizes. ```ts const hash = await kokio.eSIMWalletFactory!.deployESIMWalletWithUserOp( diff --git a/docs/sdk/mobile/payments.md b/docs/sdk/mobile/payments.md index 0dd2f15..0dbe0d3 100644 --- a/docs/sdk/mobile/payments.md +++ b/docs/sdk/mobile/payments.md @@ -37,7 +37,7 @@ const usdcSymbol = stringToHex("USDC", { size: 32 }); // 0x5553444300000000000000000000000000000000000000000000000000000000 ``` -The USDC registered on the current deployment is a test token, not Circle's. Its address is on the [deployed addresses](../../contracts/deployments.md) page. +`USDC` on the current deployment is Circle's Base Sepolia USDC. Read any asset's token address with [`resolveAsset`](#resolveasset) rather than hardcoding it. ## registry {#registry} @@ -51,10 +51,10 @@ Returns `Promise
`. ## settlementToken {#settlementtoken} -Reads the ERC-20 registered under the `USDC` symbol at configure time. +Reads the token set when the adapter was initialized, the one the vault is meant to end up holding. A purchase does not pay in it: it pays in the token of the asset it names, which `resolveAsset` returns. ```ts -const usdc = await kokio.paymentAdapter!.settlementToken(); +const token = await kokio.paymentAdapter!.settlementToken(); ``` Returns `Promise
`. diff --git a/docs/sdk/mobile/setup.md b/docs/sdk/mobile/setup.md index 6a5a8b5..ef2af97 100644 --- a/docs/sdk/mobile/setup.md +++ b/docs/sdk/mobile/setup.md @@ -11,11 +11,11 @@ This page gets you from an empty project to a client that can send one. ## What you need {#what-you-need} -- a viem `WalletClient` for the target chain, carrying an `account` and a real RPC URL +- a viem `WalletClient` for the target chain, with a real RPC URL - the passkey `credentialId` and `rpId` registered for this device -- a Pimlico API key and a gas policy id, used by the bundler and paymaster +- a Pimlico API key, used by the bundler and paymaster, and optionally a Pimlico sponsorship policy id -Passkey signing goes through [`react-native-passkey`](https://github.com/f-23/react-native-passkey) and runs only on a device or simulator that supports WebAuthn. It does not work in a plain Node process. +Passkey signing goes through [`react-native-passkey`](https://github.com/f-23/react-native-passkey), which the app installs itself (`npm install kokio-sdk react-native-passkey`). It runs only on a device or simulator that supports WebAuthn, not in a plain Node process. ## Constructing the client {#constructing-the-client} @@ -27,7 +27,6 @@ import { baseSepolia } from "viem/chains"; const rpcUrl = `https://base-sepolia.g.alchemy.com/v2/${alchemyApiKey}`; const walletClient = createWalletClient({ - account: deviceWalletAddress, // presence is checked, this client never signs chain: baseSepolia, transport: http(rpcUrl), }); @@ -37,15 +36,13 @@ const kokio = new Kokio( credentialId, // passkey credential id on the device rpId, // relying party id, your app domain pimlicoAPIKey, - gasPolicyId, + gasPolicyId, // Pimlico sponsorship policy id, or "" for none ); ``` -Two details in that wallet client are easy to get wrong, and both fail later, far from the line that caused them. +The wallet client needs no `account`. The passkey signs everything. The one call that needs an account on it is `deviceWalletFactory.createAccountWithEOA`, which a normal app never uses. -**`account` has to be set.** Without it `getSmartWallet` throws "No signer account found with WalletClient". It is only a presence check. The passkey signs everything and this account is never asked for a signature. Any address the app already holds will do, including the stored device wallet address. The one call that actually spends from it is `deviceWalletFactory.createAccountWithEOA`, which a normal app never uses. - -**Give `http()` a real RPC URL.** The SDK reads `client.transport.url` to build the public client it uses for contract reads and nonce lookups. Calling `http()` with no argument leaves that undefined, the reads fall back to Base Sepolia's public endpoint, and its rate limit makes wallet derivation fail intermittently. +**Give `http()` a real RPC URL.** This is easy to get wrong, and it fails later, far from the line that caused it. The SDK reads `client.transport.url` to build the public client it uses for contract reads and nonce lookups. Calling `http()` with no argument leaves that undefined, the reads fall back to Base Sepolia's public endpoint, and its rate limit makes wallet derivation fail intermittently. ## The second construction {#the-second-construction} @@ -77,7 +74,7 @@ const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash }); if (!receipt.success) throw new Error("operation reverted"); ``` -Check `receipt.success`. This is the mistake worth guarding against: an operation whose calls revert is still mined and still returns a receipt, so the await resolving is not proof that anything landed. `receipt.receipt.transactionHash` is the onchain transaction, which is what a block explorer link needs. +A write the contract would refuse throws `ContractRevertError` before it is sent, with the contract's error name in `err.decoded?.errorName`. Still check `receipt.success`: if the chain changes between that check and inclusion, the operation can revert onchain, and it is then mined and still returns a receipt, so the await resolving is not proof that anything landed. `receipt.receipt.transactionHash` is the onchain transaction, which is what a block explorer link needs. ## Switching eSIM wallet {#switching-esim-wallet} @@ -91,7 +88,15 @@ session.setESIMWalletAddress(anotherESIMWalletAddress); ## Who deploys the device wallet {#who-deploys} -Normally the backend does, with `admin.deviceWalletFactory.createAccount`. See [backend setup](../backend/setup.md). The app only needs `deviceWalletFactory.createAccountWithEOA` if it holds its own funded EOA, which the wallet client above does not set up. +Either side can. The backend can deploy it with `admin.deviceWalletFactory.createAccount`, see [backend setup](../backend/setup.md). Or the app deploys it with its first sponsored user operation, since the smart account carries its own deployment: + +```ts +await session.deviceWallet!.sendUserOperation([]); // an empty operation that only deploys the wallet +``` + +A wallet deployed that way is not registered yet, so it cannot deploy eSIM wallets. The backend registers it with [`admin.deviceWalletFactory.postCreateAccount`](../backend/device-wallet-factory.md#postcreateaccount). + +The app only needs `deviceWalletFactory.createAccountWithEOA` if it holds its own funded EOA, which the wallet client above does not set up. ## Where to go next {#next} diff --git a/docs/sdk/mobile/smart-account.md b/docs/sdk/mobile/smart-account.md index 70f50dc..515ef73 100644 --- a/docs/sdk/mobile/smart-account.md +++ b/docs/sdk/mobile/smart-account.md @@ -32,6 +32,8 @@ const account = await kokio.smartAccount.getSmartWallet( Returns `KokioSmartAccount`, a viem smart account object. Pass it to `getSmartWalletClient`. +The wallet client given to `Kokio` does not need an `account`. The passkey signs, so a client with a chain and a transport is enough. + ## getSmartWalletClient {#getsmartwalletclient} Builds the client that signs with the passkey and sends user operations through Pimlico's bundler and paymaster. Every write on every other mobile surface needs this client, so build it once and reuse it. @@ -42,6 +44,16 @@ const smartAccountClient = await kokio.smartAccount.getSmartWalletClient(account Returns `KokioSmartAccountClient`, a bundler client that can also read contracts directly, since it carries viem's public actions too. Pass it as `smartAccountClient` to a new `Kokio(...)` call so `deviceWallet`, `eSIMWallet` and the rest become available. +Gas is paid by the paymaster at the same endpoint, so the device wallet never needs ETH. The gas policy id given to `Kokio` is optional: pass `""` to send none, or a Pimlico sponsorship policy id to have its rules applied. + +To send user operations somewhere other than Pimlico, such as a local bundler in tests, pass `bundlerUrl`. That endpoint must also answer the ERC-7677 paymaster methods. + +```ts +const smartAccountClient = await kokio.smartAccount.getSmartWalletClient(account, { + bundlerUrl: "http://127.0.0.1:4337", +}); +``` + ## P256 verifier {#p256-verifier} `kokio.P256Verifier` diff --git a/docs/sdk/overview.md b/docs/sdk/overview.md index 1419e1c..0845439 100644 --- a/docs/sdk/overview.md +++ b/docs/sdk/overview.md @@ -7,11 +7,19 @@ description: kokio-sdk is the TypeScript client for the Kokio eSIM contracts, wi `kokio-sdk` is the TypeScript package for talking to the Kokio eSIM contracts. It wraps [viem](https://viem.sh), including viem's account abstraction module, so you do not assemble calldata or manage an ERC-4337 bundler yourself. Install it, construct one of the two entry points, and call methods. +In the mobile app, install the passkey module alongside it: + +```sh +npm install kokio-sdk react-native-passkey +``` + +A backend that only uses `kokio-sdk/admin` needs the SDK alone: + ```sh npm install kokio-sdk ``` -Version 3.0.1. It ships as ES modules and needs Node 18 or newer, or a React Native runtime. `viem` comes with it, so there is nothing else to install. +Version 3.1.0. It ships as ES modules and needs Node 18 or newer, or a React Native runtime. `viem` comes with it. `react-native-passkey` is a peer dependency because it is a native module: Expo only links native code for packages the app lists itself, and one copy avoids version clashes. The admin entry point never loads it. ## Two entry points {#two-entry-points} @@ -46,14 +54,14 @@ try { await admin.registry.requestAdminUpdate(newAdmin); } catch (err) { if (err instanceof ContractRevertError) { - console.error("reverted:", err.message); + console.error("reverted:", err.decoded?.errorName); // e.g. "OwnableUnauthorizedAccount" } } ``` -`KokioError` is the base class. The named subclasses are `MissingSmartWalletError`, `MissingEOAWalletError`, `InvalidClientError`, `UnsupportedChainError`, `CounterfactualMismatchError` and `ContractRevertError`. `decodeContractRevert` turns raw revert data into a readable reason. +A write the contract would refuse throws `ContractRevertError` before anything is sent, on both entry points. For a user operation the SDK reads the reason out of the bundler's simulation error, so the contract's own error name reaches the app instead of a generic bundler message. `decoded` is `null` only when the revert data matches no Kokio contract's errors. -Some errors are not exported by name. The paginated `lazyWalletRegistry` calls can throw narrower subclasses such as `BatchSizeOutOfRangeError`. Catch those with `instanceof KokioError` and read `.code` rather than importing the class. +`KokioError` is the base class. The named subclasses are `MissingSmartWalletError`, `MissingEOAWalletError`, `InvalidClientError`, `UnsupportedChainError`, `UnconfiguredChainError`, `CounterfactualMismatchError` and `ContractRevertError`, plus the ones the paginated `lazyWalletRegistry` calls throw: `BatchSizeOutOfRangeError`, `DepositOnResumeError`, `ESIMWalletNotLazyDeployedError`, `MissingBatchEventError` and `StalledBatchError`. `decodeContractRevert` turns raw revert data into a readable reason. ## Chains {#chains} diff --git a/static/llms.txt b/static/llms.txt index 9003574..6ab3629 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -44,14 +44,14 @@ Every page below carries the full generated reference for its contract: every st ## TypeScript SDK -`kokio-sdk` v3.0.1 is the client library for the contracts above. Two entry points: `Kokio` for a mobile app signing with a passkey, `KokioAdmin` for a backend signing with an admin key. +`kokio-sdk` v3.1.0 is the client library for the contracts above. Two entry points: `Kokio` for a mobile app signing with a passkey, `KokioAdmin` for a backend signing with an admin key. - [SDK overview](https://docs.kokio.app/docs/sdk/overview): what the package is, the two entry points, install, errors, chains -- [Mobile setup](https://docs.kokio.app/docs/sdk/mobile/setup): constructing `Kokio`, the wallet client, the passkey, the bundler keys -- [Smart account](https://docs.kokio.app/docs/sdk/mobile/smart-account): resolving a passkey to its account, and verifying a signature -- [Device wallet, mobile](https://docs.kokio.app/docs/sdk/mobile/device-wallet): attach eSIM wallets, grant fund access, transfer ownership, manage gas +- [Mobile setup](https://docs.kokio.app/docs/sdk/mobile/setup): constructing `Kokio`, the wallet client, the passkey, the bundler keys, who deploys the device wallet +- [Smart account](https://docs.kokio.app/docs/sdk/mobile/smart-account): resolving a passkey to its account, sponsored gas, a custom bundler URL, and verifying a signature +- [Device wallet, mobile](https://docs.kokio.app/docs/sdk/mobile/device-wallet): deploy and bind an eSIM wallet in one operation, grant fund access, transfer ownership, manage gas - [Device wallet factory, mobile](https://docs.kokio.app/docs/sdk/mobile/device-wallet-factory): predict an address, check an identifier is free -- [eSIM wallet, mobile](https://docs.kokio.app/docs/sdk/mobile/esim-wallet): buy a data bundle, set a price cap, move an eSIM between devices +- [eSIM wallet, mobile](https://docs.kokio.app/docs/sdk/mobile/esim-wallet): buy a data bundle with or without fund access, set a price cap, move an eSIM between devices - [Registry, mobile](https://docs.kokio.app/docs/sdk/mobile/registry): bind an eSIM wallet, read the pause switch and price caps - [Payments, mobile](https://docs.kokio.app/docs/sdk/mobile/payments): which currencies are accepted, and how prices and symbols are encoded - [Backend setup](https://docs.kokio.app/docs/sdk/backend/setup): constructing `KokioAdmin`, and the trust boundary on the admin key