kokio-sdk 2.0.0 → 3.0.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 (81) hide show
  1. package/README.md +21 -7
  2. package/dist/esm/abis/DeviceWallet.js +60 -59
  3. package/dist/esm/abis/ESIMWallet.js +263 -58
  4. package/dist/esm/abis/ESIMWalletFactory.js +2 -2
  5. package/dist/esm/abis/LazyWalletRegistry.js +68 -24
  6. package/dist/esm/abis/PaymentAdapter.js +861 -0
  7. package/dist/esm/abis/Registry.js +294 -18
  8. package/dist/esm/abis/RegistryHelper.js +101 -9
  9. package/dist/esm/abis/index.js +2 -1
  10. package/dist/esm/admin/config-admin.js +4 -0
  11. package/dist/esm/admin/interface/deviceWalletClass.js +3 -3
  12. package/dist/esm/admin/interface/eSIMWalletClass.js +6 -6
  13. package/dist/esm/admin/interface/lazyWalletRegistryClass.js +4 -1
  14. package/dist/esm/admin/interface/paymentAdapterClass.js +54 -0
  15. package/dist/esm/admin/interface/protocolAdminClass.js +6 -3
  16. package/dist/esm/admin/interface/registryClass.js +15 -6
  17. package/dist/esm/config.js +3 -0
  18. package/dist/esm/interface/deviceWalletClass.js +5 -5
  19. package/dist/esm/interface/eSIMWalletClass.js +10 -7
  20. package/dist/esm/interface/paymentAdapterClass.js +28 -0
  21. package/dist/esm/interface/registryClass.js +12 -3
  22. package/dist/esm/logic/admin/deviceWallet.eoa.js +3 -3
  23. package/dist/esm/logic/admin/deviceWalletFactory.eoa.js +6 -6
  24. package/dist/esm/logic/admin/eSIMWallet.eoa.js +14 -12
  25. package/dist/esm/logic/admin/eSIMWalletFactory.eoa.js +4 -4
  26. package/dist/esm/logic/admin/lazyWalletRegistry.eoa.js +7 -7
  27. package/dist/esm/logic/admin/paymentAdapter.eoa.js +123 -0
  28. package/dist/esm/logic/admin/protocolAdmin.eoa.js +34 -16
  29. package/dist/esm/logic/admin/reads/deviceWallet.reads.js +2 -2
  30. package/dist/esm/logic/admin/reads/eSIMWallet.reads.js +8 -7
  31. package/dist/esm/logic/admin/reads/lazyWalletRegistry.reads.js +19 -2
  32. package/dist/esm/logic/admin/reads/paymentAdapter.reads.js +106 -0
  33. package/dist/esm/logic/admin/reads/registry.reads.js +34 -6
  34. package/dist/esm/logic/admin/registry.eoa.js +44 -20
  35. package/dist/esm/logic/constants.js +17 -87
  36. package/dist/esm/logic/deviceWallet.js +11 -11
  37. package/dist/esm/logic/eSIMWallet.js +51 -22
  38. package/dist/esm/logic/errors.js +30 -1
  39. package/dist/esm/logic/paymentAdapter.js +116 -0
  40. package/dist/esm/logic/registry.js +50 -5
  41. package/dist/esm/types-export.js +1 -1
  42. package/dist/esm/types.js +11 -1
  43. package/dist/types/abis/DeviceWallet.d.ts +50 -49
  44. package/dist/types/abis/ESIMWallet.d.ts +215 -55
  45. package/dist/types/abis/ESIMWalletFactory.d.ts +2 -2
  46. package/dist/types/abis/LazyWalletRegistry.d.ts +58 -24
  47. package/dist/types/abis/PaymentAdapter.d.ts +661 -0
  48. package/dist/types/abis/Registry.d.ts +233 -18
  49. package/dist/types/abis/RegistryHelper.d.ts +83 -9
  50. package/dist/types/abis/index.d.ts +2 -1
  51. package/dist/types/admin/config-admin.d.ts +2 -0
  52. package/dist/types/admin/interface/deviceWalletClass.d.ts +1 -1
  53. package/dist/types/admin/interface/eSIMWalletClass.d.ts +3 -3
  54. package/dist/types/admin/interface/lazyWalletRegistryClass.d.ts +1 -0
  55. package/dist/types/admin/interface/paymentAdapterClass.d.ts +20 -0
  56. package/dist/types/admin/interface/protocolAdminClass.d.ts +2 -1
  57. package/dist/types/admin/interface/registryClass.d.ts +6 -2
  58. package/dist/types/config.d.ts +2 -0
  59. package/dist/types/interface/constantsClass.d.ts +2 -2
  60. package/dist/types/interface/deviceWalletClass.d.ts +2 -2
  61. package/dist/types/interface/eSIMWalletClass.d.ts +5 -4
  62. package/dist/types/interface/paymentAdapterClass.d.ts +13 -0
  63. package/dist/types/interface/registryClass.d.ts +5 -2
  64. package/dist/types/logic/admin/eSIMWallet.eoa.d.ts +9 -6
  65. package/dist/types/logic/admin/paymentAdapter.eoa.d.ts +28 -0
  66. package/dist/types/logic/admin/protocolAdmin.eoa.d.ts +12 -2
  67. package/dist/types/logic/admin/reads/deviceWallet.reads.d.ts +1 -1
  68. package/dist/types/logic/admin/reads/eSIMWallet.reads.d.ts +5 -4
  69. package/dist/types/logic/admin/reads/lazyWalletRegistry.reads.d.ts +7 -0
  70. package/dist/types/logic/admin/reads/paymentAdapter.reads.d.ts +30 -0
  71. package/dist/types/logic/admin/reads/registry.reads.d.ts +13 -5
  72. package/dist/types/logic/admin/registry.eoa.d.ts +18 -8
  73. package/dist/types/logic/constants.d.ts +5 -17
  74. package/dist/types/logic/deviceWallet.d.ts +7 -7
  75. package/dist/types/logic/eSIMWallet.d.ts +29 -15
  76. package/dist/types/logic/errors.d.ts +16 -1
  77. package/dist/types/logic/paymentAdapter.d.ts +40 -0
  78. package/dist/types/logic/registry.d.ts +20 -5
  79. package/dist/types/types-export.d.ts +2 -1
  80. package/dist/types/types.d.ts +20 -2
  81. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { encodeFunctionData, publicActions } from "viem";
2
2
  import { _getChainSpecificConstants } from "../constants.js";
3
- import { MissingEOAWalletError } from "../errors.js";
3
+ import { MissingEOAWalletError, writeContractOrThrow } from "../errors.js";
4
4
  import { ProtocolAdmin, Registry } from "../../abis/index.js";
5
5
  // Admin-EOA logic for `ProtocolAdmin`, the timelock that owns `Registry`,
6
6
  // `LazyWalletRegistry`, `DeviceWalletFactory` and `ESIMWalletFactory`.
@@ -116,7 +116,7 @@ export const _schedule = async (client, call, opts = {}, delay) => {
116
116
  functionName: "hashOperation",
117
117
  args: [target, value, payload, predecessor, salt]
118
118
  });
119
- const hash = await client.writeContract({
119
+ const hash = await writeContractOrThrow(client, {
120
120
  address: values.factoryAddresses.PROTOCOL_ADMIN,
121
121
  chain: values.chain,
122
122
  account: client.account.address,
@@ -156,7 +156,7 @@ export const _scheduleBatch = async (client, calls, opts = {}, delay) => {
156
156
  functionName: "hashOperationBatch",
157
157
  args: [targets, callValues, payloads, predecessor, salt]
158
158
  });
159
- const hash = await client.writeContract({
159
+ const hash = await writeContractOrThrow(client, {
160
160
  address: values.factoryAddresses.PROTOCOL_ADMIN,
161
161
  chain: values.chain,
162
162
  account: client.account.address,
@@ -183,7 +183,7 @@ export const _scheduleRaw = async (client, target, value, payload, predecessor,
183
183
  functionName: "hashOperation",
184
184
  args: [target, value, payload, predecessor, salt]
185
185
  });
186
- const hash = await client.writeContract({
186
+ const hash = await writeContractOrThrow(client, {
187
187
  address: values.factoryAddresses.PROTOCOL_ADMIN,
188
188
  chain: values.chain,
189
189
  account: client.account.address,
@@ -208,7 +208,7 @@ export const _execute = async (client, operation) => {
208
208
  const values = await _resolve(client);
209
209
  if (!client.account)
210
210
  throw new MissingEOAWalletError();
211
- return client.writeContract({
211
+ return writeContractOrThrow(client, {
212
212
  address: values.factoryAddresses.PROTOCOL_ADMIN,
213
213
  chain: values.chain,
214
214
  account: client.account.address,
@@ -223,7 +223,7 @@ export const _executeBatch = async (client, operation) => {
223
223
  const values = await _resolve(client);
224
224
  if (!client.account)
225
225
  throw new MissingEOAWalletError();
226
- return client.writeContract({
226
+ return writeContractOrThrow(client, {
227
227
  address: values.factoryAddresses.PROTOCOL_ADMIN,
228
228
  chain: values.chain,
229
229
  account: client.account.address,
@@ -238,7 +238,7 @@ export const _executeRaw = async (client, target, value, payload, predecessor, s
238
238
  const values = await _resolve(client);
239
239
  if (!client.account)
240
240
  throw new MissingEOAWalletError();
241
- return client.writeContract({
241
+ return writeContractOrThrow(client, {
242
242
  address: values.factoryAddresses.PROTOCOL_ADMIN,
243
243
  chain: values.chain,
244
244
  account: client.account.address,
@@ -259,7 +259,7 @@ export const _cancel = async (client, id) => {
259
259
  const values = await _resolve(client);
260
260
  if (!client.account)
261
261
  throw new MissingEOAWalletError();
262
- return client.writeContract({
262
+ return writeContractOrThrow(client, {
263
263
  address: values.factoryAddresses.PROTOCOL_ADMIN,
264
264
  chain: values.chain,
265
265
  account: client.account.address,
@@ -281,7 +281,7 @@ export const _unpauseInstantly = async (client, target) => {
281
281
  const values = await _resolve(client);
282
282
  if (!client.account)
283
283
  throw new MissingEOAWalletError();
284
- return client.writeContract({
284
+ return writeContractOrThrow(client, {
285
285
  address: values.factoryAddresses.PROTOCOL_ADMIN,
286
286
  chain: values.chain,
287
287
  account: client.account.address,
@@ -301,7 +301,7 @@ export const _revokeCancellersInstantly = async (client, accounts) => {
301
301
  const values = await _resolve(client);
302
302
  if (!client.account)
303
303
  throw new MissingEOAWalletError();
304
- return client.writeContract({
304
+ return writeContractOrThrow(client, {
305
305
  address: values.factoryAddresses.PROTOCOL_ADMIN,
306
306
  chain: values.chain,
307
307
  account: client.account.address,
@@ -324,7 +324,7 @@ export const _disableAdminInstantly = async (client, target) => {
324
324
  const values = await _resolve(client);
325
325
  if (!client.account)
326
326
  throw new MissingEOAWalletError();
327
- return client.writeContract({
327
+ return writeContractOrThrow(client, {
328
328
  address: values.factoryAddresses.PROTOCOL_ADMIN,
329
329
  chain: values.chain,
330
330
  account: client.account.address,
@@ -345,7 +345,7 @@ export const _acceptOwnershipBatch = async (client, targets) => {
345
345
  const values = await _resolve(client);
346
346
  if (!client.account)
347
347
  throw new MissingEOAWalletError();
348
- return client.writeContract({
348
+ return writeContractOrThrow(client, {
349
349
  address: values.factoryAddresses.PROTOCOL_ADMIN,
350
350
  chain: values.chain,
351
351
  account: client.account.address,
@@ -362,7 +362,7 @@ export const _renounceRole = async (client, role, account) => {
362
362
  const values = await _resolve(client);
363
363
  if (!client.account)
364
364
  throw new MissingEOAWalletError();
365
- return client.writeContract({
365
+ return writeContractOrThrow(client, {
366
366
  address: values.factoryAddresses.PROTOCOL_ADMIN,
367
367
  chain: values.chain,
368
368
  account: client.account.address,
@@ -456,22 +456,40 @@ export const _unpauseCall = async (client, target) => {
456
456
  };
457
457
  };
458
458
  /**
459
- * Set the fallback data bundle price ceiling. Pass the result to `schedule`.
459
+ * Set the fallback price ceiling, in USD cents. Pass the result to `schedule`.
460
460
  *
461
461
  * Zero reverts on execution, not on scheduling, so a zero here costs the whole
462
462
  * delay before it fails.
463
463
  *
464
464
  * `target` defaults to the registry.
465
465
  */
466
- export const _setDefaultDataBundlePriceCapCall = async (client, cap, target) => {
466
+ export const _setDefaultPriceCapUSDCentsCall = async (client, cap, target) => {
467
467
  const values = await _resolve(client);
468
468
  return {
469
469
  address: target ?? values.factoryAddresses.REGISTRY,
470
470
  abi: Registry,
471
- functionName: 'setDefaultDataBundlePriceCap',
471
+ functionName: 'setDefaultPriceCapUSDCents',
472
472
  args: [cap],
473
473
  };
474
474
  };
475
+ /**
476
+ * Point the registry at a new payment adapter. Pass the result to `schedule`.
477
+ *
478
+ * Owner only, deliberately not the admin: the adapter holds the spent payment
479
+ * references, so an admin that could swap it would get an empty set back and
480
+ * could record every purchase a second time.
481
+ *
482
+ * `target` defaults to the registry.
483
+ */
484
+ export const _setPaymentAdapterCall = async (client, paymentAdapter, target) => {
485
+ const values = await _resolve(client);
486
+ return {
487
+ address: target ?? values.factoryAddresses.REGISTRY,
488
+ abi: Registry,
489
+ functionName: 'setPaymentAdapter',
490
+ args: [paymentAdapter],
491
+ };
492
+ };
475
493
  // ---------------------------------------------------------------------------
476
494
  // Admin suspension payloads - the owner's route, aimed at the target contract
477
495
  // ---------------------------------------------------------------------------
@@ -24,11 +24,11 @@ export const _isValidESIMWallet = async (client, deviceWalletAddress, eSIMWallet
24
24
  });
25
25
  };
26
26
  /** Whether an eSIM wallet is allowed to pull ETH from this device wallet. */
27
- export const _canPullETH = async (client, deviceWalletAddress, eSIMWallet) => {
27
+ export const _canPullFunds = async (client, deviceWalletAddress, eSIMWallet) => {
28
28
  return client.extend(publicActions).readContract({
29
29
  address: deviceWalletAddress,
30
30
  abi: DeviceWallet,
31
- functionName: "canPullETH",
31
+ functionName: "canPullFunds",
32
32
  args: [eSIMWallet]
33
33
  });
34
34
  };
@@ -26,13 +26,13 @@ export const _eSIMUniqueIdentifier = async (client, eSIMWalletAddress) => {
26
26
  * This wallet's own price ceiling in wei. Zero means it follows the registry's
27
27
  * `defaultDataBundlePriceCap` instead, which is the state a fresh wallet and a
28
28
  * newly handed-over wallet both start in. Worth reading before naming a price on
29
- * `buyDataBundle`, since a price over the ceiling reverts.
29
+ * `buyDataBundleWithToken`, since a price over the ceiling reverts.
30
30
  */
31
- export const _dataBundlePriceCap = async (client, eSIMWalletAddress) => {
31
+ export const _priceCapUSDCents = async (client, eSIMWalletAddress) => {
32
32
  return client.extend(publicActions).readContract({
33
33
  address: eSIMWalletAddress,
34
34
  abi: ESIMWallet,
35
- functionName: "dataBundlePriceCap",
35
+ functionName: "priceCapUSDCents",
36
36
  args: []
37
37
  });
38
38
  };
@@ -74,15 +74,16 @@ export const _deviceWallet = async (client, eSIMWalletAddress) => {
74
74
  *
75
75
  * The contract publishes no length getter, so there is no way to ask how many
76
76
  * entries exist. Read upwards from zero until a call reverts, or track the count
77
- * from the `DataBundleBought` and `TransactionHistoryPopulated` events, whose
78
- * `_totalEntries` is the length after the batch landed.
77
+ * from the `DataBundleBoughtWithToken`, `DataBundleSettlementRecorded` and
78
+ * `TransactionHistoryPopulated` events, whose `_totalEntries` is the length
79
+ * after the batch landed.
79
80
  */
80
81
  export const _transactionHistory = async (client, eSIMWalletAddress, index) => {
81
- const [dataBundleID, dataBundlePrice] = await client.extend(publicActions).readContract({
82
+ const [id, priceUSDCents, settlement] = await client.extend(publicActions).readContract({
82
83
  address: eSIMWalletAddress,
83
84
  abi: ESIMWallet,
84
85
  functionName: "transactionHistory",
85
86
  args: [index]
86
87
  });
87
- return { dataBundleID, dataBundlePrice };
88
+ return { id, priceUSDCents, settlement };
88
89
  };
@@ -38,13 +38,13 @@ export const _deviceIdentifierToESIMDetails = async (client, deviceIdentifier, e
38
38
  const chainID = await client.getChainId();
39
39
  const rpcURL = client.transport.url;
40
40
  const values = _getChainSpecificConstants(chainID, rpcURL);
41
- const [dataBundleID, dataBundlePrice] = await client.extend(publicActions).readContract({
41
+ const [id, priceUSDCents, settlement] = await client.extend(publicActions).readContract({
42
42
  address: values.factoryAddresses.LAZY_WALLET_REGISTRY,
43
43
  abi: LazyWalletRegistry,
44
44
  functionName: "deviceIdentifierToESIMDetails",
45
45
  args: [deviceIdentifier, eSIMIdentifier, index]
46
46
  });
47
- return { dataBundleID, dataBundlePrice };
47
+ return { id, priceUSDCents, settlement };
48
48
  };
49
49
  /** Most eSIM wallets one `deployLazyWalletAndSetESIMIdentifier` call will deploy. */
50
50
  export const _maxESIMWalletsPerCall = async (client) => {
@@ -125,6 +125,23 @@ export const _historyEntriesCopied = async (client, eSIMIdentifier) => {
125
125
  args: [eSIMIdentifier]
126
126
  });
127
127
  };
128
+ /**
129
+ * History entries still waiting to be copied in for this eSIM. A wallet's own
130
+ * purchase paths (`buyDataBundleWithToken`, `recordSettledPurchase`) refuse a
131
+ * new entry while this is non-zero, since it would land ahead of history that
132
+ * has not arrived yet.
133
+ */
134
+ export const _outstandingHistoryEntries = async (client, eSIMIdentifier) => {
135
+ const chainID = await client.getChainId();
136
+ const rpcURL = client.transport.url;
137
+ const values = _getChainSpecificConstants(chainID, rpcURL);
138
+ return client.extend(publicActions).readContract({
139
+ address: values.factoryAddresses.LAZY_WALLET_REGISTRY,
140
+ abi: LazyWalletRegistry,
141
+ functionName: "outstandingHistoryEntries",
142
+ args: [eSIMIdentifier]
143
+ });
144
+ };
128
145
  /** Whether a device identifier has purchases recorded against it here. */
129
146
  export const _isDeviceIdentifierReserved = async (client, deviceIdentifier) => {
130
147
  const chainID = await client.getChainId();
@@ -0,0 +1,106 @@
1
+ import { publicActions } from "viem";
2
+ import { _getChainSpecificConstants } from "../../constants.js";
3
+ import { PaymentAdapter } from "../../../abis/index.js";
4
+ // Read-only admin logic for `PaymentAdapter`. Every write on it
5
+ // (registerAsset/updateAsset) is `onlyOwner`, i.e. only reachable through the
6
+ // timelock, so those live as payload builders on `KokioAdmin.protocolAdmin`
7
+ // rather than here.
8
+ /** The registry this adapter reads `vault()` and `isESIMWalletValid()` from. */
9
+ export const _registry = async (client) => {
10
+ const chainID = await client.getChainId();
11
+ const rpcURL = client.transport.url;
12
+ const values = _getChainSpecificConstants(chainID, rpcURL);
13
+ return client.extend(publicActions).readContract({
14
+ address: values.factoryAddresses.PAYMENT_ADAPTER,
15
+ abi: PaymentAdapter,
16
+ functionName: "registry",
17
+ args: []
18
+ });
19
+ };
20
+ /** The ERC-20 registered under the `USDC` symbol at configure time. */
21
+ export const _settlementToken = async (client) => {
22
+ const chainID = await client.getChainId();
23
+ const rpcURL = client.transport.url;
24
+ const values = _getChainSpecificConstants(chainID, rpcURL);
25
+ return client.extend(publicActions).readContract({
26
+ address: values.factoryAddresses.PAYMENT_ADAPTER,
27
+ abi: PaymentAdapter,
28
+ functionName: "settlementToken",
29
+ args: []
30
+ });
31
+ };
32
+ /**
33
+ * The raw currency table entry for a symbol. `decimals` reads zero for a symbol
34
+ * never registered, which is how `resolveAsset` tells "not registered" apart
35
+ * from "registered but withdrawn" (`allowed: false`).
36
+ */
37
+ export const _assets = async (client, symbol) => {
38
+ const chainID = await client.getChainId();
39
+ const rpcURL = client.transport.url;
40
+ const values = _getChainSpecificConstants(chainID, rpcURL);
41
+ const [allowed, isDollarUnit, decimals, token] = await client.extend(publicActions).readContract({
42
+ address: values.factoryAddresses.PAYMENT_ADAPTER,
43
+ abi: PaymentAdapter,
44
+ functionName: "assets",
45
+ args: [symbol]
46
+ });
47
+ return { allowed, isDollarUnit, decimals, token };
48
+ };
49
+ /** A currency's full entry, reverting if the symbol was never registered. */
50
+ export const _resolveAsset = async (client, symbol) => {
51
+ const chainID = await client.getChainId();
52
+ const rpcURL = client.transport.url;
53
+ const values = _getChainSpecificConstants(chainID, rpcURL);
54
+ return client.extend(publicActions).readContract({
55
+ address: values.factoryAddresses.PAYMENT_ADAPTER,
56
+ abi: PaymentAdapter,
57
+ functionName: "resolveAsset",
58
+ args: [symbol]
59
+ });
60
+ };
61
+ /**
62
+ * The amount of `symbol`, in its smallest unit, that a `priceUSDCents` charge
63
+ * currently costs. Worth reading before `recordSettledPurchase`, to size
64
+ * `_tokenAmount` for the backend's own settlement record.
65
+ */
66
+ export const _quote = async (client, symbol, priceUSDCents) => {
67
+ const chainID = await client.getChainId();
68
+ const rpcURL = client.transport.url;
69
+ const values = _getChainSpecificConstants(chainID, rpcURL);
70
+ return client.extend(publicActions).readContract({
71
+ address: values.factoryAddresses.PAYMENT_ADAPTER,
72
+ abi: PaymentAdapter,
73
+ functionName: "quote",
74
+ args: [symbol, priceUSDCents]
75
+ });
76
+ };
77
+ /**
78
+ * Whether a payment reference has already been spent here. Retired from the
79
+ * registry's live purchase paths (`PaymentAdapter.consumePaymentReference` is
80
+ * `onlyRegistry` but nothing calls it any more): replay protection now lives on
81
+ * `Registry.usedPaymentReferences`, scoped per eSIM wallet. Kept for whatever
82
+ * still reads the adapter's own record from before that move.
83
+ */
84
+ export const _usedReferences = async (client, paymentReference) => {
85
+ const chainID = await client.getChainId();
86
+ const rpcURL = client.transport.url;
87
+ const values = _getChainSpecificConstants(chainID, rpcURL);
88
+ return client.extend(publicActions).readContract({
89
+ address: values.factoryAddresses.PAYMENT_ADAPTER,
90
+ abi: PaymentAdapter,
91
+ functionName: "usedReferences",
92
+ args: [paymentReference]
93
+ });
94
+ };
95
+ /** The address holding upgrade authority over this adapter (its owner). */
96
+ export const _upgradeManager = async (client) => {
97
+ const chainID = await client.getChainId();
98
+ const rpcURL = client.transport.url;
99
+ const values = _getChainSpecificConstants(chainID, rpcURL);
100
+ return client.extend(publicActions).readContract({
101
+ address: values.factoryAddresses.PAYMENT_ADAPTER,
102
+ abi: PaymentAdapter,
103
+ functionName: "upgradeManager",
104
+ args: []
105
+ });
106
+ };
@@ -54,8 +54,8 @@ export const _adminDisabled = async (client) => {
54
54
  });
55
55
  };
56
56
  /**
57
- * Whether the protocol is paused. While true, every ETH-moving path on the
58
- * device wallets and eSIM wallets reverts `ProtocolPaused`.
57
+ * Whether the protocol is paused. While true, the purchase and token-pull
58
+ * paths on the device wallets and eSIM wallets revert `ProtocolPaused`.
59
59
  */
60
60
  export const _paused = async (client) => {
61
61
  const chainID = await client.getChainId();
@@ -69,17 +69,17 @@ export const _paused = async (client) => {
69
69
  });
70
70
  };
71
71
  /**
72
- * The fallback price ceiling in wei, applied to any eSIM wallet holding no cap of
73
- * its own. Never zero.
72
+ * The fallback price ceiling in USD cents, applied to any eSIM wallet holding no
73
+ * cap of its own. Never zero.
74
74
  */
75
- export const _defaultDataBundlePriceCap = async (client) => {
75
+ export const _defaultPriceCapUSDCents = async (client) => {
76
76
  const chainID = await client.getChainId();
77
77
  const rpcURL = client.transport.url;
78
78
  const values = _getChainSpecificConstants(chainID, rpcURL);
79
79
  return client.extend(publicActions).readContract({
80
80
  address: values.factoryAddresses.REGISTRY,
81
81
  abi: Registry,
82
- functionName: "defaultDataBundlePriceCap",
82
+ functionName: "defaultPriceCapUSDCents",
83
83
  args: []
84
84
  });
85
85
  };
@@ -414,3 +414,31 @@ export const _upgradeInterfaceVersion = async (client) => {
414
414
  args: []
415
415
  });
416
416
  };
417
+ /** The payment adapter this registry currently points at. */
418
+ export const _paymentAdapter = async (client) => {
419
+ const chainID = await client.getChainId();
420
+ const rpcURL = client.transport.url;
421
+ const values = _getChainSpecificConstants(chainID, rpcURL);
422
+ return client.extend(publicActions).readContract({
423
+ address: values.factoryAddresses.REGISTRY,
424
+ abi: Registry,
425
+ functionName: "paymentAdapter",
426
+ args: []
427
+ });
428
+ };
429
+ /**
430
+ * Whether a payment reference has already been spent for an eSIM wallet.
431
+ * Scoped per wallet: pass the same `keccak256(abi.encode(eSIMWallet, paymentReference))`
432
+ * the contract keys `usedPaymentReferences` by, not the bare reference.
433
+ */
434
+ export const _usedPaymentReferences = async (client, scopedReference) => {
435
+ const chainID = await client.getChainId();
436
+ const rpcURL = client.transport.url;
437
+ const values = _getChainSpecificConstants(chainID, rpcURL);
438
+ return client.extend(publicActions).readContract({
439
+ address: values.factoryAddresses.REGISTRY,
440
+ abi: Registry,
441
+ functionName: "usedPaymentReferences",
442
+ args: [scopedReference]
443
+ });
444
+ };
@@ -1,5 +1,5 @@
1
1
  import { _getChainSpecificConstants } from "../constants.js";
2
- import { MissingEOAWalletError } from "../errors.js";
2
+ import { MissingEOAWalletError, writeContractOrThrow } from "../errors.js";
3
3
  import { Registry } from "../../abis/index.js";
4
4
  // Admin-EOA logic for `Registry`. Most of this is `onlyOwner`, so the `client`
5
5
  // must carry the owner EOA. `_acceptAdminUpdate` is the nominee's own call and
@@ -16,7 +16,7 @@ export const _addOrUpdateLazyWalletRegistryAddress = async (client, lazyWalletRe
16
16
  const values = _getChainSpecificConstants(chainID, rpcURL);
17
17
  if (!client.account)
18
18
  throw new MissingEOAWalletError();
19
- return client.writeContract({
19
+ return writeContractOrThrow(client, {
20
20
  address: values.factoryAddresses.REGISTRY,
21
21
  chain: values.chain,
22
22
  account: client.account.address,
@@ -32,7 +32,7 @@ export const _updateVaultAddress = async (client, newVaultAddress) => {
32
32
  const values = _getChainSpecificConstants(chainID, rpcURL);
33
33
  if (!client.account)
34
34
  throw new MissingEOAWalletError();
35
- return client.writeContract({
35
+ return writeContractOrThrow(client, {
36
36
  address: values.factoryAddresses.REGISTRY,
37
37
  chain: values.chain,
38
38
  account: client.account.address,
@@ -56,7 +56,7 @@ export const _requestAdminUpdate = async (client, newAdmin) => {
56
56
  const values = _getChainSpecificConstants(chainID, rpcURL);
57
57
  if (!client.account)
58
58
  throw new MissingEOAWalletError();
59
- return client.writeContract({
59
+ return writeContractOrThrow(client, {
60
60
  address: values.factoryAddresses.REGISTRY,
61
61
  chain: values.chain,
62
62
  account: client.account.address,
@@ -78,7 +78,7 @@ export const _disableAdmin = async (client) => {
78
78
  const values = _getChainSpecificConstants(chainID, rpcURL);
79
79
  if (!client.account)
80
80
  throw new MissingEOAWalletError();
81
- return client.writeContract({
81
+ return writeContractOrThrow(client, {
82
82
  address: values.factoryAddresses.REGISTRY,
83
83
  chain: values.chain,
84
84
  account: client.account.address,
@@ -100,7 +100,7 @@ export const _enableAdmin = async (client) => {
100
100
  const values = _getChainSpecificConstants(chainID, rpcURL);
101
101
  if (!client.account)
102
102
  throw new MissingEOAWalletError();
103
- return client.writeContract({
103
+ return writeContractOrThrow(client, {
104
104
  address: values.factoryAddresses.REGISTRY,
105
105
  chain: values.chain,
106
106
  account: client.account.address,
@@ -110,13 +110,13 @@ export const _enableAdmin = async (client) => {
110
110
  });
111
111
  };
112
112
  /**
113
- * Stop the ETH-moving paths on every device wallet and eSIM wallet.
114
- * `onlyESIMWalletAdmin`, so this is the one emergency lever the backend key can
115
- * pull on its own.
113
+ * Stop the purchase and token-pull paths on every device wallet and eSIM
114
+ * wallet. `onlyESIMWalletAdmin`, so this is the one emergency lever the backend
115
+ * key can pull on its own.
116
116
  *
117
117
  * It cannot release it again: `_unpause` is `onlyOwner`. That split is what stops
118
- * a compromised backend key holding user funds. Owners can still spend their own
119
- * ETH through their device wallet's `execute`, which a pause never reaches.
118
+ * a compromised backend key holding user funds. Owners can still move their own
119
+ * funds through their device wallet's `execute`, which a pause never reaches.
120
120
  */
121
121
  export const _pause = async (client) => {
122
122
  const chainID = await client.getChainId();
@@ -124,7 +124,7 @@ export const _pause = async (client) => {
124
124
  const values = _getChainSpecificConstants(chainID, rpcURL);
125
125
  if (!client.account)
126
126
  throw new MissingEOAWalletError();
127
- return client.writeContract({
127
+ return writeContractOrThrow(client, {
128
128
  address: values.factoryAddresses.REGISTRY,
129
129
  chain: values.chain,
130
130
  account: client.account.address,
@@ -146,7 +146,7 @@ export const _unpause = async (client) => {
146
146
  const values = _getChainSpecificConstants(chainID, rpcURL);
147
147
  if (!client.account)
148
148
  throw new MissingEOAWalletError();
149
- return client.writeContract({
149
+ return writeContractOrThrow(client, {
150
150
  address: values.factoryAddresses.REGISTRY,
151
151
  chain: values.chain,
152
152
  account: client.account.address,
@@ -158,23 +158,23 @@ export const _unpause = async (client) => {
158
158
  /**
159
159
  * Set the price ceiling every eSIM wallet falls back to when it holds none of its
160
160
  * own. `onlyOwner`, deliberately not the admin: the admin names the price on
161
- * `buyDataBundle`, so it must not also be able to raise its own limit.
161
+ * `buyDataBundleWithToken`, so it must not also be able to raise its own limit.
162
162
  *
163
163
  * Zero reverts `ZeroDataBundlePriceCap`, since a zero would read as "no ceiling"
164
164
  * for every wallet without one of its own.
165
165
  */
166
- export const _setDefaultDataBundlePriceCap = async (client, cap) => {
166
+ export const _setDefaultPriceCapUSDCents = async (client, cap) => {
167
167
  const chainID = await client.getChainId();
168
168
  const rpcURL = client.transport.url;
169
169
  const values = _getChainSpecificConstants(chainID, rpcURL);
170
170
  if (!client.account)
171
171
  throw new MissingEOAWalletError();
172
- return client.writeContract({
172
+ return writeContractOrThrow(client, {
173
173
  address: values.factoryAddresses.REGISTRY,
174
174
  chain: values.chain,
175
175
  account: client.account.address,
176
176
  abi: Registry,
177
- functionName: 'setDefaultDataBundlePriceCap',
177
+ functionName: 'setDefaultPriceCapUSDCents',
178
178
  args: [cap]
179
179
  });
180
180
  };
@@ -190,7 +190,7 @@ export const _assignESIMIdentifier = async (client, eSIMWalletAddress, eSIMUniqu
190
190
  const values = _getChainSpecificConstants(chainID, rpcURL);
191
191
  if (!client.account)
192
192
  throw new MissingEOAWalletError();
193
- return client.writeContract({
193
+ return writeContractOrThrow(client, {
194
194
  address: values.factoryAddresses.REGISTRY,
195
195
  chain: values.chain,
196
196
  account: client.account.address,
@@ -199,6 +199,30 @@ export const _assignESIMIdentifier = async (client, eSIMWalletAddress, eSIMUniqu
199
199
  args: [eSIMWalletAddress, eSIMUniqueIdentifier]
200
200
  });
201
201
  };
202
+ /**
203
+ * Record a data bundle paid for outside the protocol - a card or an external
204
+ * wallet, never the device wallet. `onlyESIMWalletAdmin`. No money moves here:
205
+ * `_dataBundleDetail.settlement` must be `ExternalWallet` or `Fiat` (the
206
+ * contract reverts `SettlementNotAsserted` on `DeviceWallet`, since this call
207
+ * never sees a transfer to prove it), and `_tokenAmount` is recorded for
208
+ * offchain matching but never checked against `_dataBundleDetail.priceUSDCents`.
209
+ * `_paymentReference` is spendable once per eSIM wallet.
210
+ */
211
+ export const _recordSettledPurchase = async (client, eSIMWalletAddress, dataBundleDetail, asset, tokenAmount, paymentReference) => {
212
+ const chainID = await client.getChainId();
213
+ const rpcURL = client.transport.url;
214
+ const values = _getChainSpecificConstants(chainID, rpcURL);
215
+ if (!client.account)
216
+ throw new MissingEOAWalletError();
217
+ return writeContractOrThrow(client, {
218
+ address: values.factoryAddresses.REGISTRY,
219
+ chain: values.chain,
220
+ account: client.account.address,
221
+ abi: Registry,
222
+ functionName: 'recordSettledPurchase',
223
+ args: [eSIMWalletAddress, dataBundleDetail, asset, tokenAmount, paymentReference]
224
+ });
225
+ };
202
226
  /**
203
227
  * Step 2 of the 2-step admin handover: the nominee accepts. The chain requires
204
228
  * `msg.sender` to equal the pending admin, so the `client` here must be the
@@ -210,7 +234,7 @@ export const _acceptAdminUpdate = async (client) => {
210
234
  const values = _getChainSpecificConstants(chainID, rpcURL);
211
235
  if (!client.account)
212
236
  throw new MissingEOAWalletError();
213
- return client.writeContract({
237
+ return writeContractOrThrow(client, {
214
238
  address: values.factoryAddresses.REGISTRY,
215
239
  chain: values.chain,
216
240
  account: client.account.address,
@@ -234,7 +258,7 @@ export const _acceptOwnership = async (client) => {
234
258
  const values = _getChainSpecificConstants(chainID, rpcURL);
235
259
  if (!client.account)
236
260
  throw new MissingEOAWalletError();
237
- return client.writeContract({
261
+ return writeContractOrThrow(client, {
238
262
  address: values.factoryAddresses.REGISTRY,
239
263
  chain: values.chain,
240
264
  account: client.account.address,