@iota/isc-sdk 0.0.0-dev-20251015065834

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 (132) hide show
  1. package/.prettierignore +2 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/CHANGELOG.md +81 -0
  4. package/LICENSE +201 -0
  5. package/README.md +88 -0
  6. package/dist/cjs/api/EvmRpcClient.d.ts +7 -0
  7. package/dist/cjs/api/EvmRpcClient.js +57 -0
  8. package/dist/cjs/api/EvmRpcClient.js.map +7 -0
  9. package/dist/cjs/api/index.d.ts +1 -0
  10. package/dist/cjs/api/index.js +19 -0
  11. package/dist/cjs/api/index.js.map +7 -0
  12. package/dist/cjs/bcs.d.ts +944 -0
  13. package/dist/cjs/bcs.js +260 -0
  14. package/dist/cjs/bcs.js.map +7 -0
  15. package/dist/cjs/constants.d.ts +2 -0
  16. package/dist/cjs/constants.js +27 -0
  17. package/dist/cjs/constants.js.map +7 -0
  18. package/dist/cjs/enums/contracts.enums.d.ts +76 -0
  19. package/dist/cjs/enums/contracts.enums.js +113 -0
  20. package/dist/cjs/enums/contracts.enums.js.map +7 -0
  21. package/dist/cjs/enums/index.d.ts +1 -0
  22. package/dist/cjs/enums/index.js +19 -0
  23. package/dist/cjs/enums/index.js.map +7 -0
  24. package/dist/cjs/index.d.ts +9 -0
  25. package/dist/cjs/index.js +27 -0
  26. package/dist/cjs/index.js.map +7 -0
  27. package/dist/cjs/isc.d.ts +26 -0
  28. package/dist/cjs/isc.js +220 -0
  29. package/dist/cjs/isc.js.map +7 -0
  30. package/dist/cjs/move_bcs.d.ts +124 -0
  31. package/dist/cjs/move_bcs.js +70 -0
  32. package/dist/cjs/move_bcs.js.map +7 -0
  33. package/dist/cjs/package.json +4 -0
  34. package/dist/cjs/transaction.d.ts +168 -0
  35. package/dist/cjs/transaction.js +328 -0
  36. package/dist/cjs/transaction.js.map +7 -0
  37. package/dist/cjs/types/assetsResponse.d.ts +37 -0
  38. package/dist/cjs/types/assetsResponse.js +34 -0
  39. package/dist/cjs/types/assetsResponse.js.map +7 -0
  40. package/dist/cjs/types/chainData.d.ts +12 -0
  41. package/dist/cjs/types/chainData.js +29 -0
  42. package/dist/cjs/types/chainData.js.map +7 -0
  43. package/dist/cjs/types/index.d.ts +2 -0
  44. package/dist/cjs/types/index.js +20 -0
  45. package/dist/cjs/types/index.js.map +7 -0
  46. package/dist/cjs/utils/getHname.d.ts +1 -0
  47. package/dist/cjs/utils/getHname.js +30 -0
  48. package/dist/cjs/utils/getHname.js.map +7 -0
  49. package/dist/cjs/utils/index.d.ts +1 -0
  50. package/dist/cjs/utils/index.js +19 -0
  51. package/dist/cjs/utils/index.js.map +7 -0
  52. package/dist/cjs/vite-env.d.js +2 -0
  53. package/dist/cjs/vite-env.d.js.map +7 -0
  54. package/dist/esm/api/EvmRpcClient.d.ts +7 -0
  55. package/dist/esm/api/EvmRpcClient.js +37 -0
  56. package/dist/esm/api/EvmRpcClient.js.map +7 -0
  57. package/dist/esm/api/index.d.ts +1 -0
  58. package/dist/esm/api/index.js +2 -0
  59. package/dist/esm/api/index.js.map +7 -0
  60. package/dist/esm/bcs.d.ts +944 -0
  61. package/dist/esm/bcs.js +240 -0
  62. package/dist/esm/bcs.js.map +7 -0
  63. package/dist/esm/constants.d.ts +2 -0
  64. package/dist/esm/constants.js +7 -0
  65. package/dist/esm/constants.js.map +7 -0
  66. package/dist/esm/enums/contracts.enums.d.ts +76 -0
  67. package/dist/esm/enums/contracts.enums.js +93 -0
  68. package/dist/esm/enums/contracts.enums.js.map +7 -0
  69. package/dist/esm/enums/index.d.ts +1 -0
  70. package/dist/esm/enums/index.js +2 -0
  71. package/dist/esm/enums/index.js.map +7 -0
  72. package/dist/esm/index.d.ts +9 -0
  73. package/dist/esm/index.js +10 -0
  74. package/dist/esm/index.js.map +7 -0
  75. package/dist/esm/isc.d.ts +26 -0
  76. package/dist/esm/isc.js +200 -0
  77. package/dist/esm/isc.js.map +7 -0
  78. package/dist/esm/move_bcs.d.ts +124 -0
  79. package/dist/esm/move_bcs.js +50 -0
  80. package/dist/esm/move_bcs.js.map +7 -0
  81. package/dist/esm/package.json +4 -0
  82. package/dist/esm/transaction.d.ts +168 -0
  83. package/dist/esm/transaction.js +298 -0
  84. package/dist/esm/transaction.js.map +7 -0
  85. package/dist/esm/types/assetsResponse.d.ts +37 -0
  86. package/dist/esm/types/assetsResponse.js +14 -0
  87. package/dist/esm/types/assetsResponse.js.map +7 -0
  88. package/dist/esm/types/chainData.d.ts +12 -0
  89. package/dist/esm/types/chainData.js +9 -0
  90. package/dist/esm/types/chainData.js.map +7 -0
  91. package/dist/esm/types/index.d.ts +2 -0
  92. package/dist/esm/types/index.js +3 -0
  93. package/dist/esm/types/index.js.map +7 -0
  94. package/dist/esm/utils/getHname.d.ts +1 -0
  95. package/dist/esm/utils/getHname.js +10 -0
  96. package/dist/esm/utils/getHname.js.map +7 -0
  97. package/dist/esm/utils/index.d.ts +1 -0
  98. package/dist/esm/utils/index.js +2 -0
  99. package/dist/esm/utils/index.js.map +7 -0
  100. package/dist/esm/vite-env.d.js +10 -0
  101. package/dist/esm/vite-env.d.js.map +7 -0
  102. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  103. package/dist/tsconfig.tsbuildinfo +1 -0
  104. package/examples/L1AddressL2funds.ts +72 -0
  105. package/examples/anchor.ts +72 -0
  106. package/examples/assetsBag.ts +61 -0
  107. package/examples/config.ts +81 -0
  108. package/examples/tokens.ts +96 -0
  109. package/package.json +57 -0
  110. package/src/api/EvmRpcClient.ts +48 -0
  111. package/src/api/index.ts +1 -0
  112. package/src/bcs.ts +237 -0
  113. package/src/constants.ts +4 -0
  114. package/src/enums/contracts.enums.ts +82 -0
  115. package/src/enums/index.ts +1 -0
  116. package/src/index.ts +9 -0
  117. package/src/isc.ts +331 -0
  118. package/src/move_bcs.ts +47 -0
  119. package/src/transaction.ts +380 -0
  120. package/src/types/assetsResponse.ts +13 -0
  121. package/src/types/chainData.ts +8 -0
  122. package/src/types/index.ts +2 -0
  123. package/src/utils/getHname.ts +11 -0
  124. package/src/utils/index.ts +1 -0
  125. package/src/vite-env.d.ts +1 -0
  126. package/tests/L1ToL2Tokens.spec.ts +141 -0
  127. package/tests/config.ts +81 -0
  128. package/tests/getHname.spec.ts +78 -0
  129. package/tests/utils.ts +69 -0
  130. package/tsconfig.esm.json +7 -0
  131. package/tsconfig.json +11 -0
  132. package/vitest.config.ts +10 -0
@@ -0,0 +1,380 @@
1
+ import type { TransactionObjectArgument } from '@iota/iota-sdk/transactions';
2
+ import { Transaction } from '@iota/iota-sdk/transactions';
3
+ import * as isc from './isc.js';
4
+ import type { ChainData } from './types/index.js';
5
+ import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
6
+ import { L2_FROM_L1_GAS_BUDGET } from './constants.js';
7
+ import type { ObjectArgument } from './isc.js';
8
+
9
+ export type Agent = {
10
+ type: 'evm';
11
+ address: string;
12
+ };
13
+
14
+ export class IscTransaction {
15
+ #finalized: boolean;
16
+ #transaction: Transaction;
17
+ #chainData: ChainData;
18
+
19
+ constructor(chainData: ChainData, transaction = new Transaction()) {
20
+ this.#finalized = false;
21
+ this.#transaction = transaction;
22
+ this.#chainData = chainData;
23
+ }
24
+
25
+ private validateFinalizedStatus() {
26
+ if (this.#finalized) {
27
+ throw Error('Transaction is built.');
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Getter for the IOTA MOVE Transaction.
33
+ */
34
+ transaction(): Transaction {
35
+ return this.#transaction;
36
+ }
37
+
38
+ /**
39
+ * Create a bag.
40
+ */
41
+ newBag(): TransactionObjectArgument {
42
+ this.validateFinalizedStatus();
43
+ return isc.newBag(this.#transaction, this.#chainData);
44
+ }
45
+
46
+ /**
47
+ * Get some amount in a coin.
48
+ */
49
+ coinFromAmount({ amount }: { amount: number | bigint }) {
50
+ this.validateFinalizedStatus();
51
+ return isc.coinFromAmount(this.#transaction, BigInt(amount));
52
+ }
53
+
54
+ /**
55
+ * Place a coin in a bag.
56
+ *
57
+ * **Uses the IOTA Coin Type by default.**
58
+ */
59
+ placeCoinInBag({
60
+ bag,
61
+ coinType = IOTA_TYPE_ARG,
62
+ coin,
63
+ }: {
64
+ coin: ObjectArgument;
65
+ coinType?: string;
66
+ bag: TransactionObjectArgument;
67
+ }) {
68
+ this.validateFinalizedStatus();
69
+ isc.placeCoinInBag(this.#transaction, this.#chainData, bag, coinType, coin);
70
+ }
71
+
72
+ /**
73
+ * Finally create and send a request calling the given `contractFunction` with `contractArgs` in `contract`
74
+ */
75
+ createAndSend({
76
+ bag,
77
+ contract,
78
+ contractFunction,
79
+ contractArgs,
80
+ transfers,
81
+ gasBudget = L2_FROM_L1_GAS_BUDGET,
82
+ }: {
83
+ contractArgs: Uint8Array[];
84
+ contract: number;
85
+ contractFunction: number;
86
+ transfers: Array<[string, number | bigint]>;
87
+ gasBudget?: number | bigint;
88
+ bag: ObjectArgument;
89
+ }) {
90
+ this.validateFinalizedStatus();
91
+ isc.createAndSendRequest(
92
+ this.#transaction,
93
+ this.#chainData,
94
+ contract,
95
+ contractFunction,
96
+ contractArgs,
97
+ bag,
98
+ transfers,
99
+ gasBudget,
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Finally create and send a request calling the given `accountsFunction` in `accountsContract`
105
+ */
106
+ createAndSendToEvm({
107
+ address,
108
+ accountsContract,
109
+ accountsFunction,
110
+ transfers,
111
+ gasBudget = L2_FROM_L1_GAS_BUDGET,
112
+ bag,
113
+ }: {
114
+ address: string;
115
+ accountsContract: number;
116
+ accountsFunction: number;
117
+ transfers: Array<[string, number | bigint]>;
118
+ gasBudget?: number | bigint;
119
+ bag: ObjectArgument;
120
+ }) {
121
+ const agentID = isc.agentIdForEVM(address);
122
+ this.createAndSend({
123
+ bag,
124
+ gasBudget,
125
+ contract: accountsContract,
126
+ contractFunction: accountsFunction,
127
+ contractArgs: [agentID],
128
+ transfers,
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Take out the specified amount of coin from the bag.
134
+ *
135
+ * **Uses the IOTA Coin Type by default.**
136
+ */
137
+ takeCoinBalanceFromBag({
138
+ bag,
139
+ coinType = IOTA_TYPE_ARG,
140
+ amount,
141
+ }: {
142
+ amount: number | bigint;
143
+ coinType?: string;
144
+ bag: ObjectArgument;
145
+ }) {
146
+ this.validateFinalizedStatus();
147
+ return isc.takeCoinBalanceFromBag(
148
+ this.#transaction,
149
+ this.#chainData,
150
+ bag,
151
+ coinType,
152
+ amount,
153
+ );
154
+ }
155
+
156
+ /**
157
+ * Take out all the coin from the bag.
158
+ *
159
+ * **Uses the IOTA Coin Type by default.**
160
+ */
161
+ takeAllCoinBalanceFromBag({
162
+ bag,
163
+ coinType = IOTA_TYPE_ARG,
164
+ }: {
165
+ bag: ObjectArgument;
166
+ coinType?: string;
167
+ }) {
168
+ this.validateFinalizedStatus();
169
+ return isc.takeAllCoinBalanceFromBag(this.#transaction, this.#chainData, bag, coinType);
170
+ }
171
+
172
+ /**
173
+ * Place a coin balance in the bag.
174
+ *
175
+ * **Uses the IOTA Coin Type by default.**
176
+ */
177
+ placeCoinBalanceInBag({
178
+ bag,
179
+ coinType = IOTA_TYPE_ARG,
180
+ balance,
181
+ }: {
182
+ balance: ObjectArgument;
183
+ coinType?: string;
184
+ bag: ObjectArgument;
185
+ }) {
186
+ this.validateFinalizedStatus();
187
+ isc.placeCoinBalanceInBag(this.#transaction, this.#chainData, bag, coinType, balance);
188
+ }
189
+
190
+ /**
191
+ * Place an asset in the bag.
192
+ */
193
+ placeAssetInBag({
194
+ bag,
195
+ asset,
196
+ assetType,
197
+ }: {
198
+ asset: ObjectArgument;
199
+ bag: ObjectArgument;
200
+ assetType: string;
201
+ }) {
202
+ this.validateFinalizedStatus();
203
+ isc.placeAssetInBag(this.#transaction, this.#chainData, bag, assetType, asset);
204
+ }
205
+
206
+ /**
207
+ * Take an asset from a bag.
208
+ */
209
+ takeAssetFromBag({
210
+ bag,
211
+ assetType,
212
+ asset,
213
+ }: {
214
+ bag: ObjectArgument;
215
+ assetType: string;
216
+ asset: ObjectArgument;
217
+ }) {
218
+ this.validateFinalizedStatus();
219
+ isc.takeAssetFromBag(this.#transaction, this.#chainData, bag, assetType, asset);
220
+ }
221
+
222
+ /**
223
+ * Get the size of the bag.
224
+ */
225
+ getSizeOfBag({ bag }: { bag: ObjectArgument }) {
226
+ this.validateFinalizedStatus();
227
+ return isc.getSizeOfBag(this.#transaction, this.#chainData, bag);
228
+ }
229
+
230
+ /**
231
+ * Destroy the bag.
232
+ */
233
+ destroyBag({ bag }: { bag: ObjectArgument }) {
234
+ this.validateFinalizedStatus();
235
+ return isc.destroyBag(this.#transaction, this.#chainData, bag);
236
+ }
237
+
238
+ startNewChain({ metadata, coin }: { metadata: Uint8Array; coin?: ObjectArgument }) {
239
+ this.validateFinalizedStatus();
240
+ return isc.startNewChain(this.#transaction, this.#chainData, metadata, coin);
241
+ }
242
+
243
+ createAnchorWithAssetBag({ bag }: { bag: ObjectArgument }) {
244
+ this.validateFinalizedStatus();
245
+ return isc.createAnchorWithAssetBag(this.#transaction, this.#chainData, bag);
246
+ }
247
+
248
+ updateAnchorStateForMigraton({
249
+ anchor,
250
+ metadata,
251
+ stateIndex,
252
+ }: {
253
+ anchor: ObjectArgument;
254
+ metadata: Uint8Array;
255
+ stateIndex: number;
256
+ }) {
257
+ this.validateFinalizedStatus();
258
+ return isc.updateAnchorStateForMigraton(
259
+ this.#transaction,
260
+ this.#chainData,
261
+ anchor,
262
+ metadata,
263
+ stateIndex,
264
+ );
265
+ }
266
+
267
+ destroyAnchor({ anchor }: { anchor: ObjectArgument }) {
268
+ this.validateFinalizedStatus();
269
+ return isc.destroyAnchor(this.#transaction, this.#chainData, anchor);
270
+ }
271
+
272
+ borrowAssets({ anchor }: { anchor: ObjectArgument }) {
273
+ this.validateFinalizedStatus();
274
+ return isc.borrowAssets(this.#transaction, this.#chainData, anchor);
275
+ }
276
+
277
+ returnAssetsFromBorrow({
278
+ anchor,
279
+ bag,
280
+ borrow,
281
+ }: {
282
+ anchor: ObjectArgument;
283
+ bag: ObjectArgument;
284
+ borrow: ObjectArgument;
285
+ }) {
286
+ this.validateFinalizedStatus();
287
+ return isc.returnAssetsFromBorrow(this.#transaction, this.#chainData, anchor, bag, borrow);
288
+ }
289
+
290
+ receiveRequest({ anchor, request }: { anchor: ObjectArgument; request: ObjectArgument }) {
291
+ this.validateFinalizedStatus();
292
+ return isc.receiveRequest(this.#transaction, this.#chainData, anchor, request);
293
+ }
294
+
295
+ transition({
296
+ anchor,
297
+ newStateMetadata,
298
+ receipts,
299
+ }: {
300
+ anchor: ObjectArgument;
301
+ newStateMetadata: Uint8Array;
302
+ receipts: ObjectArgument;
303
+ }) {
304
+ this.validateFinalizedStatus();
305
+ return isc.transition(
306
+ this.#transaction,
307
+ this.#chainData,
308
+ anchor,
309
+ newStateMetadata,
310
+ receipts,
311
+ );
312
+ }
313
+
314
+ placeCoinForMigration({
315
+ anchor,
316
+ coinType = IOTA_TYPE_ARG,
317
+ coin,
318
+ }: {
319
+ anchor: ObjectArgument;
320
+ coinType?: string;
321
+ coin: ObjectArgument;
322
+ }) {
323
+ this.validateFinalizedStatus();
324
+ return isc.placeCoinForMigration(
325
+ this.#transaction,
326
+ this.#chainData,
327
+ anchor,
328
+ coinType,
329
+ coin,
330
+ );
331
+ }
332
+
333
+ placeCoinBalanceForMigration({
334
+ anchor,
335
+ coinType = IOTA_TYPE_ARG,
336
+ balance,
337
+ }: {
338
+ anchor: ObjectArgument;
339
+ coinType?: string;
340
+ balance: ObjectArgument;
341
+ }) {
342
+ this.validateFinalizedStatus();
343
+ return isc.placeCoinBalanceForMigration(
344
+ this.#transaction,
345
+ this.#chainData,
346
+ anchor,
347
+ coinType,
348
+ balance,
349
+ );
350
+ }
351
+
352
+ placeAssetForMigration({
353
+ anchor,
354
+ assetType,
355
+ asset,
356
+ }: {
357
+ anchor: ObjectArgument;
358
+ assetType: string;
359
+ asset: ObjectArgument;
360
+ }) {
361
+ this.validateFinalizedStatus();
362
+ return isc.placeAssetForMigration(
363
+ this.#transaction,
364
+ this.#chainData,
365
+ anchor,
366
+ assetType,
367
+ asset,
368
+ );
369
+ }
370
+
371
+ /**
372
+ * Stop building this ISC Transaction and return the IOTA MOVE Transaction.
373
+ * @returns IOTA MOVE Transaction.
374
+ */
375
+ build(): Transaction {
376
+ this.validateFinalizedStatus();
377
+ this.#finalized = true;
378
+ return this.#transaction;
379
+ }
380
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+
3
+ export const CoinJSONSchema = z.object({
4
+ balance: z.string(),
5
+ coinType: z.string(),
6
+ });
7
+
8
+ export const AssetsResponseSchema = z.object({
9
+ baseTokens: z.string(),
10
+ nativeTokens: z.array(CoinJSONSchema),
11
+ });
12
+
13
+ export type AssetsResponse = z.infer<typeof AssetsResponseSchema>;
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+
3
+ export const ChainDataSchema = z.object({
4
+ packageId: z.string(),
5
+ chainId: z.string(),
6
+ });
7
+
8
+ export type ChainData = z.infer<typeof ChainDataSchema>;
@@ -0,0 +1,2 @@
1
+ export * from './assetsResponse.js';
2
+ export * from './chainData.js';
@@ -0,0 +1,11 @@
1
+ import { blake2b } from '@noble/hashes/blake2';
2
+
3
+ export function getHname(input: string): number {
4
+ // Encode input as UTF-8 and hash it using BLAKE2b (32-byte digest)
5
+ const hash = blake2b(new TextEncoder().encode(input), { dkLen: 32 });
6
+
7
+ // Extract the first 4 bytes and interpret as little-endian uint32
8
+ const value = hash[0] | (hash[1] << 8) | (hash[2] << 16) | (hash[3] << 24);
9
+
10
+ return Number('0x' + (value >>> 0).toString(16).padStart(8, '0')); // Convert to hex with leading zeros
11
+ }
@@ -0,0 +1 @@
1
+ export * from './getHname.js';
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,141 @@
1
+ import {
2
+ AccountsContractMethod,
3
+ CoreContract,
4
+ getHname,
5
+ IscTransaction,
6
+ L2_FROM_L1_GAS_BUDGET,
7
+ } from '../src/index';
8
+ import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
9
+ import { Ed25519Keypair } from '@iota/iota-sdk/keypairs/ed25519';
10
+ import { requestIotaFromFaucetV0 } from '@iota/iota-sdk/faucet';
11
+ import { IotaClient } from '@iota/iota-sdk/client';
12
+ import { beforeAll, expect, test } from 'vitest';
13
+ import { CONFIG } from './config';
14
+ import { Wallet } from 'ethers';
15
+ import { bcs } from '@iota/iota-sdk/bcs';
16
+ import { checkL2BalanceWithRetries, requestFunds } from './utils';
17
+
18
+ const { L1 } = CONFIG;
19
+
20
+ let client: IotaClient;
21
+
22
+ beforeAll(async () => {
23
+ client = new IotaClient({
24
+ url: L1.rpcUrl,
25
+ });
26
+ });
27
+
28
+ test('Send IOTA', async () => {
29
+ const keypair = new Ed25519Keypair();
30
+ const address = keypair.toIotaAddress();
31
+
32
+ await requestIotaFromFaucetV0({
33
+ host: L1.faucetUrl!,
34
+ recipient: address,
35
+ });
36
+
37
+ const wallet = Wallet.createRandom();
38
+
39
+ // EVM Address
40
+ const recipientAddress = wallet.address;
41
+ // Amount to send (1 IOTAs)
42
+ const amountToSend = BigInt(1 * 1000000000);
43
+ // We also need to place a little more in the bag to cover the L2 gas
44
+ const amountToPlace = amountToSend + L2_FROM_L1_GAS_BUDGET;
45
+
46
+ const iscTx = new IscTransaction(L1);
47
+
48
+ const bag = iscTx.newBag();
49
+ const coin = iscTx.coinFromAmount({ amount: amountToPlace });
50
+ iscTx.placeCoinInBag({ coin, bag });
51
+ iscTx.createAndSendToEvm({
52
+ bag,
53
+ transfers: [[IOTA_TYPE_ARG, amountToSend]],
54
+ address: recipientAddress,
55
+ accountsContract: getHname(CoreContract.Accounts),
56
+ accountsFunction: getHname(AccountsContractMethod.TransferAllowanceTo),
57
+ });
58
+
59
+ const transaction = iscTx.build();
60
+ transaction.setSender(address);
61
+ await transaction.build({ client });
62
+
63
+ await client.signAndExecuteTransaction({
64
+ signer: keypair,
65
+ transaction,
66
+ });
67
+
68
+ const evmBalance = await checkL2BalanceWithRetries(recipientAddress);
69
+ expect(evmBalance?.baseTokens).toEqual(amountToSend.toString());
70
+ });
71
+
72
+ test('Send Non-IOTA Tokens', async () => {
73
+ const MNEMONIC =
74
+ 'mom program scrap easily doctor seed slender secret mad flat foam hospital cherry seek river you obscure column blood reflect arch pencil cat burst';
75
+ const TOKEN_COIN_TYPE =
76
+ '0xe1e88f4962b3ea96cfad19aee42f666b04bbce4dc4327c3cd63f1b8ff16e13b2::tool_coin::TOOL_COIN';
77
+
78
+ const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC);
79
+ const address = keypair.toIotaAddress();
80
+ const wallet = Wallet.createRandom();
81
+
82
+ await requestFunds(client, L1.faucetUrl!, address);
83
+
84
+ // EVM Address
85
+ const recipientAddress = wallet.address;
86
+ // Amount to send (0.01 IOTAs)
87
+ const amountToSend = BigInt(1 * 1_000_000);
88
+ // Amount to send (1 Boxfish)
89
+ const tokenAmountToSend = BigInt(1);
90
+ // We also need to place a little more in the bag to cover the L2 gas
91
+ const amountToPlace = amountToSend + L2_FROM_L1_GAS_BUDGET;
92
+
93
+ const iscTx = new IscTransaction(L1);
94
+ const tx = iscTx.transaction();
95
+
96
+ const bag = iscTx.newBag();
97
+
98
+ // Place IOTA
99
+ const iotaCoin = iscTx.coinFromAmount({ amount: amountToPlace });
100
+ iscTx.placeCoinInBag({ coin: iotaCoin, bag, coinType: IOTA_TYPE_ARG });
101
+
102
+ // Place Token
103
+ const tokenCoin = tx.splitCoins(
104
+ tx.object('0xf7662ffd9cb079d8e75ab4805ba78fdb0e0fb78cf49aa0fa01ecb7ebdf15d04e'),
105
+ [tx.pure(bcs.U64.serialize(tokenAmountToSend))],
106
+ );
107
+ iscTx.placeCoinInBag({
108
+ bag,
109
+ coin: tokenCoin,
110
+ coinType: TOKEN_COIN_TYPE,
111
+ });
112
+
113
+ iscTx.createAndSendToEvm({
114
+ bag,
115
+ transfers: [
116
+ [IOTA_TYPE_ARG, amountToSend],
117
+ [TOKEN_COIN_TYPE, tokenAmountToSend],
118
+ ],
119
+ address: recipientAddress,
120
+ accountsContract: getHname(CoreContract.Accounts),
121
+ accountsFunction: getHname(AccountsContractMethod.TransferAllowanceTo),
122
+ });
123
+
124
+ const transaction = iscTx.build();
125
+ transaction.setSender(address);
126
+ await transaction.build({ client });
127
+
128
+ await client.signAndExecuteTransaction({
129
+ signer: keypair,
130
+ transaction,
131
+ });
132
+
133
+ const evmBalance = await checkL2BalanceWithRetries(recipientAddress, TOKEN_COIN_TYPE);
134
+ expect(evmBalance?.baseTokens).toEqual(amountToSend.toString());
135
+ expect(evmBalance?.nativeTokens).toEqual([
136
+ {
137
+ coinType: TOKEN_COIN_TYPE,
138
+ balance: tokenAmountToSend.toString(),
139
+ },
140
+ ]);
141
+ });
@@ -0,0 +1,81 @@
1
+ // Copyright (c) 2025 IOTA Stiftung
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { z } from 'zod';
5
+ import { config } from 'dotenv';
6
+
7
+ const HEX_REGEX = /^0x[0-9a-fA-F]+$/;
8
+
9
+ const envSchema = z.record(
10
+ z.object({
11
+ L1: z.object({
12
+ networkName: z.string(),
13
+ rpcUrl: z.string().url(),
14
+ faucetUrl: z.string().url().optional(),
15
+ chainId: z.string(),
16
+ packageId: z.string(),
17
+ }),
18
+ L2: z.object({
19
+ chainName: z.string(),
20
+ chainCurrency: z.string(),
21
+ rpcUrl: z.string().url(),
22
+ chainId: z.preprocess(
23
+ (val) => (val !== undefined ? Number(val) : undefined),
24
+ z.number().int().positive(),
25
+ ),
26
+ chainDecimals: z.preprocess(
27
+ (val) => (val !== undefined ? Number(val) : undefined),
28
+ z.number().int().positive(),
29
+ ),
30
+ chainExplorerName: z.string(),
31
+ chainExplorerUrl: z.string(),
32
+ wagmiAppName: z.string(),
33
+ walletConnectProjectId: z.string(),
34
+ iscContractAddress: z
35
+ .string()
36
+ .regex(
37
+ HEX_REGEX,
38
+ 'Must be a valid hex string starting with 0x',
39
+ ) as z.ZodType<`0x${string}`>,
40
+ evmRpcUrl: z.string().url(),
41
+ }),
42
+ }),
43
+ );
44
+
45
+ type Config = z.infer<typeof envSchema>;
46
+
47
+ function loadConfig(): Config {
48
+ config();
49
+ const rawEvmBridgeConfig = process.env.VITE_EVM_BRIDGE_CONFIG as string;
50
+
51
+ let evmBridgeConfig: Record<string, unknown> = {};
52
+
53
+ try {
54
+ evmBridgeConfig = JSON.parse(rawEvmBridgeConfig);
55
+ } catch (error) {
56
+ throw new Error(
57
+ `Failed to parse IOTA EVM Bridge config JSON env var! ${(error as Error)?.message}`,
58
+ );
59
+ }
60
+
61
+ try {
62
+ return envSchema.parse(evmBridgeConfig);
63
+ } catch (error) {
64
+ if (error instanceof z.ZodError) {
65
+ const missingVars = error.issues
66
+ .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
67
+ .join('\n');
68
+ throw new Error(`Missing required configuration:\n${missingVars}`);
69
+ }
70
+
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ export const CONFIG = getDefaultNetwork();
76
+
77
+ function getDefaultNetwork() {
78
+ const config = loadConfig();
79
+ const evmBridgeDefaultNetwork = process.env.VITE_EVM_BRIDGE_DEFAULT_NETWORK as string;
80
+ return config[evmBridgeDefaultNetwork];
81
+ }