kokio-sdk 3.3.0 → 3.5.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.
Files changed (55) hide show
  1. package/README.md +8 -4
  2. package/dist/esm/abis/DeviceWallet.js +29 -0
  3. package/dist/esm/abis/ESIMWallet.js +55 -0
  4. package/dist/esm/abis/PaymentAdapter.js +53 -0
  5. package/dist/esm/abis/ProtocolAdmin.js +48 -0
  6. package/dist/esm/abis/Registry.js +55 -0
  7. package/dist/esm/abis/RegistryHelper.js +13 -0
  8. package/dist/esm/admin/config-admin.js +3 -1
  9. package/dist/esm/admin/interface/eSIMWalletClass.js +4 -1
  10. package/dist/esm/admin/interface/paymentAdapterClass.js +4 -1
  11. package/dist/esm/admin/interface/protocolAdminClass.js +7 -1
  12. package/dist/esm/admin/interface/registryClass.js +6 -3
  13. package/dist/esm/admin/interface/utilsClass.js +19 -3
  14. package/dist/esm/interface/deviceWalletClass.js +6 -3
  15. package/dist/esm/interface/eSIMWalletClass.js +7 -1
  16. package/dist/esm/interface/registryClass.js +4 -1
  17. package/dist/esm/logic/admin/eSIMWallet.eoa.js +9 -1
  18. package/dist/esm/logic/admin/paymentAdapter.eoa.js +16 -0
  19. package/dist/esm/logic/admin/protocolAdmin.eoa.js +34 -0
  20. package/dist/esm/logic/admin/reads/eSIMWallet.reads.js +12 -0
  21. package/dist/esm/logic/admin/reads/registry.reads.js +12 -0
  22. package/dist/esm/logic/admin/registry.eoa.js +79 -5
  23. package/dist/esm/logic/admin/utils/paymentReference.js +58 -0
  24. package/dist/esm/logic/constants.js +11 -14
  25. package/dist/esm/logic/deviceWallet.js +19 -6
  26. package/dist/esm/logic/eSIMWallet.js +36 -0
  27. package/dist/esm/logic/errors.js +38 -0
  28. package/dist/esm/logic/registry.js +12 -0
  29. package/dist/types/abis/DeviceWallet.d.ts +22 -0
  30. package/dist/types/abis/ESIMWallet.d.ts +42 -0
  31. package/dist/types/abis/PaymentAdapter.d.ts +42 -0
  32. package/dist/types/abis/ProtocolAdmin.d.ts +37 -0
  33. package/dist/types/abis/Registry.d.ts +42 -0
  34. package/dist/types/abis/RegistryHelper.d.ts +10 -0
  35. package/dist/types/admin/config-admin.d.ts +2 -1
  36. package/dist/types/admin/interface/eSIMWalletClass.d.ts +1 -0
  37. package/dist/types/admin/interface/paymentAdapterClass.d.ts +1 -0
  38. package/dist/types/admin/interface/protocolAdminClass.d.ts +2 -0
  39. package/dist/types/admin/interface/registryClass.d.ts +14 -1
  40. package/dist/types/admin/interface/utilsClass.d.ts +19 -4
  41. package/dist/types/interface/deviceWalletClass.d.ts +2 -1
  42. package/dist/types/interface/eSIMWalletClass.d.ts +2 -0
  43. package/dist/types/interface/registryClass.d.ts +1 -0
  44. package/dist/types/logic/admin/eSIMWallet.eoa.d.ts +3 -0
  45. package/dist/types/logic/admin/paymentAdapter.eoa.d.ts +6 -0
  46. package/dist/types/logic/admin/protocolAdmin.eoa.d.ts +14 -0
  47. package/dist/types/logic/admin/reads/eSIMWallet.reads.d.ts +5 -0
  48. package/dist/types/logic/admin/reads/registry.reads.d.ts +2 -0
  49. package/dist/types/logic/admin/registry.eoa.d.ts +23 -2
  50. package/dist/types/logic/admin/utils/paymentReference.d.ts +26 -0
  51. package/dist/types/logic/deviceWallet.d.ts +11 -5
  52. package/dist/types/logic/eSIMWallet.d.ts +14 -0
  53. package/dist/types/logic/errors.d.ts +27 -0
  54. package/dist/types/logic/registry.d.ts +2 -0
  55. package/package.json +1 -1
@@ -271,6 +271,25 @@ export const _cancel = async (client, id) => {
271
271
  // ---------------------------------------------------------------------------
272
272
  // Guardian powers - GUARDIAN_ROLE, no delay
273
273
  // ---------------------------------------------------------------------------
274
+ /**
275
+ * Pause a protocol contract straight away. `GUARDIAN_ROLE`.
276
+ *
277
+ * Only stops the contract, which the admin key could already do, and a guardian
278
+ * can release it at once with `_unpauseInstantly`.
279
+ */
280
+ export const _pauseInstantly = async (client, target) => {
281
+ const values = await _resolve(client);
282
+ if (!client.account)
283
+ throw new MissingEOAWalletError();
284
+ return writeContractOrThrow(client, {
285
+ address: values.factoryAddresses.PROTOCOL_ADMIN,
286
+ chain: values.chain,
287
+ account: client.account,
288
+ abi: ProtocolAdmin,
289
+ functionName: 'pauseInstantly',
290
+ args: [target]
291
+ });
292
+ };
274
293
  /**
275
294
  * Release a pause on a protocol contract straight away. `GUARDIAN_ROLE`.
276
295
  *
@@ -472,6 +491,21 @@ export const _setDefaultPriceCapUSDCentsCall = async (client, cap, target) => {
472
491
  args: [cap],
473
492
  };
474
493
  };
494
+ /**
495
+ * Set what the admin key may charge each newly created eSIM wallet, in USD
496
+ * cents. Pass the result to `schedule`. Existing wallets keep their own figure.
497
+ *
498
+ * `target` defaults to the registry.
499
+ */
500
+ export const _setDefaultAdminAllowanceUSDCentsCall = async (client, allowance, target) => {
501
+ const values = await _resolve(client);
502
+ return {
503
+ address: target ?? values.factoryAddresses.REGISTRY,
504
+ abi: Registry,
505
+ functionName: 'setDefaultAdminAllowanceUSDCents',
506
+ args: [allowance],
507
+ };
508
+ };
475
509
  /**
476
510
  * Point the registry at a new payment adapter. Pass the result to `schedule`.
477
511
  *
@@ -36,6 +36,18 @@ export const _priceCapUSDCents = async (client, eSIMWalletAddress) => {
36
36
  args: []
37
37
  });
38
38
  };
39
+ /**
40
+ * What the admin key may still charge this eSIM wallet, in USD cents. Only the
41
+ * owner can raise it, so a purchase above it needs the user first.
42
+ */
43
+ export const _adminAllowanceUSDCents = async (client, eSIMWalletAddress) => {
44
+ return client.extend(publicActions).readContract({
45
+ address: eSIMWalletAddress,
46
+ abi: ESIMWallet,
47
+ functionName: "adminAllowanceUSDCents",
48
+ args: []
49
+ });
50
+ };
39
51
  /** The pending owner proposed via `requestTransferOwnership` (zero if none). */
40
52
  export const _newRequestedOwner = async (client, eSIMWalletAddress) => {
41
53
  return client.extend(publicActions).readContract({
@@ -83,6 +83,18 @@ export const _defaultPriceCapUSDCents = async (client) => {
83
83
  args: []
84
84
  });
85
85
  };
86
+ /** What the admin key may charge a newly created eSIM wallet in total, in USD cents. */
87
+ export const _defaultAdminAllowanceUSDCents = async (client) => {
88
+ const chainID = await _chainId(client);
89
+ const rpcURL = client.transport.url;
90
+ const values = _getChainSpecificConstants(chainID, rpcURL);
91
+ return client.extend(publicActions).readContract({
92
+ address: values.factoryAddresses.REGISTRY,
93
+ abi: Registry,
94
+ functionName: "defaultAdminAllowanceUSDCents",
95
+ args: []
96
+ });
97
+ };
86
98
  /** The vault EOA recorded in the registry. */
87
99
  export const _vault = async (client) => {
88
100
  const chainID = await _chainId(client);
@@ -1,6 +1,9 @@
1
+ import { encodeAbiParameters, keccak256, publicActions, stringToHex } from "viem";
1
2
  import { _chainId, _getChainSpecificConstants } from "../constants.js";
2
- import { MissingEOAWalletError, writeContractOrThrow } from "../errors.js";
3
+ import { CouponSplitOutOfRangeError, InvalidPaymentReferenceError, MissingEOAWalletError, TransactionRevertedError, writeContractOrThrow, } from "../errors.js";
3
4
  import { Registry } from "../../abis/index.js";
5
+ import { Settlement } from "../../types.js";
6
+ import { PaymentReferenceKind, _parsePaymentReference } from "./utils/paymentReference.js";
4
7
  // Admin-EOA logic for `Registry`. Most of this is `onlyOwner`, so the `client`
5
8
  // must carry the owner EOA. `_acceptAdminUpdate` is the nominee's own call and
6
9
  // `_assignESIMIdentifier` is `onlyESIMWalletAdmin`.
@@ -207,21 +210,92 @@ export const _assignESIMIdentifier = async (client, eSIMWalletAddress, eSIMUniqu
207
210
  * never sees a transfer to prove it), and `_tokenAmount` is recorded for
208
211
  * offchain matching but never checked against `_dataBundleDetail.priceUSDCents`.
209
212
  * `_paymentReference` is spendable once per eSIM wallet.
213
+ *
214
+ * Given `[couponRef, remainderRef]` and `couponUSDCents`, records a purchase
215
+ * split between a coupon and another payment as two lines, see
216
+ * {@link _couponSplitLines}. Lines already recorded are skipped, so a retry
217
+ * after a partial failure sends only what is missing. Returns the last hash
218
+ * sent; any earlier one is already mined.
210
219
  */
211
- export const _recordSettledPurchase = async (client, eSIMWalletAddress, dataBundleDetail, asset, tokenAmount, paymentReference) => {
220
+ export const _recordSettledPurchase = async (client, eSIMWalletAddress, dataBundleDetail, asset, tokenAmount, paymentReference, couponUSDCents) => {
212
221
  const chainID = await _chainId(client);
213
222
  const rpcURL = client.transport.url;
214
223
  const values = _getChainSpecificConstants(chainID, rpcURL);
215
224
  if (!client.account)
216
225
  throw new MissingEOAWalletError();
217
- return writeContractOrThrow(client, {
226
+ const account = client.account;
227
+ const record = (line) => writeContractOrThrow(client, {
218
228
  address: values.factoryAddresses.REGISTRY,
219
229
  chain: values.chain,
220
- account: client.account,
230
+ account,
221
231
  abi: Registry,
222
232
  functionName: 'recordSettledPurchase',
223
- args: [eSIMWalletAddress, dataBundleDetail, asset, tokenAmount, paymentReference]
233
+ args: [eSIMWalletAddress, line.dataBundleDetail, line.asset, line.tokenAmount, line.paymentReference]
224
234
  });
235
+ if (typeof paymentReference === "string") {
236
+ return record({ dataBundleDetail, asset, tokenAmount, paymentReference });
237
+ }
238
+ const lines = _couponSplitLines(dataBundleDetail, asset, tokenAmount, paymentReference, couponUSDCents);
239
+ const publicClient = client.extend(publicActions);
240
+ const spent = await Promise.all(lines.map((line) => publicClient.readContract({
241
+ address: values.factoryAddresses.REGISTRY,
242
+ abi: Registry,
243
+ functionName: "usedPaymentReferences",
244
+ args: [keccak256(encodeAbiParameters([{ type: "address" }, { type: "bytes32" }], [eSIMWalletAddress, line.paymentReference]))]
245
+ })));
246
+ const missing = lines.filter((_, i) => !spent[i]);
247
+ // With both lines recorded, the remainder is sent anyway so the contract refuses it with
248
+ // `PaymentReferenceAlreadyUsed`, the same answer a retried single reference gets.
249
+ let hash;
250
+ for (const line of missing.length > 0 ? missing : [lines[0]]) {
251
+ if (hash) {
252
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
253
+ if (receipt.status !== "success")
254
+ throw new TransactionRevertedError(hash);
255
+ }
256
+ hash = await record(line);
257
+ }
258
+ return hash;
259
+ };
260
+ // Coupons are recorded as card payments in USD, whose smallest unit is a cent.
261
+ const COUPON_ASSET = stringToHex("USD", { size: 32 });
262
+ /**
263
+ * Splits one purchase into its remainder line and its coupon line, in that
264
+ * order. `dataBundleDetail` carries the full price and how the remainder was
265
+ * paid; `asset` and `tokenAmount` are what the user paid for the remainder.
266
+ * The coupon line is always `Fiat` in `USD`, with the coupon's cents as its
267
+ * token amount.
268
+ */
269
+ export const _couponSplitLines = (dataBundleDetail, asset, tokenAmount, [couponRef, remainderRef], couponUSDCents) => {
270
+ const coupon = _parsePaymentReference(couponRef);
271
+ const remainder = _parsePaymentReference(remainderRef);
272
+ if (coupon.kind !== PaymentReferenceKind.CouponPart) {
273
+ throw new InvalidPaymentReferenceError(couponRef, "is not tagged as a coupon part");
274
+ }
275
+ if (remainder.kind !== PaymentReferenceKind.Remainder) {
276
+ throw new InvalidPaymentReferenceError(remainderRef, "is not tagged as a remainder");
277
+ }
278
+ if (coupon.reference !== remainder.reference) {
279
+ throw new InvalidPaymentReferenceError(remainderRef, `belongs to a different order than ${couponRef}`);
280
+ }
281
+ const price = dataBundleDetail.priceUSDCents;
282
+ if (couponUSDCents === undefined || couponUSDCents <= 0n || couponUSDCents >= price) {
283
+ throw new CouponSplitOutOfRangeError(couponUSDCents, price);
284
+ }
285
+ return [
286
+ {
287
+ dataBundleDetail: { ...dataBundleDetail, priceUSDCents: price - couponUSDCents },
288
+ asset,
289
+ tokenAmount,
290
+ paymentReference: remainderRef,
291
+ },
292
+ {
293
+ dataBundleDetail: { id: dataBundleDetail.id, priceUSDCents: couponUSDCents, settlement: Settlement.Fiat },
294
+ asset: COUPON_ASSET,
295
+ tokenAmount: couponUSDCents,
296
+ paymentReference: couponRef,
297
+ },
298
+ ];
225
299
  };
226
300
  /**
227
301
  * Step 2 of the 2-step admin handover: the nominee accepts. The chain requires
@@ -0,0 +1,58 @@
1
+ import { concat, isHex, pad, size, slice } from "viem";
2
+ import { InvalidPaymentReferenceError } from "../../errors.js";
3
+ /** How a purchase was paid for, as written into its payment reference. */
4
+ export var PaymentReferenceKind;
5
+ (function (PaymentReferenceKind) {
6
+ /** Paid in full without a coupon. Carries no tag. */
7
+ PaymentReferenceKind["Standard"] = "Standard";
8
+ /** Paid in full by a coupon. */
9
+ PaymentReferenceKind["Coupon"] = "Coupon";
10
+ /** The coupon's share of a purchase split between a coupon and another payment. */
11
+ PaymentReferenceKind["CouponPart"] = "CouponPart";
12
+ /** What the user paid on top of the coupon in a split purchase. */
13
+ PaymentReferenceKind["Remainder"] = "Remainder";
14
+ })(PaymentReferenceKind || (PaymentReferenceKind = {}));
15
+ // The backend puts the order id in the low 12 bytes, leaving the high 20 free for a tag.
16
+ // Every coupon tag starts with 0xfee0ff ("fee off") so it stands out on an explorer.
17
+ const TAG_SIZE = 20;
18
+ const TAGS = {
19
+ [PaymentReferenceKind.Standard]: pad("0x", { size: TAG_SIZE }),
20
+ [PaymentReferenceKind.Coupon]: pad("0xfee0ff", { size: TAG_SIZE, dir: "right" }),
21
+ [PaymentReferenceKind.CouponPart]: pad("0xfee0ffc0de", { size: TAG_SIZE, dir: "right" }),
22
+ [PaymentReferenceKind.Remainder]: pad("0xfee0ffba1a5ce0", { size: TAG_SIZE, dir: "right" }),
23
+ };
24
+ // Lowercased so a checksum-style or uppercase input compares equal to the tags above.
25
+ const _splitReference = (reference) => {
26
+ if (!isHex(reference, { strict: true }) || size(reference) !== 32) {
27
+ throw new InvalidPaymentReferenceError(reference, "is not 32 bytes of hex");
28
+ }
29
+ const lower = reference.toLowerCase();
30
+ const order = slice(lower, TAG_SIZE);
31
+ // The contracts refuse a zero reference, and a tag would hide an empty order id from that check.
32
+ if (BigInt(order) === 0n)
33
+ throw new InvalidPaymentReferenceError(reference, "has an empty order part");
34
+ return { tag: slice(lower, 0, TAG_SIZE), order };
35
+ };
36
+ /**
37
+ * Writes `kind`'s tag into the high 20 bytes of `reference`, which must be zero
38
+ * there. Tag both halves of a split purchase from the same reference, so each
39
+ * spends once onchain and both still point at the same order.
40
+ */
41
+ export const _tagPaymentReference = (reference, kind) => {
42
+ const { tag, order } = _splitReference(reference);
43
+ if (tag !== TAGS[PaymentReferenceKind.Standard]) {
44
+ throw new InvalidPaymentReferenceError(reference, "is already tagged");
45
+ }
46
+ return concat([TAGS[kind], order]);
47
+ };
48
+ /**
49
+ * Reads back how a purchase was paid for, and the untagged reference it was
50
+ * built from. Throws on a tag this SDK did not write.
51
+ */
52
+ export const _parsePaymentReference = (reference) => {
53
+ const { tag, order } = _splitReference(reference);
54
+ const kind = Object.keys(TAGS).find((k) => TAGS[k] === tag);
55
+ if (!kind)
56
+ throw new InvalidPaymentReferenceError(reference, "has an unknown tag");
57
+ return { kind, reference: pad(order, { size: 32 }) };
58
+ };
@@ -25,18 +25,17 @@ export var CHAIN_ID;
25
25
  CHAIN_ID[CHAIN_ID["BASE_MAINNET"] = 8453] = "BASE_MAINNET";
26
26
  CHAIN_ID[CHAIN_ID["BASE_SEPOLIA"] = 84532] = "BASE_SEPOLIA";
27
27
  })(CHAIN_ID || (CHAIN_ID = {}));
28
- // Not yet deployed. Kept as a placeholder so mainnet has a documented shape to
29
- // fill in once the protocol ships there, following Base Sepolia's soak.
28
+ // From smart-contract-suite `deployments/base-8453-entrypoint-v8.json`.
30
29
  export const baseMainnetFactoryAddresses = {
31
- DEVICE_WALLET_FACTORY: '0x',
32
- ESIM_WALLET_FACTORY: '0x',
33
- LAZY_WALLET_REGISTRY: '0x',
34
- REGISTRY: '0x',
35
- ENTRY_POINT: '0x',
36
- SENDER_CREATOR: '0x',
37
- P256VERIFIER: '0x',
38
- PROTOCOL_ADMIN: '0x',
39
- PAYMENT_ADAPTER: '0x'
30
+ DEVICE_WALLET_FACTORY: '0xAC6B6A9F905e2593d587D244cEee6a0886952a6D',
31
+ ESIM_WALLET_FACTORY: '0xC88723826B9070ea89d9040387DE4C22A490EdB6',
32
+ LAZY_WALLET_REGISTRY: '0x2046aE906FB940Ad99147f233711Bb6711A2Ab0C',
33
+ REGISTRY: '0x76fB7F8C97457A5a6dF19a7041a66a7f1b3c910c',
34
+ ENTRY_POINT: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108',
35
+ SENDER_CREATOR: '0x449ED7C3e6Fee6a97311d4b55475DF59C44AdD33',
36
+ P256VERIFIER: '0x4E2C5B9B88184464162d581D6BF36615f0040fE6',
37
+ PROTOCOL_ADMIN: '0x0E664e956aAD2599D17dE2381cc22687a066778B',
38
+ PAYMENT_ADAPTER: '0xbb80B10BECB997b02a4BA4068C145Ef18129943d'
40
39
  };
41
40
  export const baseSepoliaFactoryAddresses = {
42
41
  DEVICE_WALLET_FACTORY: '0x0BB3BA8D9233514a4aA6D72c243a2473f9cFf0bb',
@@ -73,9 +72,7 @@ export const _extractChainID = async (client) => {
73
72
  }
74
73
  return _chainId(client);
75
74
  };
76
- // Maps each supported chain id to its factory-address book + viem chain. Base
77
- // Mainnet is listed with '0x' placeholders so the guard below rejects it with a
78
- // clear message rather than leaking '0x' into viem calls.
75
+ // Maps each supported chain id to its factory-address book + viem chain.
79
76
  const CHAIN_CONFIG = {
80
77
  [CHAIN_ID.BASE_MAINNET]: { factoryAddresses: baseMainnetFactoryAddresses, chain: base },
81
78
  [CHAIN_ID.BASE_SEPOLIA]: { factoryAddresses: baseSepoliaFactoryAddresses, chain: baseSepolia },
@@ -134,12 +134,12 @@ export const _removeESIMWallet = async (client, address, eSIMWalletAddress, call
134
134
  * Rotate the P256 key that owns this wallet. The registry is told in the same
135
135
  * call, so its record of the owner cannot drift from the wallet's.
136
136
  *
137
- * A key that cannot produce a verifiable signature bricks the wallet: this is
138
- * reachable only through a signed userOp, so there is no rotating back and no
139
- * reaching the balance afterwards. The contract rejects a key it can tell is
140
- * unusable, but check the new key can sign before calling.
137
+ * `newOwnerSignature` is the new passkey's WebAuthn assertion over
138
+ * `rotationChallenge(newOwner)`, ABI-encoded as a `WebAuthnSignature` with no
139
+ * version or validUntil header. Without it the contract reverts with
140
+ * `NewOwnerKeyCannotSign`, so a wallet can only move to a key that can sign.
141
141
  */
142
- export const _transferOwnership = async (client, address, newOwner) => {
142
+ export const _transferOwnership = async (client, address, newOwner, newOwnerSignature) => {
143
143
  if (!client.account)
144
144
  throw new MissingSmartWalletError();
145
145
  // UserOp - `onlySelf`.
@@ -150,7 +150,7 @@ export const _transferOwnership = async (client, address, newOwner) => {
150
150
  data: encodeFunctionData({
151
151
  abi: DeviceWallet,
152
152
  functionName: "transferOwnership",
153
- args: [newOwner]
153
+ args: [newOwner, newOwnerSignature]
154
154
  })
155
155
  }]
156
156
  });
@@ -259,6 +259,19 @@ export const _isValidSignature = async (client, address, messageHash, signature)
259
259
  args: [messageHash, signature]
260
260
  });
261
261
  };
262
+ /**
263
+ * The bytes the new passkey must sign before `transferOwnership` accepts it.
264
+ * Bound to this wallet, this chain and `newOwner`, so it cannot be reused for
265
+ * another rotation.
266
+ */
267
+ export const _rotationChallenge = async (client, address, newOwner) => {
268
+ return client.readContract({
269
+ address,
270
+ abi: DeviceWallet,
271
+ functionName: "rotationChallenge",
272
+ args: [newOwner]
273
+ });
274
+ };
262
275
  /** The registry this wallet reports its ownership changes to. */
263
276
  export const _registry = async (client, address) => {
264
277
  return client.readContract({
@@ -37,6 +37,29 @@ export const _setPriceCapUSDCents = async (client, address, cap) => {
37
37
  }]
38
38
  });
39
39
  };
40
+ /**
41
+ * Set the total the admin key may charge this eSIM wallet, in USD cents. It
42
+ * replaces what is left rather than adding to it, and zero stops admin charges.
43
+ *
44
+ * `onlyDeviceWallet`, so only the user can raise what the admin may spend. A
45
+ * handover resets it to the registry default.
46
+ */
47
+ export const _setAdminAllowanceUSDCents = async (client, address, allowance) => {
48
+ if (!client.account)
49
+ throw new MissingSmartWalletError();
50
+ // UserOp - `onlyDeviceWallet`.
51
+ return client.sendUserOperation({
52
+ account: client.account,
53
+ calls: [{
54
+ to: address,
55
+ data: encodeFunctionData({
56
+ abi: ESIMWallet,
57
+ functionName: "setAdminAllowanceUSDCents",
58
+ args: [allowance]
59
+ })
60
+ }]
61
+ });
62
+ };
40
63
  /**
41
64
  * Buy a data bundle in `asset`, an ERC-20 the payment adapter accepts.
42
65
  *
@@ -135,6 +158,19 @@ export const _priceCapUSDCents = async (client, address) => {
135
158
  const registryCap = await _defaultPriceCapUSDCents(client);
136
159
  return registryCap === 0n ? maxUint256 : registryCap;
137
160
  };
161
+ /**
162
+ * What the admin key may still charge this eSIM wallet, in USD cents. Each admin
163
+ * purchase lowers it and nothing refills it, so the owner tops it up with
164
+ * `setAdminAllowanceUSDCents`. The owner's own purchases do not touch it.
165
+ */
166
+ export const _adminAllowanceUSDCents = async (client, address) => {
167
+ return client.readContract({
168
+ address,
169
+ abi: ESIMWallet,
170
+ functionName: "adminAllowanceUSDCents",
171
+ args: []
172
+ });
173
+ };
138
174
  /**
139
175
  * The device wallet this eSIM wallet belongs to. Tracks `owner`, but it is its
140
176
  * own storage slot with its own getter, so read whichever one you mean.
@@ -228,6 +228,44 @@ export class PriceOutOfRangeError extends KokioError {
228
228
  this.priceUSDCents = priceUSDCents;
229
229
  }
230
230
  }
231
+ /**
232
+ * An admin purchase priced above what the eSIM wallet's owner still allows the
233
+ * admin key to charge. Thrown before sending, since the contract would revert
234
+ * with `AdminAllowanceExceeded`. The owner raises it with `setAdminAllowanceUSDCents`.
235
+ */
236
+ export class AdminAllowanceExceededError extends KokioError {
237
+ priceUSDCents;
238
+ remainingUSDCents;
239
+ constructor(priceUSDCents, remainingUSDCents) {
240
+ super("ADMIN_ALLOWANCE_EXCEEDED", `${priceUSDCents} cents is more than the ${remainingUSDCents} cents the owner still allows the admin to charge.`);
241
+ this.priceUSDCents = priceUSDCents;
242
+ this.remainingUSDCents = remainingUSDCents;
243
+ }
244
+ }
245
+ /**
246
+ * A payment reference that is not 32 bytes, has an empty order part, or
247
+ * carries a tag this SDK cannot write over or read back.
248
+ */
249
+ export class InvalidPaymentReferenceError extends KokioError {
250
+ reference;
251
+ constructor(reference, reason) {
252
+ super("INVALID_PAYMENT_REFERENCE", `Payment reference "${reference}" ${reason}.`);
253
+ this.reference = reference;
254
+ }
255
+ }
256
+ /**
257
+ * A coupon that covers none or all of the price. Neither is a split: a coupon
258
+ * covering the whole price is a full-coupon order with a single reference.
259
+ */
260
+ export class CouponSplitOutOfRangeError extends KokioError {
261
+ couponUSDCents;
262
+ priceUSDCents;
263
+ constructor(couponUSDCents, priceUSDCents) {
264
+ super("COUPON_SPLIT_OUT_OF_RANGE", `A coupon of ${couponUSDCents} cents does not split a price of ${priceUSDCents} cents.`);
265
+ this.couponUSDCents = couponUSDCents;
266
+ this.priceUSDCents = priceUSDCents;
267
+ }
268
+ }
231
269
  // Every ABI that can surface a custom error from an on-chain revert. viem's
232
270
  // `decodeErrorResult` walks each ABI's `error` fragments to match the 4-byte
233
271
  // selector in the revert data.
@@ -218,6 +218,18 @@ export const _defaultPriceCapUSDCents = async (client) => {
218
218
  args: []
219
219
  });
220
220
  };
221
+ /** What the admin key may charge a newly created eSIM wallet in total, in USD cents. */
222
+ export const _defaultAdminAllowanceUSDCents = async (client) => {
223
+ const chainID = await _chainId(client);
224
+ const rpcURL = client.transport.url;
225
+ const values = _getChainSpecificConstants(chainID, rpcURL);
226
+ return client.readContract({
227
+ address: values.factoryAddresses.REGISTRY,
228
+ abi: Registry,
229
+ functionName: "defaultAdminAllowanceUSDCents",
230
+ args: []
231
+ });
232
+ };
221
233
  /** The payment adapter this registry currently points at. */
222
234
  export const _paymentAdapter = async (client) => {
223
235
  const chainID = await _chainId(client);
@@ -345,6 +345,20 @@ declare const DeviceWallet: readonly [{
345
345
  }];
346
346
  readonly outputs: readonly [];
347
347
  readonly stateMutability: "nonpayable";
348
+ }, {
349
+ readonly type: "function";
350
+ readonly name: "rotationChallenge";
351
+ readonly inputs: readonly [{
352
+ readonly name: "newOwner";
353
+ readonly type: "bytes32[2]";
354
+ readonly internalType: "bytes32[2]";
355
+ }];
356
+ readonly outputs: readonly [{
357
+ readonly name: "";
358
+ readonly type: "bytes";
359
+ readonly internalType: "bytes";
360
+ }];
361
+ readonly stateMutability: "view";
348
362
  }, {
349
363
  readonly type: "function";
350
364
  readonly name: "supportsInterface";
@@ -380,6 +394,10 @@ declare const DeviceWallet: readonly [{
380
394
  readonly name: "newOwner";
381
395
  readonly type: "bytes32[2]";
382
396
  readonly internalType: "bytes32[2]";
397
+ }, {
398
+ readonly name: "newOwnerSignature";
399
+ readonly type: "bytes";
400
+ readonly internalType: "bytes";
383
401
  }];
384
402
  readonly outputs: readonly [{
385
403
  readonly name: "";
@@ -643,6 +661,10 @@ declare const DeviceWallet: readonly [{
643
661
  readonly type: "error";
644
662
  readonly name: "InvalidInitialization";
645
663
  readonly inputs: readonly [];
664
+ }, {
665
+ readonly type: "error";
666
+ readonly name: "NewOwnerKeyCannotSign";
667
+ readonly inputs: readonly [];
646
668
  }, {
647
669
  readonly type: "error";
648
670
  readonly name: "NotInitializing";
@@ -7,6 +7,16 @@ declare const ESIMWallet: readonly [{
7
7
  readonly inputs: readonly [];
8
8
  readonly outputs: readonly [];
9
9
  readonly stateMutability: "nonpayable";
10
+ }, {
11
+ readonly type: "function";
12
+ readonly name: "adminAllowanceUSDCents";
13
+ readonly inputs: readonly [];
14
+ readonly outputs: readonly [{
15
+ readonly name: "";
16
+ readonly type: "uint64";
17
+ readonly internalType: "uint64";
18
+ }];
19
+ readonly stateMutability: "view";
10
20
  }, {
11
21
  readonly type: "function";
12
22
  readonly name: "buyDataBundleWithToken";
@@ -218,6 +228,16 @@ declare const ESIMWallet: readonly [{
218
228
  readonly internalType: "uint256";
219
229
  }];
220
230
  readonly stateMutability: "nonpayable";
231
+ }, {
232
+ readonly type: "function";
233
+ readonly name: "setAdminAllowanceUSDCents";
234
+ readonly inputs: readonly [{
235
+ readonly name: "_allowance";
236
+ readonly type: "uint64";
237
+ readonly internalType: "uint64";
238
+ }];
239
+ readonly outputs: readonly [];
240
+ readonly stateMutability: "nonpayable";
221
241
  }, {
222
242
  readonly type: "function";
223
243
  readonly name: "setESIMUniqueIdentifier";
@@ -270,6 +290,16 @@ declare const ESIMWallet: readonly [{
270
290
  }];
271
291
  readonly outputs: readonly [];
272
292
  readonly stateMutability: "pure";
293
+ }, {
294
+ readonly type: "event";
295
+ readonly name: "AdminAllowanceUSDCentsUpdated";
296
+ readonly inputs: readonly [{
297
+ readonly name: "_allowance";
298
+ readonly type: "uint64";
299
+ readonly indexed: false;
300
+ readonly internalType: "uint64";
301
+ }];
302
+ readonly anonymous: false;
273
303
  }, {
274
304
  readonly type: "event";
275
305
  readonly name: "DataBundleBoughtWithToken";
@@ -483,6 +513,18 @@ declare const ESIMWallet: readonly [{
483
513
  readonly internalType: "uint256";
484
514
  }];
485
515
  readonly anonymous: false;
516
+ }, {
517
+ readonly type: "error";
518
+ readonly name: "AdminAllowanceExceeded";
519
+ readonly inputs: readonly [{
520
+ readonly name: "priceUSDCents";
521
+ readonly type: "uint64";
522
+ readonly internalType: "uint64";
523
+ }, {
524
+ readonly name: "remaining";
525
+ readonly type: "uint64";
526
+ readonly internalType: "uint64";
527
+ }];
486
528
  }, {
487
529
  readonly type: "error";
488
530
  readonly name: "AssetNotTransferable";
@@ -116,6 +116,24 @@ declare const PaymentAdapter: readonly [{
116
116
  readonly internalType: "uint256";
117
117
  }];
118
118
  readonly stateMutability: "view";
119
+ }, {
120
+ readonly type: "function";
121
+ readonly name: "recoverTokens";
122
+ readonly inputs: readonly [{
123
+ readonly name: "_token";
124
+ readonly type: "address";
125
+ readonly internalType: "address";
126
+ }, {
127
+ readonly name: "_to";
128
+ readonly type: "address";
129
+ readonly internalType: "address";
130
+ }, {
131
+ readonly name: "_amount";
132
+ readonly type: "uint256";
133
+ readonly internalType: "uint256";
134
+ }];
135
+ readonly outputs: readonly [];
136
+ readonly stateMutability: "nonpayable";
119
137
  }, {
120
138
  readonly type: "function";
121
139
  readonly name: "registerAsset";
@@ -443,6 +461,26 @@ declare const PaymentAdapter: readonly [{
443
461
  readonly internalType: "uint256";
444
462
  }];
445
463
  readonly anonymous: false;
464
+ }, {
465
+ readonly type: "event";
466
+ readonly name: "TokensRecovered";
467
+ readonly inputs: readonly [{
468
+ readonly name: "_token";
469
+ readonly type: "address";
470
+ readonly indexed: true;
471
+ readonly internalType: "address";
472
+ }, {
473
+ readonly name: "_to";
474
+ readonly type: "address";
475
+ readonly indexed: true;
476
+ readonly internalType: "address";
477
+ }, {
478
+ readonly name: "_amount";
479
+ readonly type: "uint256";
480
+ readonly indexed: false;
481
+ readonly internalType: "uint256";
482
+ }];
483
+ readonly anonymous: false;
446
484
  }, {
447
485
  readonly type: "event";
448
486
  readonly name: "Upgraded";
@@ -653,6 +691,10 @@ declare const PaymentAdapter: readonly [{
653
691
  readonly type: "string";
654
692
  readonly internalType: "string";
655
693
  }];
694
+ }, {
695
+ readonly type: "error";
696
+ readonly name: "ZeroAmount";
697
+ readonly inputs: readonly [];
656
698
  }, {
657
699
  readonly type: "error";
658
700
  readonly name: "ZeroDataBundlePrice";