kokio-sdk 3.0.2 → 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.
- package/README.md +15 -15
- package/dist/esm/admin/config-admin.js +6 -1
- package/dist/esm/admin/interface/callsClass.js +30 -0
- package/dist/esm/config.js +1 -1
- package/dist/esm/interface/deviceWalletClass.js +10 -1
- package/dist/esm/interface/eSIMWalletClass.js +17 -1
- package/dist/esm/interface/eSIMWalletFactoryClass.js +9 -1
- package/dist/esm/interface/registryClass.js +8 -0
- package/dist/esm/interface/smartAccountClass.js +9 -2
- package/dist/esm/logic/P256Verifier.js +2 -2
- package/dist/esm/logic/account-kit/createSmartAccount.js +47 -36
- package/dist/esm/logic/admin/deviceWallet.eoa.js +5 -5
- package/dist/esm/logic/admin/deviceWalletFactory.eoa.js +13 -13
- package/dist/esm/logic/admin/eSIMWallet.eoa.js +3 -3
- package/dist/esm/logic/admin/eSIMWalletFactory.eoa.js +9 -9
- package/dist/esm/logic/admin/lazyWalletRegistry.eoa.js +13 -13
- package/dist/esm/logic/admin/paymentAdapter.eoa.js +11 -11
- package/dist/esm/logic/admin/protocolAdmin.eoa.js +14 -14
- package/dist/esm/logic/admin/reads/deviceWalletFactory.reads.js +9 -9
- package/dist/esm/logic/admin/reads/eSIMWalletFactory.reads.js +7 -7
- package/dist/esm/logic/admin/reads/lazyWalletRegistry.reads.js +18 -18
- package/dist/esm/logic/admin/reads/paymentAdapter.reads.js +8 -8
- package/dist/esm/logic/admin/reads/protocolAdmin.reads.js +2 -2
- package/dist/esm/logic/admin/reads/registry.reads.js +31 -31
- package/dist/esm/logic/admin/registry.eoa.js +27 -27
- package/dist/esm/logic/calls/eSIMWallet.calls.js +72 -0
- package/dist/esm/logic/constants.js +14 -1
- package/dist/esm/logic/deviceWallet.js +35 -1
- package/dist/esm/logic/deviceWalletFactory.js +11 -11
- package/dist/esm/logic/eSIMWallet.js +32 -0
- package/dist/esm/logic/eSIMWalletFactory.js +15 -3
- package/dist/esm/logic/errors.js +15 -9
- package/dist/esm/logic/paymentAdapter.js +8 -8
- package/dist/esm/logic/registry.js +17 -17
- package/dist/esm/logic/utils.js +1 -81
- package/dist/types/admin/config-admin.d.ts +4 -1
- package/dist/types/admin/interface/callsClass.d.ts +26 -0
- package/dist/types/config.d.ts +1 -1
- package/dist/types/interface/deviceWalletClass.d.ts +12 -0
- package/dist/types/interface/eSIMWalletClass.d.ts +14 -0
- package/dist/types/interface/eSIMWalletFactoryClass.d.ts +6 -0
- package/dist/types/interface/registryClass.d.ts +8 -0
- package/dist/types/interface/smartAccountClass.d.ts +10 -1
- package/dist/types/logic/account-kit/createSmartAccount.d.ts +1 -1
- package/dist/types/logic/calls/eSIMWallet.calls.d.ts +25 -0
- package/dist/types/logic/constants.d.ts +3 -0
- package/dist/types/logic/deviceWallet.d.ts +12 -0
- package/dist/types/logic/eSIMWallet.d.ts +16 -0
- package/dist/types/logic/eSIMWalletFactory.d.ts +2 -0
- package/dist/types/logic/errors.d.ts +6 -5
- package/dist/types/logic/utils.d.ts +0 -21
- package/dist/types/types.d.ts +1 -1
- package/package.json +21 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _getChainSpecificConstants } from "../constants.js";
|
|
1
|
+
import { _chainId, _getChainSpecificConstants } from "../constants.js";
|
|
2
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`
|
|
@@ -11,7 +11,7 @@ import { Registry } from "../../abis/index.js";
|
|
|
11
11
|
// deployments whose owner is a plain EOA or multisig.
|
|
12
12
|
/** Wire (or rewire) the LazyWalletRegistry into the Registry. `onlyOwner`. */
|
|
13
13
|
export const _addOrUpdateLazyWalletRegistryAddress = async (client, lazyWalletRegistry) => {
|
|
14
|
-
const chainID = await client
|
|
14
|
+
const chainID = await _chainId(client);
|
|
15
15
|
const rpcURL = client.transport.url;
|
|
16
16
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
17
17
|
if (!client.account)
|
|
@@ -19,7 +19,7 @@ export const _addOrUpdateLazyWalletRegistryAddress = async (client, lazyWalletRe
|
|
|
19
19
|
return writeContractOrThrow(client, {
|
|
20
20
|
address: values.factoryAddresses.REGISTRY,
|
|
21
21
|
chain: values.chain,
|
|
22
|
-
account: client.account
|
|
22
|
+
account: client.account,
|
|
23
23
|
abi: Registry,
|
|
24
24
|
functionName: 'addOrUpdateLazyWalletRegistryAddress',
|
|
25
25
|
args: [lazyWalletRegistry]
|
|
@@ -27,7 +27,7 @@ export const _addOrUpdateLazyWalletRegistryAddress = async (client, lazyWalletRe
|
|
|
27
27
|
};
|
|
28
28
|
/** Update the vault that receives eSIM payments. `onlyOwner`. */
|
|
29
29
|
export const _updateVaultAddress = async (client, newVaultAddress) => {
|
|
30
|
-
const chainID = await client
|
|
30
|
+
const chainID = await _chainId(client);
|
|
31
31
|
const rpcURL = client.transport.url;
|
|
32
32
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
33
33
|
if (!client.account)
|
|
@@ -35,7 +35,7 @@ export const _updateVaultAddress = async (client, newVaultAddress) => {
|
|
|
35
35
|
return writeContractOrThrow(client, {
|
|
36
36
|
address: values.factoryAddresses.REGISTRY,
|
|
37
37
|
chain: values.chain,
|
|
38
|
-
account: client.account
|
|
38
|
+
account: client.account,
|
|
39
39
|
abi: Registry,
|
|
40
40
|
functionName: 'updateVaultAddress',
|
|
41
41
|
args: [newVaultAddress]
|
|
@@ -51,7 +51,7 @@ export const _updateVaultAddress = async (client, newVaultAddress) => {
|
|
|
51
51
|
* incumbent instead withdraws a pending nomination and hands the role back.
|
|
52
52
|
*/
|
|
53
53
|
export const _requestAdminUpdate = async (client, newAdmin) => {
|
|
54
|
-
const chainID = await client
|
|
54
|
+
const chainID = await _chainId(client);
|
|
55
55
|
const rpcURL = client.transport.url;
|
|
56
56
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
57
57
|
if (!client.account)
|
|
@@ -59,7 +59,7 @@ export const _requestAdminUpdate = async (client, newAdmin) => {
|
|
|
59
59
|
return writeContractOrThrow(client, {
|
|
60
60
|
address: values.factoryAddresses.REGISTRY,
|
|
61
61
|
chain: values.chain,
|
|
62
|
-
account: client.account
|
|
62
|
+
account: client.account,
|
|
63
63
|
abi: Registry,
|
|
64
64
|
functionName: 'requestAdminUpdate',
|
|
65
65
|
args: [newAdmin]
|
|
@@ -73,7 +73,7 @@ export const _requestAdminUpdate = async (client, newAdmin) => {
|
|
|
73
73
|
* rather than passing quietly and leaving the caller believing it acted.
|
|
74
74
|
*/
|
|
75
75
|
export const _disableAdmin = async (client) => {
|
|
76
|
-
const chainID = await client
|
|
76
|
+
const chainID = await _chainId(client);
|
|
77
77
|
const rpcURL = client.transport.url;
|
|
78
78
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
79
79
|
if (!client.account)
|
|
@@ -81,7 +81,7 @@ export const _disableAdmin = async (client) => {
|
|
|
81
81
|
return writeContractOrThrow(client, {
|
|
82
82
|
address: values.factoryAddresses.REGISTRY,
|
|
83
83
|
chain: values.chain,
|
|
84
|
-
account: client.account
|
|
84
|
+
account: client.account,
|
|
85
85
|
abi: Registry,
|
|
86
86
|
functionName: 'disableAdmin',
|
|
87
87
|
args: []
|
|
@@ -95,7 +95,7 @@ export const _disableAdmin = async (client) => {
|
|
|
95
95
|
* incumbent. Reverts if the admin was never suspended.
|
|
96
96
|
*/
|
|
97
97
|
export const _enableAdmin = async (client) => {
|
|
98
|
-
const chainID = await client
|
|
98
|
+
const chainID = await _chainId(client);
|
|
99
99
|
const rpcURL = client.transport.url;
|
|
100
100
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
101
101
|
if (!client.account)
|
|
@@ -103,7 +103,7 @@ export const _enableAdmin = async (client) => {
|
|
|
103
103
|
return writeContractOrThrow(client, {
|
|
104
104
|
address: values.factoryAddresses.REGISTRY,
|
|
105
105
|
chain: values.chain,
|
|
106
|
-
account: client.account
|
|
106
|
+
account: client.account,
|
|
107
107
|
abi: Registry,
|
|
108
108
|
functionName: 'enableAdmin',
|
|
109
109
|
args: []
|
|
@@ -119,7 +119,7 @@ export const _enableAdmin = async (client) => {
|
|
|
119
119
|
* funds through their device wallet's `execute`, which a pause never reaches.
|
|
120
120
|
*/
|
|
121
121
|
export const _pause = async (client) => {
|
|
122
|
-
const chainID = await client
|
|
122
|
+
const chainID = await _chainId(client);
|
|
123
123
|
const rpcURL = client.transport.url;
|
|
124
124
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
125
125
|
if (!client.account)
|
|
@@ -127,7 +127,7 @@ export const _pause = async (client) => {
|
|
|
127
127
|
return writeContractOrThrow(client, {
|
|
128
128
|
address: values.factoryAddresses.REGISTRY,
|
|
129
129
|
chain: values.chain,
|
|
130
|
-
account: client.account
|
|
130
|
+
account: client.account,
|
|
131
131
|
abi: Registry,
|
|
132
132
|
functionName: 'pause',
|
|
133
133
|
args: []
|
|
@@ -141,7 +141,7 @@ export const _pause = async (client) => {
|
|
|
141
141
|
* `unpauseInstantly` if the wait is not acceptable.
|
|
142
142
|
*/
|
|
143
143
|
export const _unpause = async (client) => {
|
|
144
|
-
const chainID = await client
|
|
144
|
+
const chainID = await _chainId(client);
|
|
145
145
|
const rpcURL = client.transport.url;
|
|
146
146
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
147
147
|
if (!client.account)
|
|
@@ -149,7 +149,7 @@ export const _unpause = async (client) => {
|
|
|
149
149
|
return writeContractOrThrow(client, {
|
|
150
150
|
address: values.factoryAddresses.REGISTRY,
|
|
151
151
|
chain: values.chain,
|
|
152
|
-
account: client.account
|
|
152
|
+
account: client.account,
|
|
153
153
|
abi: Registry,
|
|
154
154
|
functionName: 'unpause',
|
|
155
155
|
args: []
|
|
@@ -164,7 +164,7 @@ export const _unpause = async (client) => {
|
|
|
164
164
|
* for every wallet without one of its own.
|
|
165
165
|
*/
|
|
166
166
|
export const _setDefaultPriceCapUSDCents = async (client, cap) => {
|
|
167
|
-
const chainID = await client
|
|
167
|
+
const chainID = await _chainId(client);
|
|
168
168
|
const rpcURL = client.transport.url;
|
|
169
169
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
170
170
|
if (!client.account)
|
|
@@ -172,7 +172,7 @@ export const _setDefaultPriceCapUSDCents = async (client, cap) => {
|
|
|
172
172
|
return writeContractOrThrow(client, {
|
|
173
173
|
address: values.factoryAddresses.REGISTRY,
|
|
174
174
|
chain: values.chain,
|
|
175
|
-
account: client.account
|
|
175
|
+
account: client.account,
|
|
176
176
|
abi: Registry,
|
|
177
177
|
functionName: 'setDefaultPriceCapUSDCents',
|
|
178
178
|
args: [cap]
|
|
@@ -185,7 +185,7 @@ export const _setDefaultPriceCapUSDCents = async (client, cap) => {
|
|
|
185
185
|
* wallet reverts rather than moving.
|
|
186
186
|
*/
|
|
187
187
|
export const _assignESIMIdentifier = async (client, eSIMWalletAddress, eSIMUniqueIdentifier) => {
|
|
188
|
-
const chainID = await client
|
|
188
|
+
const chainID = await _chainId(client);
|
|
189
189
|
const rpcURL = client.transport.url;
|
|
190
190
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
191
191
|
if (!client.account)
|
|
@@ -193,7 +193,7 @@ export const _assignESIMIdentifier = async (client, eSIMWalletAddress, eSIMUniqu
|
|
|
193
193
|
return writeContractOrThrow(client, {
|
|
194
194
|
address: values.factoryAddresses.REGISTRY,
|
|
195
195
|
chain: values.chain,
|
|
196
|
-
account: client.account
|
|
196
|
+
account: client.account,
|
|
197
197
|
abi: Registry,
|
|
198
198
|
functionName: 'assignESIMIdentifier',
|
|
199
199
|
args: [eSIMWalletAddress, eSIMUniqueIdentifier]
|
|
@@ -209,7 +209,7 @@ export const _assignESIMIdentifier = async (client, eSIMWalletAddress, eSIMUniqu
|
|
|
209
209
|
* `_paymentReference` is spendable once per eSIM wallet.
|
|
210
210
|
*/
|
|
211
211
|
export const _recordSettledPurchase = async (client, eSIMWalletAddress, dataBundleDetail, asset, tokenAmount, paymentReference) => {
|
|
212
|
-
const chainID = await client
|
|
212
|
+
const chainID = await _chainId(client);
|
|
213
213
|
const rpcURL = client.transport.url;
|
|
214
214
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
215
215
|
if (!client.account)
|
|
@@ -217,7 +217,7 @@ export const _recordSettledPurchase = async (client, eSIMWalletAddress, dataBund
|
|
|
217
217
|
return writeContractOrThrow(client, {
|
|
218
218
|
address: values.factoryAddresses.REGISTRY,
|
|
219
219
|
chain: values.chain,
|
|
220
|
-
account: client.account
|
|
220
|
+
account: client.account,
|
|
221
221
|
abi: Registry,
|
|
222
222
|
functionName: 'recordSettledPurchase',
|
|
223
223
|
args: [eSIMWalletAddress, dataBundleDetail, asset, tokenAmount, paymentReference]
|
|
@@ -229,7 +229,7 @@ export const _recordSettledPurchase = async (client, eSIMWalletAddress, dataBund
|
|
|
229
229
|
* newly nominated admin EOA.
|
|
230
230
|
*/
|
|
231
231
|
export const _acceptAdminUpdate = async (client) => {
|
|
232
|
-
const chainID = await client
|
|
232
|
+
const chainID = await _chainId(client);
|
|
233
233
|
const rpcURL = client.transport.url;
|
|
234
234
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
235
235
|
if (!client.account)
|
|
@@ -237,7 +237,7 @@ export const _acceptAdminUpdate = async (client) => {
|
|
|
237
237
|
return writeContractOrThrow(client, {
|
|
238
238
|
address: values.factoryAddresses.REGISTRY,
|
|
239
239
|
chain: values.chain,
|
|
240
|
-
account: client.account
|
|
240
|
+
account: client.account,
|
|
241
241
|
abi: Registry,
|
|
242
242
|
functionName: 'acceptAdminUpdate',
|
|
243
243
|
args: []
|
|
@@ -253,7 +253,7 @@ export const _acceptAdminUpdate = async (client) => {
|
|
|
253
253
|
* accepts for every contract at once and needs no delay.
|
|
254
254
|
*/
|
|
255
255
|
export const _acceptOwnership = async (client) => {
|
|
256
|
-
const chainID = await client
|
|
256
|
+
const chainID = await _chainId(client);
|
|
257
257
|
const rpcURL = client.transport.url;
|
|
258
258
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
259
259
|
if (!client.account)
|
|
@@ -261,7 +261,7 @@ export const _acceptOwnership = async (client) => {
|
|
|
261
261
|
return writeContractOrThrow(client, {
|
|
262
262
|
address: values.factoryAddresses.REGISTRY,
|
|
263
263
|
chain: values.chain,
|
|
264
|
-
account: client.account
|
|
264
|
+
account: client.account,
|
|
265
265
|
abi: Registry,
|
|
266
266
|
functionName: 'acceptOwnership',
|
|
267
267
|
args: []
|
|
@@ -282,7 +282,7 @@ export const _acceptOwnership = async (client) => {
|
|
|
282
282
|
* ownership where it is rather than stranding it.
|
|
283
283
|
*/
|
|
284
284
|
export const _transferOwnershipCall = async (client, newOwner) => {
|
|
285
|
-
const chainID = await client
|
|
285
|
+
const chainID = await _chainId(client);
|
|
286
286
|
const rpcURL = client.transport.url;
|
|
287
287
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
288
288
|
return {
|
|
@@ -305,7 +305,7 @@ export const _transferOwnershipCall = async (client, newOwner) => {
|
|
|
305
305
|
* again ends the proxy's life. Diff the storage layout before scheduling.
|
|
306
306
|
*/
|
|
307
307
|
export const _upgradeCall = async (client, newImplementation, data = '0x') => {
|
|
308
|
-
const chainID = await client
|
|
308
|
+
const chainID = await _chainId(client);
|
|
309
309
|
const rpcURL = client.transport.url;
|
|
310
310
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
311
311
|
return {
|
|
@@ -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
|
+
};
|
|
@@ -54,11 +54,24 @@ export const customErrors = {
|
|
|
54
54
|
MISSING_SMART_WALLET: "Error: Client does not have smart wallet account associated",
|
|
55
55
|
MISSING_EOA_WALLET: "Error: Client does not have EOA wallet associated"
|
|
56
56
|
};
|
|
57
|
+
// A client's RPC URL is fixed, so its chain id is too. Asking once per client
|
|
58
|
+
// saves every later SDK call a round trip to the node.
|
|
59
|
+
const chainIds = new WeakMap();
|
|
60
|
+
export const _chainId = (client) => {
|
|
61
|
+
let chainId = chainIds.get(client);
|
|
62
|
+
if (!chainId) {
|
|
63
|
+
chainId = client.getChainId();
|
|
64
|
+
chainIds.set(client, chainId);
|
|
65
|
+
// A failed lookup is not remembered, so the next call asks again.
|
|
66
|
+
chainId.catch(() => chainIds.delete(client));
|
|
67
|
+
}
|
|
68
|
+
return chainId;
|
|
69
|
+
};
|
|
57
70
|
export const _extractChainID = async (client) => {
|
|
58
71
|
if (!client) {
|
|
59
72
|
throw new InvalidClientError();
|
|
60
73
|
}
|
|
61
|
-
return client
|
|
74
|
+
return _chainId(client);
|
|
62
75
|
};
|
|
63
76
|
// Maps each supported chain id to its factory-address book + viem chain. Base
|
|
64
77
|
// Mainnet is listed with '0x' placeholders so the guard below rejects it with a
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { encodeFunctionData, getContract } from "viem";
|
|
2
|
-
import { DeviceWallet } from "../abis/index.js";
|
|
2
|
+
import { DeviceWallet, ESIMWalletFactory } from "../abis/index.js";
|
|
3
3
|
import { MissingSmartWalletError } from "./errors.js";
|
|
4
|
+
import { _chainId, _getChainSpecificConstants } from "./constants.js";
|
|
5
|
+
import { _getESIMWalletCounterFactualAddress } from "./eSIMWalletFactory.js";
|
|
4
6
|
// A userOp from a device wallet runs through `execute`, so at the target contract
|
|
5
7
|
// msg.sender is the device-wallet account itself. That constrains which DeviceWallet
|
|
6
8
|
// functions this surface can expose:
|
|
@@ -72,6 +74,38 @@ export const _addESIMWallet = async (client, address, eSIMWalletAddress) => {
|
|
|
72
74
|
}]
|
|
73
75
|
});
|
|
74
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Deploy an eSIM wallet for this device wallet and bind it, in one user operation.
|
|
79
|
+
*
|
|
80
|
+
* Fund access cannot be granted at bind time, so `grantAccessToFunds` adds a
|
|
81
|
+
* `toggleAccessToFunds` after the bind in the same operation. The device wallet
|
|
82
|
+
* must already be registered (`registry.isDeviceWalletValid`), or the factory
|
|
83
|
+
* refuses it.
|
|
84
|
+
*/
|
|
85
|
+
export const _deployAndBindESIMWallet = async (client, address, salt, grantAccessToFunds) => {
|
|
86
|
+
const chainID = await _chainId(client);
|
|
87
|
+
const rpcURL = client.transport.url;
|
|
88
|
+
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
89
|
+
if (!client.account)
|
|
90
|
+
throw new MissingSmartWalletError();
|
|
91
|
+
const eSIMWalletAddress = await _getESIMWalletCounterFactualAddress(client, address, salt);
|
|
92
|
+
const self = (functionName, hasAccessToFunds) => ({
|
|
93
|
+
to: address,
|
|
94
|
+
data: encodeFunctionData({ abi: DeviceWallet, functionName, args: [eSIMWalletAddress, hasAccessToFunds] })
|
|
95
|
+
});
|
|
96
|
+
const userOpHash = await client.sendUserOperation({
|
|
97
|
+
account: client.account,
|
|
98
|
+
calls: [
|
|
99
|
+
{
|
|
100
|
+
to: values.factoryAddresses.ESIM_WALLET_FACTORY,
|
|
101
|
+
data: encodeFunctionData({ abi: ESIMWalletFactory, functionName: "deployESIMWallet", args: [address, salt] })
|
|
102
|
+
},
|
|
103
|
+
self("addESIMWallet", false),
|
|
104
|
+
...(grantAccessToFunds ? [self("toggleAccessToFunds", true)] : [])
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
return { userOpHash, eSIMWalletAddress };
|
|
108
|
+
};
|
|
75
109
|
/**
|
|
76
110
|
* Release an eSIM wallet and put it on standby for a transfer.
|
|
77
111
|
*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { _getChainSpecificConstants } from "./constants.js";
|
|
1
|
+
import { _chainId, _getChainSpecificConstants } from "./constants.js";
|
|
2
2
|
import { MissingEOAWalletError } from "./errors.js";
|
|
3
3
|
import { DeviceWalletFactory } from "../abis/index.js";
|
|
4
4
|
export const _createAccountWithEOA = async (client, deviceUniqueIdentifier, deviceWalletOwnerKey, salt, depositAmount) => {
|
|
5
|
-
const chainID = await client
|
|
5
|
+
const chainID = await _chainId(client);
|
|
6
6
|
const rpcURL = client.transport.url;
|
|
7
7
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
8
8
|
if (!client.account)
|
|
@@ -12,7 +12,7 @@ export const _createAccountWithEOA = async (client, deviceUniqueIdentifier, devi
|
|
|
12
12
|
return client.writeContract({
|
|
13
13
|
address: values.factoryAddresses.DEVICE_WALLET_FACTORY,
|
|
14
14
|
chain: values.chain,
|
|
15
|
-
account: client.account
|
|
15
|
+
account: client.account,
|
|
16
16
|
abi: DeviceWalletFactory,
|
|
17
17
|
functionName: 'createAccount',
|
|
18
18
|
args: [deviceUniqueIdentifier, deviceWalletOwnerKey, salt],
|
|
@@ -23,7 +23,7 @@ export const _createAccountWithEOA = async (client, deviceUniqueIdentifier, devi
|
|
|
23
23
|
// On-chain arg order is (bytes32[2] ownerKey, string uid, uint256 salt); note this
|
|
24
24
|
// differs from `createAccount`.
|
|
25
25
|
export const _getAddress = async (client, deviceUniqueIdentifier, deviceWalletOwnerKey, salt) => {
|
|
26
|
-
const chainID = await client
|
|
26
|
+
const chainID = await _chainId(client);
|
|
27
27
|
const rpcURL = client.transport.url;
|
|
28
28
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
29
29
|
return client.readContract({
|
|
@@ -35,7 +35,7 @@ export const _getAddress = async (client, deviceUniqueIdentifier, deviceWalletOw
|
|
|
35
35
|
};
|
|
36
36
|
// `getCurrentDeviceWalletImplementation` is a `view` - read it directly instead of a userOp.
|
|
37
37
|
export const _getCurrentDeviceWalletImplementation = async (client) => {
|
|
38
|
-
const chainID = await client
|
|
38
|
+
const chainID = await _chainId(client);
|
|
39
39
|
const rpcURL = client.transport.url;
|
|
40
40
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
41
41
|
return client.readContract({
|
|
@@ -57,7 +57,7 @@ export const _getCurrentDeviceWalletImplementation = async (client) => {
|
|
|
57
57
|
* Reverts on an empty identifier or a key that is not a point on the P256 curve.
|
|
58
58
|
*/
|
|
59
59
|
export const _preCreateAccountValidation = async (client, deviceUniqueIdentifier, deviceWalletOwnerKey) => {
|
|
60
|
-
const chainID = await client
|
|
60
|
+
const chainID = await _chainId(client);
|
|
61
61
|
const rpcURL = client.transport.url;
|
|
62
62
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
63
63
|
return client.readContract({
|
|
@@ -73,7 +73,7 @@ export const _preCreateAccountValidation = async (client, deviceUniqueIdentifier
|
|
|
73
73
|
* registers it.
|
|
74
74
|
*/
|
|
75
75
|
export const _deviceWalletInfoAdded = async (client, deviceWallet) => {
|
|
76
|
-
const chainID = await client
|
|
76
|
+
const chainID = await _chainId(client);
|
|
77
77
|
const rpcURL = client.transport.url;
|
|
78
78
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
79
79
|
return client.readContract({
|
|
@@ -88,7 +88,7 @@ export const _deviceWalletInfoAdded = async (client, deviceWallet) => {
|
|
|
88
88
|
* all of them and no wallet can decline it.
|
|
89
89
|
*/
|
|
90
90
|
export const _beacon = async (client) => {
|
|
91
|
-
const chainID = await client
|
|
91
|
+
const chainID = await _chainId(client);
|
|
92
92
|
const rpcURL = client.transport.url;
|
|
93
93
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
94
94
|
return client.readContract({
|
|
@@ -100,7 +100,7 @@ export const _beacon = async (client) => {
|
|
|
100
100
|
};
|
|
101
101
|
/** The registry the factory writes new wallets into. */
|
|
102
102
|
export const _registry = async (client) => {
|
|
103
|
-
const chainID = await client
|
|
103
|
+
const chainID = await _chainId(client);
|
|
104
104
|
const rpcURL = client.transport.url;
|
|
105
105
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
106
106
|
return client.readContract({
|
|
@@ -112,7 +112,7 @@ export const _registry = async (client) => {
|
|
|
112
112
|
};
|
|
113
113
|
/** The EntryPoint baked into every device wallet this factory deploys. */
|
|
114
114
|
export const _entryPoint = async (client) => {
|
|
115
|
-
const chainID = await client
|
|
115
|
+
const chainID = await _chainId(client);
|
|
116
116
|
const rpcURL = client.transport.url;
|
|
117
117
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
118
118
|
return client.readContract({
|
|
@@ -124,7 +124,7 @@ export const _entryPoint = async (client) => {
|
|
|
124
124
|
};
|
|
125
125
|
/** The contract new device wallets verify WebAuthn assertions through. */
|
|
126
126
|
export const _verifier = async (client) => {
|
|
127
|
-
const chainID = await client
|
|
127
|
+
const chainID = await _chainId(client);
|
|
128
128
|
const rpcURL = client.transport.url;
|
|
129
129
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
130
130
|
return client.readContract({
|
|
@@ -2,6 +2,7 @@ import { encodeFunctionData, maxUint256 } from "viem";
|
|
|
2
2
|
import { MissingSmartWalletError } from "./errors.js";
|
|
3
3
|
import { ESIMWallet } from "../abis/index.js";
|
|
4
4
|
import { _defaultPriceCapUSDCents } from "./registry.js";
|
|
5
|
+
import { _acceptAndBindESIMWalletCalls, _buyDataBundleWithTransferCalls } from "./calls/eSIMWallet.calls.js";
|
|
5
6
|
// Not exposed on this surface:
|
|
6
7
|
// - populateHistory and setESIMUniqueIdentifier are `onlyRegistry` - callable
|
|
7
8
|
// only by the registry contract. Naming an eSIM goes through
|
|
@@ -61,6 +62,22 @@ export const _buyDataBundleWithToken = async (client, address, dataBundleDetails
|
|
|
61
62
|
}]
|
|
62
63
|
});
|
|
63
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* Buy a data bundle with tokens the device wallet sends over in the same user
|
|
67
|
+
* operation, so the eSIM wallet needs no access to the device wallet's funds.
|
|
68
|
+
*
|
|
69
|
+
* Only the shortfall is sent: the quote for the bundle minus what this eSIM
|
|
70
|
+
* wallet already holds of `asset`. Arguments are as for `buyDataBundleWithToken`.
|
|
71
|
+
* The backend builds the same calls with `admin.calls.buyDataBundleWithTransfer`.
|
|
72
|
+
*/
|
|
73
|
+
export const _buyDataBundleWithTransfer = async (client, address, dataBundleDetails, asset, maxAmountIn, paymentReference) => {
|
|
74
|
+
if (!client.account)
|
|
75
|
+
throw new MissingSmartWalletError();
|
|
76
|
+
return client.sendUserOperation({
|
|
77
|
+
account: client.account,
|
|
78
|
+
calls: await _buyDataBundleWithTransferCalls(client, address, dataBundleDetails, asset, maxAmountIn, paymentReference)
|
|
79
|
+
});
|
|
80
|
+
};
|
|
64
81
|
/**
|
|
65
82
|
* Send an ERC-20 held by this eSIM wallet back to its owning device wallet.
|
|
66
83
|
* `onlyDeviceWallet`.
|
|
@@ -180,6 +197,21 @@ export const _acceptOwnershipTransfer = async (client, address) => {
|
|
|
180
197
|
}]
|
|
181
198
|
});
|
|
182
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
|
+
};
|
|
183
215
|
export const _sendETHToDeviceWallet = async (client, address, amount) => {
|
|
184
216
|
if (!client.account)
|
|
185
217
|
throw new MissingSmartWalletError();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { encodeFunctionData } from "viem";
|
|
2
|
-
import { _getChainSpecificConstants } from "./constants.js";
|
|
2
|
+
import { _chainId, _getChainSpecificConstants } from "./constants.js";
|
|
3
3
|
import { MissingSmartWalletError } from "./errors.js";
|
|
4
4
|
import { ESIMWalletFactory } from "../abis/index.js";
|
|
5
5
|
// Not exposed on this surface:
|
|
@@ -10,7 +10,7 @@ import { ESIMWalletFactory } from "../abis/index.js";
|
|
|
10
10
|
// `deployESIMWalletWithUserOp` works because a registered device wallet passes
|
|
11
11
|
// `registry.isDeviceWalletValid(msg.sender)`, so the userOp sender satisfies the modifier.
|
|
12
12
|
export const _deployESIMWalletWithUserOp = async (client, deviceWalletAddress, salt) => {
|
|
13
|
-
const chainID = await client
|
|
13
|
+
const chainID = await _chainId(client);
|
|
14
14
|
const rpcURL = client.transport.url;
|
|
15
15
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
16
16
|
if (!client.account)
|
|
@@ -28,8 +28,20 @@ export const _deployESIMWalletWithUserOp = async (client, deviceWalletAddress, s
|
|
|
28
28
|
}]
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
|
+
/** Address `deployESIMWallet` deploys to for this device wallet and salt. */
|
|
32
|
+
export const _getESIMWalletCounterFactualAddress = async (client, deviceWalletAddress, salt) => {
|
|
33
|
+
const chainID = await _chainId(client);
|
|
34
|
+
const rpcURL = client.transport.url;
|
|
35
|
+
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
36
|
+
return client.readContract({
|
|
37
|
+
address: values.factoryAddresses.ESIM_WALLET_FACTORY,
|
|
38
|
+
abi: ESIMWalletFactory,
|
|
39
|
+
functionName: "getCounterFactualAddress",
|
|
40
|
+
args: [deviceWalletAddress, salt]
|
|
41
|
+
});
|
|
42
|
+
};
|
|
31
43
|
export const _getCurrentESIMWalletImplementation = async (client) => {
|
|
32
|
-
const chainID = await client
|
|
44
|
+
const chainID = await _chainId(client);
|
|
33
45
|
const rpcURL = client.transport.url;
|
|
34
46
|
const values = _getChainSpecificConstants(chainID, rpcURL);
|
|
35
47
|
// `getCurrentESIMWalletImplementation` is a `view` - read it directly instead of a userOp.
|
package/dist/esm/logic/errors.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseError, ContractFunctionRevertedError, decodeErrorResult, isHex, } from "viem";
|
|
2
|
-
import { DeviceWallet, DeviceWalletFactory, ESIMWallet, ESIMWalletFactory, LazyWalletRegistry, P256Verifier, Registry, RegistryHelper, } from "../abis/index.js";
|
|
2
|
+
import { DeviceWallet, DeviceWalletFactory, ESIMWallet, ESIMWalletFactory, LazyWalletRegistry, P256Verifier, Registry, RegistryHelper, ProtocolAdmin, PaymentAdapter, } from "../abis/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Base class for every error the Kokio SDK throws deliberately. Consumers can
|
|
5
5
|
* `instanceof KokioError` to distinguish SDK-originated failures from viem /
|
|
@@ -144,6 +144,8 @@ const REVERTABLE_ABIS = [
|
|
|
144
144
|
P256Verifier,
|
|
145
145
|
Registry,
|
|
146
146
|
RegistryHelper,
|
|
147
|
+
ProtocolAdmin,
|
|
148
|
+
PaymentAdapter,
|
|
147
149
|
];
|
|
148
150
|
/**
|
|
149
151
|
* Best-effort decode of raw revert data (the ABI-encoded `Error(string)`,
|
|
@@ -181,21 +183,25 @@ export class ContractRevertError extends KokioError {
|
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
/**
|
|
184
|
-
* Pulls a ContractRevertError out of an error thrown by viem, or `null`
|
|
185
|
-
*
|
|
186
|
+
* Pulls a ContractRevertError out of an error thrown by viem, or `null` when
|
|
187
|
+
* the error carries no revert data (a network failure, a rejected signature).
|
|
186
188
|
*/
|
|
187
189
|
export const toContractRevertError = (err) => {
|
|
188
190
|
if (!(err instanceof BaseError))
|
|
189
191
|
return null;
|
|
190
192
|
const revert = err.walk((e) => e instanceof ContractFunctionRevertedError);
|
|
191
|
-
if (
|
|
192
|
-
return
|
|
193
|
-
|
|
193
|
+
if (revert instanceof ContractFunctionRevertedError && revert.raw)
|
|
194
|
+
return new ContractRevertError(revert.raw);
|
|
195
|
+
// A bundler rejects a reverting user operation while estimating its gas, and
|
|
196
|
+
// puts the revert data only in the error message.
|
|
197
|
+
const data = err.details?.match(/reverted during simulation with reason: (0x[0-9a-fA-F]+)/)?.[1];
|
|
198
|
+
return data ? new ContractRevertError(data) : null;
|
|
194
199
|
};
|
|
195
200
|
/**
|
|
196
|
-
* `client.writeContract`, but a
|
|
197
|
-
*
|
|
198
|
-
*
|
|
201
|
+
* `client.writeContract`, but a revert comes back as a ContractRevertError
|
|
202
|
+
* instead of viem's raw error chain. Its `decoded` is null when the selector
|
|
203
|
+
* belongs to none of the Kokio contracts. Anything that is not a revert, such
|
|
204
|
+
* as a network failure, is rethrown as-is.
|
|
199
205
|
*
|
|
200
206
|
* Mirrors `WalletClient["writeContract"]`'s own generics rather than reading
|
|
201
207
|
* them off `Parameters<...>`, since that would collapse the per-call overload
|