kokio-sdk 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ import { AdminProtocolAdminSubPackage } from "./interface/protocolAdminClass.js"
7
7
  import { AdminDeviceWalletSubPackage } from "./interface/deviceWalletClass.js";
8
8
  import { AdminESIMWalletSubPackage } from "./interface/eSIMWalletClass.js";
9
9
  import { AdminPaymentAdapterSubPackage } from "./interface/paymentAdapterClass.js";
10
+ import { AdminCallsSubPackage } from "./interface/callsClass.js";
10
11
  // Re-export the typed error surface so backend consumers can `instanceof
11
12
  // KokioError` (or a subclass) and decode reverts without reaching into internal
12
13
  // module paths - mirroring `config.ts`.
@@ -42,6 +43,8 @@ export class KokioAdmin {
42
43
  lazyWalletRegistry;
43
44
  protocolAdmin;
44
45
  paymentAdapter;
46
+ /** Calls for the app to sign as a user operation, built here so the backend holds the purchase logic. */
47
+ calls;
45
48
  // Instance-scoped surfaces - undefined until their address is known.
46
49
  deviceWallet;
47
50
  eSIMWallet;
@@ -56,6 +59,7 @@ export class KokioAdmin {
56
59
  this.lazyWalletRegistry = new AdminLazyWalletRegistrySubPackage(walletClient);
57
60
  this.protocolAdmin = new AdminProtocolAdminSubPackage(walletClient);
58
61
  this.paymentAdapter = new AdminPaymentAdapterSubPackage(walletClient);
62
+ this.calls = new AdminCallsSubPackage(walletClient);
59
63
  this.deviceWallet = deviceWalletAddress ? new AdminDeviceWalletSubPackage(walletClient, deviceWalletAddress) : undefined;
60
64
  this.eSIMWallet = eSIMWalletAddress ? new AdminESIMWalletSubPackage(walletClient, eSIMWalletAddress) : undefined;
61
65
  }
@@ -106,6 +110,7 @@ export class KokioAdmin {
106
110
  this.lazyWalletRegistry = new AdminLazyWalletRegistrySubPackage(walletClient);
107
111
  this.protocolAdmin = new AdminProtocolAdminSubPackage(walletClient);
108
112
  this.paymentAdapter = new AdminPaymentAdapterSubPackage(walletClient);
113
+ this.calls = new AdminCallsSubPackage(walletClient);
109
114
  this.deviceWallet = this.deviceWalletAddress ? new AdminDeviceWalletSubPackage(walletClient, this.deviceWalletAddress) : undefined;
110
115
  this.eSIMWallet = this.eSIMWalletAddress ? new AdminESIMWalletSubPackage(walletClient, this.eSIMWalletAddress) : undefined;
111
116
  return this;
@@ -0,0 +1,30 @@
1
+ import { publicActions } from "viem";
2
+ import { _acceptAndBindESIMWalletCalls, _buyDataBundleWithTransferCalls } from "../../logic/calls/eSIMWallet.calls.js";
3
+ /**
4
+ * Builds the calls a user's device wallet signs as one user operation. Nothing
5
+ * is signed or sent here: hand the result to the app, which passes it to
6
+ * `deviceWallet.sendUserOperation`. Addresses are passed per call, since one
7
+ * backend builds calls for many users.
8
+ */
9
+ export class AdminCallsSubPackage {
10
+ walletClient;
11
+ constructor(walletClient) {
12
+ this.walletClient = walletClient;
13
+ }
14
+ /**
15
+ * The calls `kokio.eSIMWallet.buyDataBundleWithTransfer` sends: a token
16
+ * transfer from the device wallet for whatever the eSIM wallet is short of,
17
+ * then the purchase. The purchase emits `DataBundleBoughtWithToken` as usual.
18
+ */
19
+ buyDataBundleWithTransfer(eSIMWalletAddress, dataBundleDetails, asset, maxAmountIn, paymentReference) {
20
+ return _buyDataBundleWithTransferCalls(this.walletClient.extend(publicActions), eSIMWalletAddress, dataBundleDetails, asset, maxAmountIn, paymentReference);
21
+ }
22
+ /**
23
+ * The calls `kokio.eSIMWallet.acceptAndBindESIMWallet` sends, for the device
24
+ * wallet named in `requestTransferOwnership` to sign. Build them once the
25
+ * `OwnershipTransferRequested` event names that device wallet as `_newOwner`.
26
+ */
27
+ acceptAndBindESIMWallet(eSIMWalletAddress, newDeviceWalletAddress, options = {}) {
28
+ return _acceptAndBindESIMWalletCalls(eSIMWalletAddress, newDeviceWalletAddress, options.grantAccessToFunds ?? false);
29
+ }
30
+ }
@@ -1,4 +1,4 @@
1
- import { _acceptOwnershipTransfer, _buyDataBundleWithToken, _buyDataBundleWithTransfer, _priceCapUSDCents, _deviceWallet, _owner, _requestTransferOwnership, _sendETHToDeviceWallet, _sendTokenToDeviceWallet, _setPriceCapUSDCents, _transactionHistory } from "../logic/eSIMWallet.js";
1
+ import { _acceptAndBindESIMWallet, _acceptOwnershipTransfer, _buyDataBundleWithToken, _buyDataBundleWithTransfer, _priceCapUSDCents, _deviceWallet, _owner, _requestTransferOwnership, _sendETHToDeviceWallet, _sendTokenToDeviceWallet, _setPriceCapUSDCents, _transactionHistory } from "../logic/eSIMWallet.js";
2
2
  export class ESIMWalletSubPackage {
3
3
  client;
4
4
  address;
@@ -9,6 +9,14 @@ export class ESIMWalletSubPackage {
9
9
  acceptOwnershipTransfer() {
10
10
  return _acceptOwnershipTransfer(this.client, this.address);
11
11
  }
12
+ /**
13
+ * Accept this eSIM wallet's transfer and bind it to the signing device
14
+ * wallet in one user operation, optionally granting it access to that
15
+ * wallet's tokens too.
16
+ */
17
+ acceptAndBindESIMWallet(options = {}) {
18
+ return _acceptAndBindESIMWallet(this.client, this.address, options.grantAccessToFunds ?? false);
19
+ }
12
20
  buyDataBundleWithToken(dataBundleDetails, asset, maxAmountIn, paymentReference) {
13
21
  return _buyDataBundleWithToken(this.client, this.address, dataBundleDetails, asset, maxAmountIn, paymentReference);
14
22
  }
@@ -0,0 +1,72 @@
1
+ import { encodeFunctionData, erc20Abi } from "viem";
2
+ import { DeviceWallet, ESIMWallet, PaymentAdapter, Registry } from "../../abis/index.js";
3
+ import { _chainId, _getChainSpecificConstants } from "../constants.js";
4
+ /**
5
+ * The calls for buying a data bundle with tokens the device wallet sends over
6
+ * in the same user operation, so the eSIM wallet needs no access to the device
7
+ * wallet's funds.
8
+ *
9
+ * Only the shortfall is sent: the quote for the bundle minus what the eSIM
10
+ * wallet already holds of `asset`, worked out when this runs. If that balance
11
+ * or the quote changes before the operation lands, it reverts, so build the
12
+ * calls again rather than resending old ones.
13
+ */
14
+ export const _buyDataBundleWithTransferCalls = async (client, eSIMWalletAddress, dataBundleDetails, asset, maxAmountIn, paymentReference) => {
15
+ const chainID = await _chainId(client);
16
+ const values = _getChainSpecificConstants(chainID, client.transport.url);
17
+ // The eSIM wallet pays through whichever adapter the registry names, so read it there.
18
+ const adapter = await client.readContract({
19
+ address: values.factoryAddresses.REGISTRY, abi: Registry, functionName: "paymentAdapter"
20
+ });
21
+ const [{ token }, amountIn] = await Promise.all([
22
+ client.readContract({
23
+ address: adapter, abi: PaymentAdapter, functionName: "resolveAsset", args: [asset]
24
+ }),
25
+ client.readContract({
26
+ address: adapter, abi: PaymentAdapter, functionName: "quote", args: [asset, dataBundleDetails.priceUSDCents]
27
+ }),
28
+ ]);
29
+ const held = await client.readContract({
30
+ address: token, abi: erc20Abi, functionName: "balanceOf", args: [eSIMWalletAddress]
31
+ });
32
+ const buy = {
33
+ to: eSIMWalletAddress,
34
+ data: encodeFunctionData({
35
+ abi: ESIMWallet,
36
+ functionName: "buyDataBundleWithToken",
37
+ args: [dataBundleDetails, asset, maxAmountIn, paymentReference]
38
+ })
39
+ };
40
+ if (held >= amountIn)
41
+ return [buy];
42
+ return [
43
+ {
44
+ to: token,
45
+ data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [eSIMWalletAddress, amountIn - held] })
46
+ },
47
+ buy
48
+ ];
49
+ };
50
+ /**
51
+ * The calls for the new device wallet to take over an eSIM wallet another
52
+ * device wallet asked to hand it: accept the transfer, then bind it, which also
53
+ * tells the registry and clears the standby flag.
54
+ *
55
+ * Accepting first is what lets the bind through: it makes this device wallet
56
+ * the owner and clears the pending transfer. Fund access cannot be granted at
57
+ * bind time, so `grantAccessToFunds` adds a `toggleAccessToFunds` after it.
58
+ */
59
+ export const _acceptAndBindESIMWalletCalls = (eSIMWalletAddress, deviceWalletAddress, grantAccessToFunds) => {
60
+ const self = (functionName, hasAccessToFunds) => ({
61
+ to: deviceWalletAddress,
62
+ data: encodeFunctionData({ abi: DeviceWallet, functionName, args: [eSIMWalletAddress, hasAccessToFunds] })
63
+ });
64
+ return [
65
+ {
66
+ to: eSIMWalletAddress,
67
+ data: encodeFunctionData({ abi: ESIMWallet, functionName: "acceptOwnershipTransfer", args: [] })
68
+ },
69
+ self("addESIMWallet", false),
70
+ ...(grantAccessToFunds ? [self("toggleAccessToFunds", true)] : [])
71
+ ];
72
+ };
@@ -1,8 +1,8 @@
1
- import { encodeFunctionData, erc20Abi, maxUint256 } from "viem";
1
+ import { encodeFunctionData, maxUint256 } from "viem";
2
2
  import { MissingSmartWalletError } from "./errors.js";
3
- import { ESIMWallet, PaymentAdapter, Registry } from "../abis/index.js";
4
- import { _chainId, _getChainSpecificConstants } from "./constants.js";
3
+ import { ESIMWallet } from "../abis/index.js";
5
4
  import { _defaultPriceCapUSDCents } from "./registry.js";
5
+ import { _acceptAndBindESIMWalletCalls, _buyDataBundleWithTransferCalls } from "./calls/eSIMWallet.calls.js";
6
6
  // Not exposed on this surface:
7
7
  // - populateHistory and setESIMUniqueIdentifier are `onlyRegistry` - callable
8
8
  // only by the registry contract. Naming an eSIM goes through
@@ -68,45 +68,14 @@ export const _buyDataBundleWithToken = async (client, address, dataBundleDetails
68
68
  *
69
69
  * Only the shortfall is sent: the quote for the bundle minus what this eSIM
70
70
  * wallet already holds of `asset`. Arguments are as for `buyDataBundleWithToken`.
71
+ * The backend builds the same calls with `admin.calls.buyDataBundleWithTransfer`.
71
72
  */
72
73
  export const _buyDataBundleWithTransfer = async (client, address, dataBundleDetails, asset, maxAmountIn, paymentReference) => {
73
- const chainID = await _chainId(client);
74
- const rpcURL = client.transport.url;
75
- const values = _getChainSpecificConstants(chainID, rpcURL);
76
74
  if (!client.account)
77
75
  throw new MissingSmartWalletError();
78
- // The eSIM wallet pays through whichever adapter the registry names, so read it there.
79
- const adapter = await client.readContract({
80
- address: values.factoryAddresses.REGISTRY, abi: Registry, functionName: "paymentAdapter"
81
- });
82
- const [{ token }, amountIn] = await Promise.all([
83
- client.readContract({
84
- address: adapter, abi: PaymentAdapter, functionName: "resolveAsset", args: [asset]
85
- }),
86
- client.readContract({
87
- address: adapter, abi: PaymentAdapter, functionName: "quote", args: [asset, dataBundleDetails.priceUSDCents]
88
- }),
89
- ]);
90
- const held = await client.readContract({
91
- address: token, abi: erc20Abi, functionName: "balanceOf", args: [address]
92
- });
93
- const buy = {
94
- to: address,
95
- data: encodeFunctionData({
96
- abi: ESIMWallet,
97
- functionName: "buyDataBundleWithToken",
98
- args: [dataBundleDetails, asset, maxAmountIn, paymentReference]
99
- })
100
- };
101
76
  return client.sendUserOperation({
102
77
  account: client.account,
103
- calls: held >= amountIn ? [buy] : [
104
- {
105
- to: token,
106
- data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [address, amountIn - held] })
107
- },
108
- buy
109
- ]
78
+ calls: await _buyDataBundleWithTransferCalls(client, address, dataBundleDetails, asset, maxAmountIn, paymentReference)
110
79
  });
111
80
  };
112
81
  /**
@@ -228,6 +197,21 @@ export const _acceptOwnershipTransfer = async (client, address) => {
228
197
  }]
229
198
  });
230
199
  };
200
+ /**
201
+ * Accept an eSIM wallet another device wallet asked to hand over, and bind it,
202
+ * in one user operation. `grantAccessToFunds` also lets it pull this device
203
+ * wallet's tokens. The backend builds the same calls with
204
+ * `admin.calls.acceptAndBindESIMWallet`.
205
+ */
206
+ export const _acceptAndBindESIMWallet = async (client, address, grantAccessToFunds) => {
207
+ if (!client.account)
208
+ throw new MissingSmartWalletError();
209
+ // UserOp - the sender is the pending `newRequestedOwner`, and then the owner the bind needs.
210
+ return client.sendUserOperation({
211
+ account: client.account,
212
+ calls: _acceptAndBindESIMWalletCalls(address, client.account.address, grantAccessToFunds)
213
+ });
214
+ };
231
215
  export const _sendETHToDeviceWallet = async (client, address, amount) => {
232
216
  if (!client.account)
233
217
  throw new MissingSmartWalletError();
@@ -8,6 +8,7 @@ import { AdminProtocolAdminSubPackage } from "./interface/protocolAdminClass.js"
8
8
  import { AdminDeviceWalletSubPackage } from "./interface/deviceWalletClass.js";
9
9
  import { AdminESIMWalletSubPackage } from "./interface/eSIMWalletClass.js";
10
10
  import { AdminPaymentAdapterSubPackage } from "./interface/paymentAdapterClass.js";
11
+ import { AdminCallsSubPackage } from "./interface/callsClass.js";
11
12
  export { KokioError, NullOrUndefinedValueError, MissingSmartWalletError, MissingEOAWalletError, InvalidClientError, UnsupportedChainError, UnconfiguredChainError, CounterfactualMismatchError, BatchSizeOutOfRangeError, DepositOnResumeError, ESIMWalletNotLazyDeployedError, MissingBatchEventError, StalledBatchError, ContractRevertError, decodeContractRevert, } from "../logic/errors.js";
12
13
  export type { DecodedRevert } from "../logic/errors.js";
13
14
  export { OperationState } from "../logic/admin/reads/protocolAdmin.reads.js";
@@ -38,6 +39,8 @@ export declare class KokioAdmin {
38
39
  lazyWalletRegistry: AdminLazyWalletRegistrySubPackage;
39
40
  protocolAdmin: AdminProtocolAdminSubPackage;
40
41
  paymentAdapter: AdminPaymentAdapterSubPackage;
42
+ /** Calls for the app to sign as a user operation, built here so the backend holds the purchase logic. */
43
+ calls: AdminCallsSubPackage;
41
44
  deviceWallet?: AdminDeviceWalletSubPackage;
42
45
  eSIMWallet?: AdminESIMWalletSubPackage;
43
46
  constructor(walletClient: WalletClient, deviceWalletAddress?: Address, eSIMWalletAddress?: Address);
@@ -0,0 +1,26 @@
1
+ import { Address, Hex, WalletClient } from "viem";
2
+ import { DataBundleDetails } from "../../types.js";
3
+ /**
4
+ * Builds the calls a user's device wallet signs as one user operation. Nothing
5
+ * is signed or sent here: hand the result to the app, which passes it to
6
+ * `deviceWallet.sendUserOperation`. Addresses are passed per call, since one
7
+ * backend builds calls for many users.
8
+ */
9
+ export declare class AdminCallsSubPackage {
10
+ walletClient: WalletClient;
11
+ constructor(walletClient: WalletClient);
12
+ /**
13
+ * The calls `kokio.eSIMWallet.buyDataBundleWithTransfer` sends: a token
14
+ * transfer from the device wallet for whatever the eSIM wallet is short of,
15
+ * then the purchase. The purchase emits `DataBundleBoughtWithToken` as usual.
16
+ */
17
+ buyDataBundleWithTransfer(eSIMWalletAddress: Address, dataBundleDetails: DataBundleDetails, asset: Hex, maxAmountIn: bigint, paymentReference: Hex): Promise<import("../../types.js").Call[]>;
18
+ /**
19
+ * The calls `kokio.eSIMWallet.acceptAndBindESIMWallet` sends, for the device
20
+ * wallet named in `requestTransferOwnership` to sign. Build them once the
21
+ * `OwnershipTransferRequested` event names that device wallet as `_newOwner`.
22
+ */
23
+ acceptAndBindESIMWallet(eSIMWalletAddress: Address, newDeviceWalletAddress: Address, options?: {
24
+ grantAccessToFunds?: boolean;
25
+ }): import("../../types.js").Call[];
26
+ }
@@ -6,6 +6,14 @@ export declare class ESIMWalletSubPackage {
6
6
  address: `0x${string}`;
7
7
  constructor(client: KokioSmartAccountClient, address: Address);
8
8
  acceptOwnershipTransfer(): Promise<`0x${string}`>;
9
+ /**
10
+ * Accept this eSIM wallet's transfer and bind it to the signing device
11
+ * wallet in one user operation, optionally granting it access to that
12
+ * wallet's tokens too.
13
+ */
14
+ acceptAndBindESIMWallet(options?: {
15
+ grantAccessToFunds?: boolean;
16
+ }): Promise<`0x${string}`>;
9
17
  buyDataBundleWithToken(dataBundleDetails: DataBundleDetails, asset: Hex, maxAmountIn: bigint, paymentReference: Hex): Promise<`0x${string}`>;
10
18
  /**
11
19
  * Same purchase as `buyDataBundleWithToken`, with the device wallet sending
@@ -0,0 +1,25 @@
1
+ import { Address, Client, Hex, PublicActions } from "viem";
2
+ import { Call, DataBundleDetails } from "../../types.js";
3
+ /** Any client that can read contracts: the mobile smart account client, or a wallet client extended with `publicActions`. */
4
+ export type CallBuilderClient = Pick<PublicActions, "readContract" | "getChainId"> & Pick<Client, "transport">;
5
+ /**
6
+ * The calls for buying a data bundle with tokens the device wallet sends over
7
+ * in the same user operation, so the eSIM wallet needs no access to the device
8
+ * wallet's funds.
9
+ *
10
+ * Only the shortfall is sent: the quote for the bundle minus what the eSIM
11
+ * wallet already holds of `asset`, worked out when this runs. If that balance
12
+ * or the quote changes before the operation lands, it reverts, so build the
13
+ * calls again rather than resending old ones.
14
+ */
15
+ export declare const _buyDataBundleWithTransferCalls: (client: CallBuilderClient, eSIMWalletAddress: Address, dataBundleDetails: DataBundleDetails, asset: Hex, maxAmountIn: bigint, paymentReference: Hex) => Promise<Call[]>;
16
+ /**
17
+ * The calls for the new device wallet to take over an eSIM wallet another
18
+ * device wallet asked to hand it: accept the transfer, then bind it, which also
19
+ * tells the registry and clears the standby flag.
20
+ *
21
+ * Accepting first is what lets the bind through: it makes this device wallet
22
+ * the owner and clears the pending transfer. Fund access cannot be granted at
23
+ * bind time, so `grantAccessToFunds` adds a `toggleAccessToFunds` after it.
24
+ */
25
+ export declare const _acceptAndBindESIMWalletCalls: (eSIMWalletAddress: Address, deviceWalletAddress: Address, grantAccessToFunds: boolean) => Call[];
@@ -27,6 +27,7 @@ export declare const _buyDataBundleWithToken: (client: KokioSmartAccountClient,
27
27
  *
28
28
  * Only the shortfall is sent: the quote for the bundle minus what this eSIM
29
29
  * wallet already holds of `asset`. Arguments are as for `buyDataBundleWithToken`.
30
+ * The backend builds the same calls with `admin.calls.buyDataBundleWithTransfer`.
30
31
  */
31
32
  export declare const _buyDataBundleWithTransfer: (client: KokioSmartAccountClient, address: Address, dataBundleDetails: DataBundleDetails, asset: Hex, maxAmountIn: bigint, paymentReference: Hex) => Promise<`0x${string}`>;
32
33
  /**
@@ -69,4 +70,11 @@ export declare const _deviceWallet: (client: KokioSmartAccountClient, address: A
69
70
  export declare const _transactionHistory: (client: KokioSmartAccountClient, address: Address, index: bigint) => Promise<DataBundleDetails>;
70
71
  export declare const _requestTransferOwnership: (client: KokioSmartAccountClient, address: Address, newOwner: Address) => Promise<`0x${string}`>;
71
72
  export declare const _acceptOwnershipTransfer: (client: KokioSmartAccountClient, address: Address) => Promise<`0x${string}`>;
73
+ /**
74
+ * Accept an eSIM wallet another device wallet asked to hand over, and bind it,
75
+ * in one user operation. `grantAccessToFunds` also lets it pull this device
76
+ * wallet's tokens. The backend builds the same calls with
77
+ * `admin.calls.acceptAndBindESIMWallet`.
78
+ */
79
+ export declare const _acceptAndBindESIMWallet: (client: KokioSmartAccountClient, address: Address, grantAccessToFunds: boolean) => Promise<`0x${string}`>;
72
80
  export declare const _sendETHToDeviceWallet: (client: KokioSmartAccountClient, address: Address, amount: bigint) => Promise<`0x${string}`>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kokio-sdk",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/config.js",