tempo.ts 0.0.0 → 0.0.1

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 (153) hide show
  1. package/README.md +92 -0
  2. package/dist/chains.d.ts +1477 -0
  3. package/dist/chains.d.ts.map +1 -0
  4. package/dist/chains.js +43 -0
  5. package/dist/chains.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/internal/types.d.ts +284 -0
  11. package/dist/internal/types.d.ts.map +1 -0
  12. package/dist/internal/types.js +2 -0
  13. package/dist/internal/types.js.map +1 -0
  14. package/dist/ox/TokenId.d.ts +18 -0
  15. package/dist/ox/TokenId.d.ts.map +1 -0
  16. package/dist/ox/TokenId.js +29 -0
  17. package/dist/ox/TokenId.js.map +1 -0
  18. package/dist/ox/TokenRole.d.ts +11 -0
  19. package/dist/ox/TokenRole.d.ts.map +1 -0
  20. package/dist/ox/TokenRole.js +22 -0
  21. package/dist/ox/TokenRole.js.map +1 -0
  22. package/dist/ox/Transaction.d.ts +161 -0
  23. package/dist/ox/Transaction.d.ts.map +1 -0
  24. package/dist/ox/Transaction.js +117 -0
  25. package/dist/ox/Transaction.js.map +1 -0
  26. package/dist/ox/TransactionEnvelopeFeeToken.d.ts +393 -0
  27. package/dist/ox/TransactionEnvelopeFeeToken.d.ts.map +1 -0
  28. package/dist/ox/TransactionEnvelopeFeeToken.js +452 -0
  29. package/dist/ox/TransactionEnvelopeFeeToken.js.map +1 -0
  30. package/dist/ox/TransactionRequest.d.ts +62 -0
  31. package/dist/ox/TransactionRequest.d.ts.map +1 -0
  32. package/dist/ox/TransactionRequest.js +66 -0
  33. package/dist/ox/TransactionRequest.js.map +1 -0
  34. package/dist/ox/index.d.ts +5 -0
  35. package/dist/ox/index.d.ts.map +1 -0
  36. package/dist/ox/index.js +5 -0
  37. package/dist/ox/index.js.map +1 -0
  38. package/dist/prool/Instance.d.ts +92 -0
  39. package/dist/prool/Instance.d.ts.map +1 -0
  40. package/dist/prool/Instance.js +96 -0
  41. package/dist/prool/Instance.js.map +1 -0
  42. package/dist/prool/index.d.ts +2 -0
  43. package/dist/prool/index.d.ts.map +1 -0
  44. package/dist/prool/index.js +2 -0
  45. package/dist/prool/index.js.map +1 -0
  46. package/dist/viem/abis.d.ts +2058 -0
  47. package/dist/viem/abis.d.ts.map +1 -0
  48. package/dist/viem/abis.js +1599 -0
  49. package/dist/viem/abis.js.map +1 -0
  50. package/dist/viem/actions/amm.d.ts +2091 -0
  51. package/dist/viem/actions/amm.d.ts.map +1 -0
  52. package/dist/viem/actions/amm.js +876 -0
  53. package/dist/viem/actions/amm.js.map +1 -0
  54. package/dist/viem/actions/fee.d.ts +727 -0
  55. package/dist/viem/actions/fee.d.ts.map +1 -0
  56. package/dist/viem/actions/fee.js +230 -0
  57. package/dist/viem/actions/fee.js.map +1 -0
  58. package/dist/viem/actions/index.d.ts +5 -0
  59. package/dist/viem/actions/index.d.ts.map +1 -0
  60. package/dist/viem/actions/index.js +5 -0
  61. package/dist/viem/actions/index.js.map +1 -0
  62. package/dist/viem/actions/policy.d.ts +1900 -0
  63. package/dist/viem/actions/policy.d.ts.map +1 -0
  64. package/dist/viem/actions/policy.js +841 -0
  65. package/dist/viem/actions/policy.js.map +1 -0
  66. package/dist/viem/actions/token.d.ts +13759 -0
  67. package/dist/viem/actions/token.d.ts.map +1 -0
  68. package/dist/viem/actions/token.js +2579 -0
  69. package/dist/viem/actions/token.js.map +1 -0
  70. package/dist/viem/addresses.d.ts +8 -0
  71. package/dist/viem/addresses.d.ts.map +1 -0
  72. package/dist/viem/addresses.js +8 -0
  73. package/dist/viem/addresses.js.map +1 -0
  74. package/dist/viem/chain.d.ts +341 -0
  75. package/dist/viem/chain.d.ts.map +1 -0
  76. package/dist/viem/chain.js +22 -0
  77. package/dist/viem/chain.js.map +1 -0
  78. package/dist/viem/client.d.ts +27 -0
  79. package/dist/viem/client.d.ts.map +1 -0
  80. package/dist/viem/client.js +28 -0
  81. package/dist/viem/client.js.map +1 -0
  82. package/dist/viem/decorator.d.ts +1636 -0
  83. package/dist/viem/decorator.d.ts.map +1 -0
  84. package/dist/viem/decorator.js +95 -0
  85. package/dist/viem/decorator.js.map +1 -0
  86. package/dist/viem/formatters.d.ts +4 -0
  87. package/dist/viem/formatters.d.ts.map +1 -0
  88. package/dist/viem/formatters.js +69 -0
  89. package/dist/viem/formatters.js.map +1 -0
  90. package/dist/viem/index.d.ts +9 -0
  91. package/dist/viem/index.d.ts.map +1 -0
  92. package/dist/viem/index.js +9 -0
  93. package/dist/viem/index.js.map +1 -0
  94. package/dist/viem/transaction.d.ts +54 -0
  95. package/dist/viem/transaction.d.ts.map +1 -0
  96. package/dist/viem/transaction.js +108 -0
  97. package/dist/viem/transaction.js.map +1 -0
  98. package/dist/viem/transport.d.ts +16 -0
  99. package/dist/viem/transport.d.ts.map +1 -0
  100. package/dist/viem/transport.js +33 -0
  101. package/dist/viem/transport.js.map +1 -0
  102. package/dist/viem/types.d.ts +10 -0
  103. package/dist/viem/types.d.ts.map +1 -0
  104. package/dist/viem/types.js +2 -0
  105. package/dist/viem/types.js.map +1 -0
  106. package/dist/viem/utils.d.ts +8 -0
  107. package/dist/viem/utils.d.ts.map +1 -0
  108. package/dist/viem/utils.js +9 -0
  109. package/dist/viem/utils.js.map +1 -0
  110. package/package.json +100 -2
  111. package/src/chains.ts +46 -0
  112. package/src/index.ts +1 -0
  113. package/src/internal/types.ts +414 -0
  114. package/src/ox/TokenId.test.ts +29 -0
  115. package/src/ox/TokenId.ts +35 -0
  116. package/src/ox/TokenRole.test.ts +20 -0
  117. package/src/ox/TokenRole.ts +27 -0
  118. package/src/ox/Transaction.test.ts +257 -0
  119. package/src/ox/Transaction.ts +247 -0
  120. package/src/ox/TransactionEnvelopeFeeToken.test.ts +1215 -0
  121. package/src/ox/TransactionEnvelopeFeeToken.ts +717 -0
  122. package/src/ox/TransactionRequest.ts +100 -0
  123. package/src/ox/index.ts +4 -0
  124. package/src/prool/Instance.test.ts +43 -0
  125. package/src/prool/Instance.ts +190 -0
  126. package/src/prool/index.ts +1 -0
  127. package/src/prool/internal/chain.json +106 -0
  128. package/src/prool/internal/consensus.toml +32 -0
  129. package/src/viem/abis.ts +1606 -0
  130. package/src/viem/actions/amm.test.ts +425 -0
  131. package/src/viem/actions/amm.ts +1308 -0
  132. package/src/viem/actions/fee.test.ts +281 -0
  133. package/src/viem/actions/fee.ts +362 -0
  134. package/src/viem/actions/index.ts +4 -0
  135. package/src/viem/actions/policy.test.ts +514 -0
  136. package/src/viem/actions/policy.ts +1284 -0
  137. package/src/viem/actions/token.test.ts +2172 -0
  138. package/src/viem/actions/token.ts +3830 -0
  139. package/src/viem/addresses.ts +10 -0
  140. package/src/viem/chain.ts +27 -0
  141. package/src/viem/client.bench-d.ts +8 -0
  142. package/src/viem/client.test.ts +152 -0
  143. package/src/viem/client.ts +91 -0
  144. package/src/viem/decorator.bench-d.ts +11 -0
  145. package/src/viem/decorator.test.ts +35 -0
  146. package/src/viem/decorator.ts +1914 -0
  147. package/src/viem/e2e.test.ts +410 -0
  148. package/src/viem/formatters.ts +100 -0
  149. package/src/viem/index.ts +8 -0
  150. package/src/viem/transaction.ts +253 -0
  151. package/src/viem/transport.ts +47 -0
  152. package/src/viem/types.ts +55 -0
  153. package/src/viem/utils.ts +37 -0
@@ -0,0 +1,2172 @@
1
+ import { setTimeout } from 'node:timers/promises'
2
+ import { Hex } from 'ox'
3
+ import * as actions from 'tempo.ts/viem/actions'
4
+ import { parseEther, publicActions } from 'viem'
5
+ import { mnemonicToAccount } from 'viem/accounts'
6
+ import { getCode, writeContractSync } from 'viem/actions'
7
+ import { describe, expect, test } from 'vitest'
8
+ import { tempoTest } from '../../../test/viem/config.js'
9
+ import { tip20Abi } from '../abis.js'
10
+ import { usdAddress, usdId } from '../addresses.js'
11
+ import { createTempoClient } from '../client.js'
12
+
13
+ const account = mnemonicToAccount(
14
+ 'test test test test test test test test test test test junk',
15
+ )
16
+ const account2 = mnemonicToAccount(
17
+ 'test test test test test test test test test test test junk',
18
+ { accountIndex: 1 },
19
+ )
20
+ const account3 = mnemonicToAccount(
21
+ 'test test test test test test test test test test test junk',
22
+ { accountIndex: 2 },
23
+ )
24
+
25
+ const client = createTempoClient({
26
+ account,
27
+ chain: tempoTest,
28
+ pollingInterval: 100,
29
+ }).extend(publicActions)
30
+
31
+ describe('approve', () => {
32
+ test('default', async () => {
33
+ {
34
+ // approve
35
+ const { receipt, ...result } = await actions.token.approveSync(client, {
36
+ spender: account2.address,
37
+ amount: parseEther('100'),
38
+ })
39
+ expect(receipt).toBeDefined()
40
+ expect(result).toMatchInlineSnapshot(`
41
+ {
42
+ "amount": 100000000000000000000n,
43
+ "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
44
+ "spender": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
45
+ }
46
+ `)
47
+ }
48
+
49
+ {
50
+ // check allowance
51
+ const allowance = await actions.token.getAllowance(client, {
52
+ spender: account2.address,
53
+ })
54
+ expect(allowance).toBe(parseEther('100'))
55
+ }
56
+
57
+ // transfer tokens for gas
58
+ await writeContractSync(client, {
59
+ abi: tip20Abi,
60
+ address: usdAddress,
61
+ functionName: 'transfer',
62
+ args: [account2.address, parseEther('1')],
63
+ })
64
+
65
+ // transfer tokens from approved account
66
+ await actions.token.transferSync(client, {
67
+ amount: parseEther('50'),
68
+ account: account2,
69
+ from: account.address,
70
+ to: '0x0000000000000000000000000000000000000001',
71
+ })
72
+
73
+ {
74
+ // verify updated allowance
75
+ const allowance = await actions.token.getAllowance(client, {
76
+ spender: account2.address,
77
+ })
78
+ expect(allowance).toBe(parseEther('50'))
79
+ }
80
+
81
+ // verify balance
82
+ const balance = await actions.token.getBalance(client, {
83
+ account: '0x0000000000000000000000000000000000000001',
84
+ })
85
+ expect(balance).toBe(parseEther('50'))
86
+ })
87
+
88
+ test('behavior: token address', async () => {
89
+ {
90
+ // approve
91
+ const { receipt, ...result } = await actions.token.approveSync(client, {
92
+ amount: parseEther('100'),
93
+ token: usdAddress,
94
+ spender: account2.address,
95
+ })
96
+ expect(receipt).toBeDefined()
97
+ expect(result).toMatchInlineSnapshot(`
98
+ {
99
+ "amount": 100000000000000000000n,
100
+ "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
101
+ "spender": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
102
+ }
103
+ `)
104
+ }
105
+
106
+ {
107
+ // check allowance
108
+ const allowance = await actions.token.getAllowance(client, {
109
+ token: usdAddress,
110
+ spender: account2.address,
111
+ })
112
+ expect(allowance).toBe(parseEther('100'))
113
+ }
114
+
115
+ // transfer tokens for gas
116
+ await writeContractSync(client, {
117
+ abi: tip20Abi,
118
+ address: usdAddress,
119
+ functionName: 'transfer',
120
+ args: [account2.address, parseEther('1')],
121
+ })
122
+
123
+ // transfer tokens from approved account
124
+ await actions.token.transferSync(client, {
125
+ amount: parseEther('50'),
126
+ account: account2,
127
+ from: account.address,
128
+ to: '0x0000000000000000000000000000000000000001',
129
+ token: usdAddress,
130
+ })
131
+
132
+ {
133
+ // verify updated allowance
134
+ const allowance = await actions.token.getAllowance(client, {
135
+ spender: account2.address,
136
+ token: usdAddress,
137
+ })
138
+ expect(allowance).toBe(parseEther('50'))
139
+ }
140
+
141
+ // verify balance
142
+ const balance = await actions.token.getBalance(client, {
143
+ account: '0x0000000000000000000000000000000000000001',
144
+ token: usdAddress,
145
+ })
146
+ expect(balance).toBe(parseEther('50'))
147
+ })
148
+
149
+ test('behavior: token address', async () => {
150
+ {
151
+ // approve
152
+ const { receipt, ...result } = await actions.token.approveSync(client, {
153
+ amount: parseEther('100'),
154
+ token: usdId,
155
+ spender: account2.address,
156
+ })
157
+ expect(receipt).toBeDefined()
158
+ expect(result).toMatchInlineSnapshot(`
159
+ {
160
+ "amount": 100000000000000000000n,
161
+ "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
162
+ "spender": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
163
+ }
164
+ `)
165
+ }
166
+
167
+ {
168
+ // check allowance
169
+ const allowance = await actions.token.getAllowance(client, {
170
+ token: usdId,
171
+ spender: account2.address,
172
+ })
173
+ expect(allowance).toBe(parseEther('100'))
174
+ }
175
+
176
+ // transfer tokens for gas
177
+ await writeContractSync(client, {
178
+ abi: tip20Abi,
179
+ address: usdAddress,
180
+ functionName: 'transfer',
181
+ args: [account2.address, parseEther('1')],
182
+ })
183
+
184
+ // transfer tokens from approved account
185
+ await actions.token.transferSync(client, {
186
+ amount: parseEther('50'),
187
+ account: account2,
188
+ from: account.address,
189
+ to: '0x0000000000000000000000000000000000000001',
190
+ token: usdId,
191
+ })
192
+
193
+ {
194
+ // verify updated allowance
195
+ const allowance = await actions.token.getAllowance(client, {
196
+ spender: account2.address,
197
+ token: usdId,
198
+ })
199
+ expect(allowance).toBe(parseEther('50'))
200
+ }
201
+
202
+ // verify balance
203
+ const balance = await actions.token.getBalance(client, {
204
+ account: '0x0000000000000000000000000000000000000001',
205
+ token: usdId,
206
+ })
207
+ expect(balance).toBe(parseEther('50'))
208
+ })
209
+ })
210
+
211
+ describe('create', () => {
212
+ test('default', async () => {
213
+ const { receipt, ...result } = await actions.token.createSync(client, {
214
+ currency: 'USD',
215
+ name: 'Test USD',
216
+ symbol: 'TUSD',
217
+ })
218
+
219
+ expect(result).toMatchInlineSnapshot(`
220
+ {
221
+ "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
222
+ "currency": "USD",
223
+ "name": "Test USD",
224
+ "symbol": "TUSD",
225
+ "token": "0x20C0000000000000000000000000000000000001",
226
+ "tokenId": 1n,
227
+ }
228
+ `)
229
+ expect(receipt).toBeDefined()
230
+
231
+ const code = await getCode(client, {
232
+ address: result.token,
233
+ })
234
+ expect(code).toBe('0xef')
235
+ })
236
+ })
237
+
238
+ describe('getAllowance', () => {
239
+ test('default', async () => {
240
+ // First, approve some allowance
241
+ await writeContractSync(client, {
242
+ abi: tip20Abi,
243
+ address: usdAddress,
244
+ functionName: 'approve',
245
+ args: [account2.address, parseEther('50')],
246
+ })
247
+
248
+ {
249
+ // Test with default token
250
+ const allowance = await actions.token.getAllowance(client, {
251
+ spender: account2.address,
252
+ })
253
+ expect(allowance).toBe(parseEther('50'))
254
+ }
255
+
256
+ {
257
+ // Test with token address
258
+ const allowance = await actions.token.getAllowance(client, {
259
+ token: usdAddress,
260
+ spender: account2.address,
261
+ })
262
+
263
+ expect(allowance).toBe(parseEther('50'))
264
+ }
265
+
266
+ {
267
+ // Test with token ID
268
+ const allowance = await actions.token.getAllowance(client, {
269
+ token: usdId,
270
+ spender: account2.address,
271
+ })
272
+
273
+ expect(allowance).toBe(parseEther('50'))
274
+ }
275
+ })
276
+ })
277
+
278
+ describe('getBalance', () => {
279
+ test('default', async () => {
280
+ {
281
+ // Test with default token
282
+ const balance = await actions.token.getBalance(client)
283
+ expect(balance).toBeGreaterThan(0n)
284
+ }
285
+
286
+ {
287
+ // Test with token address
288
+ const balance = await actions.token.getBalance(client, {
289
+ token: usdAddress,
290
+ })
291
+
292
+ expect(balance).toBeGreaterThan(0n)
293
+ }
294
+
295
+ {
296
+ // Test with token ID & different account
297
+ const balance = await actions.token.getBalance(client, {
298
+ token: usdId,
299
+ account: Hex.random(20),
300
+ })
301
+
302
+ expect(balance).toBe(0n)
303
+ }
304
+ })
305
+ })
306
+
307
+ describe('getMetadata', () => {
308
+ test('default', async () => {
309
+ const metadata = await actions.token.getMetadata(client)
310
+
311
+ expect(metadata).toMatchInlineSnapshot(`
312
+ {
313
+ "currency": "USD",
314
+ "decimals": 6,
315
+ "name": "TestUSD",
316
+ "paused": false,
317
+ "supplyCap": 115792089237316195423570985008687907853269984665640564039457584007913129639935n,
318
+ "symbol": "TestUSD",
319
+ "totalSupply": 340282366920938463647842048168863727605n,
320
+ "transferPolicy": "always-allow",
321
+ }
322
+ `)
323
+ })
324
+
325
+ test('behavior: custom token (address)', async () => {
326
+ const { token } = await actions.token.createSync(client, {
327
+ currency: 'USD',
328
+ name: 'Test USD',
329
+ symbol: 'TUSD',
330
+ })
331
+
332
+ const metadata = await actions.token.getMetadata(client, {
333
+ token,
334
+ })
335
+
336
+ expect(metadata).toMatchInlineSnapshot(`
337
+ {
338
+ "currency": "USD",
339
+ "decimals": 6,
340
+ "name": "Test USD",
341
+ "paused": false,
342
+ "supplyCap": 115792089237316195423570985008687907853269984665640564039457584007913129639935n,
343
+ "symbol": "TUSD",
344
+ "totalSupply": 0n,
345
+ "transferPolicy": "always-allow",
346
+ }
347
+ `)
348
+ })
349
+
350
+ test('behavior: custom token (id)', async () => {
351
+ const token = await actions.token.createSync(client, {
352
+ currency: 'USD',
353
+ name: 'Test USD',
354
+ symbol: 'TUSD',
355
+ })
356
+
357
+ const metadata = await actions.token.getMetadata(client, {
358
+ token: token.tokenId,
359
+ })
360
+
361
+ expect(metadata).toMatchInlineSnapshot(`
362
+ {
363
+ "currency": "USD",
364
+ "decimals": 6,
365
+ "name": "Test USD",
366
+ "paused": false,
367
+ "supplyCap": 115792089237316195423570985008687907853269984665640564039457584007913129639935n,
368
+ "symbol": "TUSD",
369
+ "totalSupply": 0n,
370
+ "transferPolicy": "always-allow",
371
+ }
372
+ `)
373
+ })
374
+ })
375
+
376
+ describe('mint', () => {
377
+ test('default', async () => {
378
+ // Create a new token where we're the admin
379
+ const { token } = await actions.token.createSync(client, {
380
+ currency: 'USD',
381
+ name: 'Mintable Token',
382
+ symbol: 'MINT',
383
+ })
384
+
385
+ // Grant issuer role
386
+ await actions.token.grantRolesSync(client, {
387
+ token,
388
+ roles: ['issuer'],
389
+ to: client.account.address,
390
+ })
391
+
392
+ // Check initial balance
393
+ const balanceBefore = await actions.token.getBalance(client, {
394
+ token,
395
+ account: account2.address,
396
+ })
397
+ expect(balanceBefore).toBe(0n)
398
+
399
+ // Mint tokens
400
+ const { receipt: mintReceipt, ...mintResult } =
401
+ await actions.token.mintSync(client, {
402
+ token,
403
+ to: account2.address,
404
+ amount: parseEther('1000'),
405
+ })
406
+ expect(mintReceipt).toBeDefined()
407
+ expect(mintResult).toMatchInlineSnapshot(`
408
+ {
409
+ "amount": 1000000000000000000000n,
410
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
411
+ }
412
+ `)
413
+
414
+ // Check balance after mint
415
+ const balanceAfter = await actions.token.getBalance(client, {
416
+ token,
417
+ account: account2.address,
418
+ })
419
+ expect(balanceAfter).toBe(parseEther('1000'))
420
+
421
+ // Check total supply
422
+ const metadata = await actions.token.getMetadata(client, {
423
+ token,
424
+ })
425
+ expect(metadata.totalSupply).toBe(parseEther('1000'))
426
+ })
427
+
428
+ // TODO: fix
429
+ test.skip('with memo', async () => {
430
+ // Create a new token
431
+ const { token } = await actions.token.createSync(client, {
432
+ admin: client.account,
433
+ currency: 'USD',
434
+ name: 'Mintable Token 2',
435
+ symbol: 'MINT2',
436
+ })
437
+
438
+ // Grant issuer role
439
+ await actions.token.grantRolesSync(client, {
440
+ token,
441
+ roles: ['issuer'],
442
+ to: client.account.address,
443
+ })
444
+
445
+ // Mint tokens with memo
446
+ const { receipt: mintMemoReceipt, ...mintMemoResult } =
447
+ await actions.token.mintSync(client, {
448
+ token,
449
+ to: account2.address,
450
+ amount: parseEther('500'),
451
+ memo: Hex.fromString('test'),
452
+ })
453
+ expect(mintMemoReceipt.status).toBe('success')
454
+ expect(mintMemoResult).toMatchInlineSnapshot(`
455
+ {
456
+ "amount": 500000000000000000000n,
457
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
458
+ }
459
+ `)
460
+
461
+ // Verify balance
462
+ const balance = await actions.token.getBalance(client, {
463
+ token,
464
+ account: account2.address,
465
+ })
466
+ expect(balance).toBe(parseEther('500'))
467
+ })
468
+ })
469
+
470
+ describe.todo('permitToken')
471
+
472
+ describe('transfer', () => {
473
+ test('default', async () => {
474
+ // Get initial balances
475
+ const senderBalanceBefore = await actions.token.getBalance(client, {
476
+ account: account.address,
477
+ })
478
+ const receiverBalanceBefore = await actions.token.getBalance(client, {
479
+ account: account2.address,
480
+ })
481
+
482
+ // Transfer tokens
483
+ const { receipt: transferReceipt, ...transferResult } =
484
+ await actions.token.transferSync(client, {
485
+ to: account2.address,
486
+ amount: parseEther('10'),
487
+ })
488
+ expect(transferReceipt).toBeDefined()
489
+ expect(transferResult).toMatchInlineSnapshot(`
490
+ {
491
+ "amount": 10000000000000000000n,
492
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
493
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
494
+ }
495
+ `)
496
+
497
+ // Verify balances
498
+ const senderBalanceAfter = await actions.token.getBalance(client, {
499
+ account: account.address,
500
+ })
501
+ const receiverBalanceAfter = await actions.token.getBalance(client, {
502
+ account: account2.address,
503
+ })
504
+
505
+ expect(senderBalanceAfter - senderBalanceBefore).toBeLessThan(
506
+ parseEther('10'),
507
+ )
508
+ expect(receiverBalanceAfter - receiverBalanceBefore).toBe(parseEther('10'))
509
+ })
510
+
511
+ test('behavior: with custom token', async () => {
512
+ // Create a new token
513
+ const { token } = await actions.token.createSync(client, {
514
+ currency: 'USD',
515
+ name: 'Transfer Token',
516
+ symbol: 'XFER',
517
+ })
518
+
519
+ // Grant issuer role and mint tokens
520
+ await actions.token.grantRolesSync(client, {
521
+ token,
522
+ roles: ['issuer'],
523
+ to: client.account.address,
524
+ })
525
+
526
+ await actions.token.mintSync(client, {
527
+ token,
528
+ to: client.account.address,
529
+ amount: parseEther('1000'),
530
+ })
531
+
532
+ // Transfer custom tokens
533
+ await actions.token.transferSync(client, {
534
+ token,
535
+ to: account2.address,
536
+ amount: parseEther('100'),
537
+ })
538
+
539
+ // Verify balance
540
+ const balance = await actions.token.getBalance(client, {
541
+ token,
542
+ account: account2.address,
543
+ })
544
+ expect(balance).toBe(parseEther('100'))
545
+ })
546
+
547
+ test('behavior: with memo', async () => {
548
+ const memo = Hex.fromString('Payment for services')
549
+
550
+ const { receipt: transferMemoReceipt, ...transferMemoResult } =
551
+ await actions.token.transferSync(client, {
552
+ to: account2.address,
553
+ amount: parseEther('5'),
554
+ memo,
555
+ })
556
+
557
+ expect(transferMemoReceipt.status).toBe('success')
558
+ expect(transferMemoResult).toMatchInlineSnapshot(`
559
+ {
560
+ "amount": 5000000000000000000n,
561
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
562
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
563
+ }
564
+ `)
565
+ })
566
+
567
+ test('behavior: from another account (transferFrom)', async () => {
568
+ // First approve account2 to spend tokens
569
+ await actions.token.approveSync(client, {
570
+ spender: account2.address,
571
+ amount: parseEther('50'),
572
+ })
573
+
574
+ // Transfer tokens for gas
575
+ await writeContractSync(client, {
576
+ abi: tip20Abi,
577
+ address: usdAddress,
578
+ functionName: 'transfer',
579
+ args: [account2.address, parseEther('1')],
580
+ })
581
+
582
+ // Get initial balance
583
+ const balanceBefore = await actions.token.getBalance(client, {
584
+ account: account3.address,
585
+ })
586
+
587
+ // Account2 transfers from account to account3
588
+ await actions.token.transferSync(client, {
589
+ account: account2,
590
+ from: account.address,
591
+ to: account3.address,
592
+ amount: parseEther('25'),
593
+ })
594
+
595
+ // Verify balance
596
+ const balanceAfter = await actions.token.getBalance(client, {
597
+ account: account3.address,
598
+ })
599
+ expect(balanceAfter - balanceBefore).toBe(parseEther('25'))
600
+
601
+ // Verify allowance was reduced
602
+ const allowance = await actions.token.getAllowance(client, {
603
+ spender: account2.address,
604
+ })
605
+ expect(allowance).toBe(parseEther('25'))
606
+ })
607
+ })
608
+
609
+ describe('burn', () => {
610
+ test('default', async () => {
611
+ // Create a new token where we have issuer role
612
+ const { token } = await actions.token.createSync(client, {
613
+ currency: 'USD',
614
+ name: 'Burnable Token',
615
+ symbol: 'BURN',
616
+ })
617
+
618
+ // Grant issuer role
619
+ await actions.token.grantRolesSync(client, {
620
+ token,
621
+ roles: ['issuer'],
622
+ to: client.account.address,
623
+ })
624
+
625
+ // Mint some tokens
626
+ await actions.token.mintSync(client, {
627
+ token,
628
+ to: client.account.address,
629
+ amount: parseEther('1000'),
630
+ })
631
+
632
+ // Check balance before burn
633
+ const balanceBefore = await actions.token.getBalance(client, {
634
+ token,
635
+ })
636
+ expect(balanceBefore).toBe(parseEther('1000'))
637
+
638
+ // Check total supply before
639
+ const metadataBefore = await actions.token.getMetadata(client, {
640
+ token,
641
+ })
642
+ expect(metadataBefore.totalSupply).toBe(parseEther('1000'))
643
+
644
+ // Burn tokens
645
+ const { receipt, ...result } = await actions.token.burnSync(client, {
646
+ token,
647
+ amount: parseEther('100'),
648
+ })
649
+ expect(receipt).toBeDefined()
650
+ expect(result).toMatchInlineSnapshot(`
651
+ {
652
+ "amount": 100000000000000000000n,
653
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
654
+ }
655
+ `)
656
+
657
+ // Check balance after burn
658
+ const balanceAfter = await actions.token.getBalance(client, {
659
+ token,
660
+ })
661
+ expect(balanceAfter).toBe(parseEther('900'))
662
+
663
+ // Check total supply after
664
+ const metadataAfter = await actions.token.getMetadata(client, {
665
+ token,
666
+ })
667
+ expect(metadataAfter.totalSupply).toBe(parseEther('900'))
668
+ })
669
+
670
+ test('behavior: requires issuer role', async () => {
671
+ // Create a new token
672
+ const { token } = await actions.token.createSync(client, {
673
+ currency: 'USD',
674
+ name: 'Restricted Burn Token',
675
+ symbol: 'RBURN',
676
+ })
677
+
678
+ // Grant issuer role to account2 (not us)
679
+ await actions.token.grantRolesSync(client, {
680
+ token,
681
+ roles: ['issuer'],
682
+ to: account2.address,
683
+ })
684
+
685
+ // Transfer gas to account2
686
+ await writeContractSync(client, {
687
+ abi: tip20Abi,
688
+ address: usdAddress,
689
+ functionName: 'transfer',
690
+ args: [account2.address, parseEther('1')],
691
+ })
692
+
693
+ await actions.token.mintSync(client, {
694
+ account: account2,
695
+ token,
696
+ to: client.account.address,
697
+ amount: parseEther('100'),
698
+ })
699
+
700
+ // Try to burn without issuer role - should fail
701
+ await expect(
702
+ actions.token.burnSync(client, {
703
+ token,
704
+ amount: parseEther('10'),
705
+ }),
706
+ ).rejects.toThrow()
707
+ })
708
+ })
709
+
710
+ describe.todo('burnBlockedToken')
711
+
712
+ describe.todo('changeTokenTransferPolicy')
713
+
714
+ describe('pause', () => {
715
+ test('default', async () => {
716
+ // Create a new token
717
+ const { token } = await actions.token.createSync(client, {
718
+ currency: 'USD',
719
+ name: 'Pausable Token',
720
+ symbol: 'PAUSE',
721
+ })
722
+
723
+ // Grant pause role
724
+ await actions.token.grantRolesSync(client, {
725
+ token,
726
+ roles: ['pause'],
727
+ to: client.account.address,
728
+ })
729
+
730
+ // Grant issuer role and mint tokens
731
+ await actions.token.grantRolesSync(client, {
732
+ token,
733
+ roles: ['issuer'],
734
+ to: client.account.address,
735
+ })
736
+
737
+ await actions.token.mintSync(client, {
738
+ token,
739
+ to: account2.address,
740
+ amount: parseEther('1000'),
741
+ })
742
+
743
+ // Verify token is not paused
744
+ let metadata = await actions.token.getMetadata(client, {
745
+ token,
746
+ })
747
+ expect(metadata.paused).toBe(false)
748
+
749
+ // Transfer gas
750
+ await writeContractSync(client, {
751
+ abi: tip20Abi,
752
+ address: usdAddress,
753
+ functionName: 'transfer',
754
+ args: [account2.address, parseEther('1')],
755
+ })
756
+
757
+ await actions.token.transferSync(client, {
758
+ account: account2,
759
+ token,
760
+ to: account3.address,
761
+ amount: parseEther('100'),
762
+ })
763
+
764
+ // Pause the token
765
+ const { receipt: pauseReceipt, ...pauseResult } =
766
+ await actions.token.pauseSync(client, {
767
+ token,
768
+ })
769
+ expect(pauseReceipt).toBeDefined()
770
+ expect(pauseResult).toMatchInlineSnapshot(`
771
+ {
772
+ "isPaused": true,
773
+ "updater": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
774
+ }
775
+ `)
776
+
777
+ // Verify token is paused
778
+ metadata = await actions.token.getMetadata(client, {
779
+ token,
780
+ })
781
+ expect(metadata.paused).toBe(true)
782
+
783
+ // Transfers should now fail
784
+ await expect(
785
+ actions.token.transferSync(client, {
786
+ account: account2,
787
+ token,
788
+ to: account3.address,
789
+ amount: parseEther('100'),
790
+ }),
791
+ ).rejects.toThrow()
792
+ })
793
+
794
+ test('behavior: requires pause role', async () => {
795
+ // Create a new token
796
+ const { token } = await actions.token.createSync(client, {
797
+ currency: 'USD',
798
+ name: 'Restricted Pause Token',
799
+ symbol: 'RPAUSE',
800
+ })
801
+
802
+ // Try to pause without pause role - should fail
803
+ await expect(
804
+ actions.token.pauseSync(client, {
805
+ token,
806
+ }),
807
+ ).rejects.toThrow()
808
+
809
+ // Grant pause role to account2
810
+ await actions.token.grantRolesSync(client, {
811
+ token,
812
+ roles: ['pause'],
813
+ to: account2.address,
814
+ })
815
+
816
+ // Transfer gas to account2
817
+ await writeContractSync(client, {
818
+ abi: tip20Abi,
819
+ address: usdAddress,
820
+ functionName: 'transfer',
821
+ args: [account2.address, parseEther('1')],
822
+ })
823
+
824
+ await actions.token.pauseSync(client, {
825
+ account: account2,
826
+ token,
827
+ })
828
+
829
+ // Verify token is paused
830
+ const metadata = await actions.token.getMetadata(client, {
831
+ token,
832
+ })
833
+ expect(metadata.paused).toBe(true)
834
+ })
835
+
836
+ test('behavior: cannot pause already paused token', async () => {
837
+ // Create a new token
838
+ const { token: address } = await actions.token.createSync(client, {
839
+ currency: 'USD',
840
+ name: 'Double Pause Token',
841
+ symbol: 'DPAUSE',
842
+ })
843
+
844
+ // Grant pause role
845
+ await actions.token.grantRolesSync(client, {
846
+ token: address,
847
+ roles: ['pause'],
848
+ to: client.account.address,
849
+ })
850
+
851
+ // Pause the token
852
+ await actions.token.pauseSync(client, {
853
+ token: address,
854
+ })
855
+
856
+ // Try to pause again - implementation may vary, but typically this succeeds without error
857
+ const { receipt: doublePauseReceipt, ...doublePauseResult } =
858
+ await actions.token.pauseSync(client, {
859
+ token: address,
860
+ })
861
+ expect(doublePauseReceipt.status).toBe('success')
862
+ expect(doublePauseResult).toMatchInlineSnapshot(`
863
+ {
864
+ "isPaused": true,
865
+ "updater": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
866
+ }
867
+ `)
868
+ })
869
+ })
870
+
871
+ describe('unpause', () => {
872
+ test('default', async () => {
873
+ // Create a new token
874
+ const { token: address } = await actions.token.createSync(client, {
875
+ currency: 'USD',
876
+ name: 'Unpausable Token',
877
+ symbol: 'UNPAUSE',
878
+ })
879
+
880
+ // Grant pause and unpause roles
881
+ await actions.token.grantRolesSync(client, {
882
+ token: address,
883
+ roles: ['pause'],
884
+ to: client.account.address,
885
+ })
886
+
887
+ await actions.token.grantRolesSync(client, {
888
+ token: address,
889
+ roles: ['unpause'],
890
+ to: client.account.address,
891
+ })
892
+
893
+ // Grant issuer role and mint tokens
894
+ await actions.token.grantRolesSync(client, {
895
+ token: address,
896
+ roles: ['issuer'],
897
+ to: client.account.address,
898
+ })
899
+
900
+ await actions.token.mintSync(client, {
901
+ token: address,
902
+ to: account2.address,
903
+ amount: parseEther('1000'),
904
+ })
905
+
906
+ // First pause the token
907
+ await actions.token.pauseSync(client, {
908
+ token: address,
909
+ })
910
+
911
+ // Verify token is paused
912
+ let metadata = await actions.token.getMetadata(client, {
913
+ token: address,
914
+ })
915
+ expect(metadata.paused).toBe(true)
916
+
917
+ // Transfer gas to account2
918
+ await writeContractSync(client, {
919
+ abi: tip20Abi,
920
+ address: usdAddress,
921
+ functionName: 'transfer',
922
+ args: [account2.address, parseEther('1')],
923
+ })
924
+
925
+ // Verify transfers fail when paused
926
+ await expect(
927
+ actions.token.transferSync(client, {
928
+ account: account2,
929
+ token: address,
930
+ to: account3.address,
931
+ amount: parseEther('100'),
932
+ }),
933
+ ).rejects.toThrow()
934
+
935
+ // Unpause the token
936
+ const { receipt: unpauseReceipt, ...unpauseResult } =
937
+ await actions.token.unpauseSync(client, {
938
+ token: address,
939
+ })
940
+ expect(unpauseReceipt).toBeDefined()
941
+ expect(unpauseResult).toMatchInlineSnapshot(`
942
+ {
943
+ "isPaused": false,
944
+ "updater": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
945
+ }
946
+ `)
947
+
948
+ // Verify token is unpaused
949
+ metadata = await actions.token.getMetadata(client, {
950
+ token: address,
951
+ })
952
+ expect(metadata.paused).toBe(false)
953
+
954
+ // Transfers should work again
955
+ await actions.token.transferSync(client, {
956
+ account: account2,
957
+ token: address,
958
+ to: account3.address,
959
+ amount: parseEther('100'),
960
+ })
961
+
962
+ const balance = await actions.token.getBalance(client, {
963
+ token: address,
964
+ account: account3.address,
965
+ })
966
+ expect(balance).toBe(parseEther('100'))
967
+ })
968
+
969
+ test('behavior: requires unpause role', async () => {
970
+ // Create a new token
971
+ const { token: address } = await actions.token.createSync(client, {
972
+ currency: 'USD',
973
+ name: 'Restricted Unpause Token',
974
+ symbol: 'RUNPAUSE',
975
+ })
976
+
977
+ // Grant pause role and pause the token
978
+ await actions.token.grantRolesSync(client, {
979
+ token: address,
980
+ roles: ['pause'],
981
+ to: client.account.address,
982
+ })
983
+
984
+ await actions.token.pauseSync(client, {
985
+ token: address,
986
+ })
987
+
988
+ // Try to unpause without unpause role - should fail
989
+ await expect(
990
+ actions.token.unpauseSync(client, {
991
+ token: address,
992
+ }),
993
+ ).rejects.toThrow()
994
+
995
+ // Grant unpause role to account2
996
+ await actions.token.grantRolesSync(client, {
997
+ token: address,
998
+ roles: ['unpause'],
999
+ to: account2.address,
1000
+ })
1001
+
1002
+ // Transfer gas to account2
1003
+ await writeContractSync(client, {
1004
+ abi: tip20Abi,
1005
+ address: usdAddress,
1006
+ functionName: 'transfer',
1007
+ args: [account2.address, parseEther('1')],
1008
+ })
1009
+
1010
+ // Now account2 should be able to unpause
1011
+ await actions.token.unpauseSync(client, {
1012
+ account: account2,
1013
+ token: address,
1014
+ })
1015
+
1016
+ // Verify token is unpaused
1017
+ const metadata = await actions.token.getMetadata(client, {
1018
+ token: address,
1019
+ })
1020
+ expect(metadata.paused).toBe(false)
1021
+ })
1022
+
1023
+ test('behavior: different roles for pause and unpause', async () => {
1024
+ // Create a new token
1025
+ const { token: address } = await actions.token.createSync(client, {
1026
+ currency: 'USD',
1027
+ name: 'Split Role Token',
1028
+ symbol: 'SPLIT',
1029
+ })
1030
+
1031
+ // Grant pause role to account2
1032
+ await actions.token.grantRolesSync(client, {
1033
+ token: address,
1034
+ roles: ['pause'],
1035
+ to: account2.address,
1036
+ })
1037
+
1038
+ // Grant unpause role to account3
1039
+ await actions.token.grantRolesSync(client, {
1040
+ token: address,
1041
+ roles: ['unpause'],
1042
+ to: account3.address,
1043
+ })
1044
+
1045
+ // Transfer gas to both accounts
1046
+ await writeContractSync(client, {
1047
+ abi: tip20Abi,
1048
+ address: usdAddress,
1049
+ functionName: 'transfer',
1050
+ args: [account2.address, parseEther('1')],
1051
+ })
1052
+
1053
+ await writeContractSync(client, {
1054
+ abi: tip20Abi,
1055
+ address: usdAddress,
1056
+ functionName: 'transfer',
1057
+ args: [account3.address, parseEther('1')],
1058
+ })
1059
+
1060
+ // Account2 can pause
1061
+ await actions.token.pauseSync(client, {
1062
+ account: account2,
1063
+ token: address,
1064
+ })
1065
+
1066
+ // Account2 cannot unpause
1067
+ await expect(
1068
+ actions.token.unpauseSync(client, {
1069
+ account: account2,
1070
+ token: address,
1071
+ }),
1072
+ ).rejects.toThrow()
1073
+
1074
+ // Account3 can unpause
1075
+ await actions.token.unpauseSync(client, {
1076
+ account: account3,
1077
+ token: address,
1078
+ })
1079
+
1080
+ // Verify token is unpaused
1081
+ const metadata = await actions.token.getMetadata(client, {
1082
+ token: address,
1083
+ })
1084
+ expect(metadata.paused).toBe(false)
1085
+ })
1086
+ })
1087
+
1088
+ describe.todo('setTokenSupplyCap')
1089
+
1090
+ describe('grantRoles', () => {
1091
+ test('default', async () => {
1092
+ // Create a new token where we're the admin
1093
+ const { token: address } = await actions.token.createSync(client, {
1094
+ admin: client.account,
1095
+ currency: 'USD',
1096
+ name: 'Test Token',
1097
+ symbol: 'TEST',
1098
+ })
1099
+
1100
+ // Grant issuer role to account2
1101
+ const { receipt: grantReceipt, value: grantValue } =
1102
+ await actions.token.grantRolesSync(client, {
1103
+ token: address,
1104
+ roles: ['issuer'],
1105
+ to: account2.address,
1106
+ })
1107
+
1108
+ expect(grantReceipt.status).toBe('success')
1109
+ expect(grantValue).toMatchInlineSnapshot(`
1110
+ [
1111
+ {
1112
+ "account": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1113
+ "hasRole": true,
1114
+ "role": "0x114e74f6ea3bd819998f78687bfcb11b140da08e9b7d222fa9c1f1ba1f2aa122",
1115
+ "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1116
+ },
1117
+ ]
1118
+ `)
1119
+ })
1120
+ })
1121
+
1122
+ describe('revokeTokenRole', async () => {
1123
+ test('default', async () => {
1124
+ const { token: address } = await actions.token.createSync(client, {
1125
+ admin: client.account,
1126
+ currency: 'USD',
1127
+ name: 'Test Token 2',
1128
+ symbol: 'TEST2',
1129
+ })
1130
+
1131
+ await actions.token.grantRolesSync(client, {
1132
+ token: address,
1133
+ roles: ['issuer'],
1134
+ to: account2.address,
1135
+ })
1136
+
1137
+ const { receipt: revokeReceipt, value: revokeValue } =
1138
+ await actions.token.revokeRolesSync(client, {
1139
+ from: account2.address,
1140
+ token: address,
1141
+ roles: ['issuer'],
1142
+ })
1143
+
1144
+ expect(revokeReceipt.status).toBe('success')
1145
+ expect(revokeValue).toMatchInlineSnapshot(`
1146
+ [
1147
+ {
1148
+ "account": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1149
+ "hasRole": false,
1150
+ "role": "0x114e74f6ea3bd819998f78687bfcb11b140da08e9b7d222fa9c1f1ba1f2aa122",
1151
+ "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1152
+ },
1153
+ ]
1154
+ `)
1155
+ })
1156
+ })
1157
+
1158
+ describe('renounceTokenRole', async () => {
1159
+ test('default', async () => {
1160
+ const { token: address } = await actions.token.createSync(client, {
1161
+ admin: client.account,
1162
+ currency: 'USD',
1163
+ name: 'Test Token 3',
1164
+ symbol: 'TEST3',
1165
+ })
1166
+
1167
+ const { receipt: grantReceipt } = await actions.token.grantRolesSync(
1168
+ client,
1169
+ {
1170
+ token: address,
1171
+ roles: ['issuer'],
1172
+ to: client.account.address,
1173
+ },
1174
+ )
1175
+ expect(grantReceipt.status).toBe('success')
1176
+
1177
+ const { receipt: renounceReceipt, value: renounceValue } =
1178
+ await actions.token.renounceRolesSync(client, {
1179
+ token: address,
1180
+ roles: ['issuer'],
1181
+ })
1182
+
1183
+ expect(renounceReceipt.status).toBe('success')
1184
+ expect(renounceValue).toMatchInlineSnapshot(`
1185
+ [
1186
+ {
1187
+ "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1188
+ "hasRole": false,
1189
+ "role": "0x114e74f6ea3bd819998f78687bfcb11b140da08e9b7d222fa9c1f1ba1f2aa122",
1190
+ "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1191
+ },
1192
+ ]
1193
+ `)
1194
+ })
1195
+ })
1196
+
1197
+ describe.todo('setRoleAdmin')
1198
+
1199
+ describe('watchCreate', () => {
1200
+ test('default', async () => {
1201
+ const receivedTokens: Array<{
1202
+ args: actions.token.watchCreate.Args
1203
+ log: actions.token.watchCreate.Log
1204
+ }> = []
1205
+
1206
+ const unwatch = actions.token.watchCreate(client, {
1207
+ onTokenCreated: (args, log) => {
1208
+ receivedTokens.push({ args, log })
1209
+ },
1210
+ })
1211
+
1212
+ try {
1213
+ await actions.token.createSync(client, {
1214
+ currency: 'USD',
1215
+ name: 'Watch Test Token 1',
1216
+ symbol: 'WATCH1',
1217
+ })
1218
+
1219
+ await actions.token.createSync(client, {
1220
+ currency: 'USD',
1221
+ name: 'Watch Test Token 2',
1222
+ symbol: 'WATCH2',
1223
+ })
1224
+
1225
+ await setTimeout(100)
1226
+
1227
+ expect(receivedTokens).toHaveLength(2)
1228
+
1229
+ expect(receivedTokens.at(0)!.args).toMatchInlineSnapshot(`
1230
+ {
1231
+ "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1232
+ "currency": "USD",
1233
+ "name": "Watch Test Token 1",
1234
+ "symbol": "WATCH1",
1235
+ "token": "0x20C0000000000000000000000000000000000001",
1236
+ "tokenId": 1n,
1237
+ }
1238
+ `)
1239
+ expect(receivedTokens.at(1)!.args).toMatchInlineSnapshot(`
1240
+ {
1241
+ "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1242
+ "currency": "USD",
1243
+ "name": "Watch Test Token 2",
1244
+ "symbol": "WATCH2",
1245
+ "token": "0x20C0000000000000000000000000000000000002",
1246
+ "tokenId": 2n,
1247
+ }
1248
+ `)
1249
+ } finally {
1250
+ // Clean up watcher
1251
+ if (unwatch) unwatch()
1252
+ }
1253
+ })
1254
+
1255
+ test('behavior: filter by tokenId', async () => {
1256
+ // First, create a token to know what ID we're at
1257
+ const { tokenId: firstId } = await actions.token.createSync(client, {
1258
+ currency: 'USD',
1259
+ name: 'Setup Token',
1260
+ symbol: 'SETUP',
1261
+ })
1262
+
1263
+ // We want to watch for the token with ID = firstId + 2
1264
+ const targetTokenId = firstId + 2n
1265
+
1266
+ const receivedTokens: Array<{
1267
+ args: actions.token.watchCreate.Args
1268
+ log: actions.token.watchCreate.Log
1269
+ }> = []
1270
+
1271
+ // Start watching for token creation events only for targetTokenId
1272
+ const unwatch = actions.token.watchCreate(client, {
1273
+ args: {
1274
+ tokenId: targetTokenId,
1275
+ },
1276
+ onTokenCreated: (args, log) => {
1277
+ receivedTokens.push({ args, log })
1278
+ },
1279
+ })
1280
+
1281
+ try {
1282
+ // Create first token (should NOT be captured - ID will be firstId + 1)
1283
+ await actions.token.createSync(client, {
1284
+ currency: 'USD',
1285
+ name: 'Filtered Watch Token 1',
1286
+ symbol: 'FWATCH1',
1287
+ })
1288
+
1289
+ // Create second token (should be captured - ID will be firstId + 2 = targetTokenId)
1290
+ const { tokenId: id2 } = await actions.token.createSync(client, {
1291
+ currency: 'USD',
1292
+ name: 'Filtered Watch Token 2',
1293
+ symbol: 'FWATCH2',
1294
+ })
1295
+
1296
+ // Create third token (should NOT be captured - ID will be firstId + 3)
1297
+ await actions.token.createSync(client, {
1298
+ currency: 'USD',
1299
+ name: 'Filtered Watch Token 3',
1300
+ symbol: 'FWATCH3',
1301
+ })
1302
+
1303
+ await setTimeout(100)
1304
+
1305
+ // Should only receive 1 event (for targetTokenId)
1306
+ expect(receivedTokens).toHaveLength(1)
1307
+
1308
+ expect(receivedTokens.at(0)!.args.tokenId).toBe(targetTokenId)
1309
+ expect(receivedTokens.at(0)!.args.tokenId).toBe(id2)
1310
+ expect(receivedTokens.at(0)!.args).toMatchInlineSnapshot(`
1311
+ {
1312
+ "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1313
+ "currency": "USD",
1314
+ "name": "Filtered Watch Token 2",
1315
+ "symbol": "FWATCH2",
1316
+ "token": "0x20C0000000000000000000000000000000000003",
1317
+ "tokenId": 3n,
1318
+ }
1319
+ `)
1320
+
1321
+ // Verify the received token has the expected tokenId
1322
+ expect(receivedTokens.at(0)!.args.tokenId).toBe(targetTokenId)
1323
+ } finally {
1324
+ if (unwatch) unwatch()
1325
+ }
1326
+ })
1327
+ })
1328
+
1329
+ describe('watchMint', () => {
1330
+ test('default', async () => {
1331
+ // Create a new token for testing
1332
+ const { token: address } = await actions.token.createSync(client, {
1333
+ currency: 'USD',
1334
+ name: 'Mint Watch Token',
1335
+ symbol: 'MINT',
1336
+ })
1337
+
1338
+ // Grant issuer role
1339
+ await actions.token.grantRolesSync(client, {
1340
+ token: address,
1341
+ roles: ['issuer'],
1342
+ to: client.account.address,
1343
+ })
1344
+
1345
+ const receivedMints: Array<{
1346
+ args: actions.token.watchMint.Args
1347
+ log: actions.token.watchMint.Log
1348
+ }> = []
1349
+
1350
+ // Start watching for mint events
1351
+ const unwatch = actions.token.watchMint(client, {
1352
+ token: address,
1353
+ onMint: (args, log) => {
1354
+ receivedMints.push({ args, log })
1355
+ },
1356
+ })
1357
+
1358
+ try {
1359
+ // Mint first batch
1360
+ await actions.token.mintSync(client, {
1361
+ token: address,
1362
+ to: account2.address,
1363
+ amount: parseEther('100'),
1364
+ })
1365
+
1366
+ // Mint second batch
1367
+ await actions.token.mintSync(client, {
1368
+ token: address,
1369
+ to: account3.address,
1370
+ amount: parseEther('50'),
1371
+ })
1372
+
1373
+ await setTimeout(100)
1374
+
1375
+ expect(receivedMints).toHaveLength(2)
1376
+
1377
+ expect(receivedMints.at(0)!.args).toMatchInlineSnapshot(`
1378
+ {
1379
+ "amount": 100000000000000000000n,
1380
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1381
+ }
1382
+ `)
1383
+ expect(receivedMints.at(1)!.args).toMatchInlineSnapshot(`
1384
+ {
1385
+ "amount": 50000000000000000000n,
1386
+ "to": "0x98e503f35D0a019cB0a251aD243a4cCFCF371F46",
1387
+ }
1388
+ `)
1389
+ } finally {
1390
+ if (unwatch) unwatch()
1391
+ }
1392
+ })
1393
+
1394
+ test('behavior: filter by to address', async () => {
1395
+ // Create a new token for testing
1396
+ const { token: address } = await actions.token.createSync(client, {
1397
+ currency: 'USD',
1398
+ name: 'Filtered Mint Token',
1399
+ symbol: 'FMINT',
1400
+ })
1401
+
1402
+ // Grant issuer role
1403
+ await actions.token.grantRolesSync(client, {
1404
+ token: address,
1405
+ roles: ['issuer'],
1406
+ to: client.account.address,
1407
+ })
1408
+
1409
+ const receivedMints: Array<{
1410
+ args: actions.token.watchMint.Args
1411
+ log: actions.token.watchMint.Log
1412
+ }> = []
1413
+
1414
+ // Start watching for mint events only to account2
1415
+ const unwatch = actions.token.watchMint(client, {
1416
+ token: address,
1417
+ args: {
1418
+ to: account2.address,
1419
+ },
1420
+ onMint: (args, log) => {
1421
+ receivedMints.push({ args, log })
1422
+ },
1423
+ })
1424
+
1425
+ try {
1426
+ // Mint to account2 (should be captured)
1427
+ await actions.token.mintSync(client, {
1428
+ token: address,
1429
+ to: account2.address,
1430
+ amount: parseEther('100'),
1431
+ })
1432
+
1433
+ // Mint to account3 (should NOT be captured)
1434
+ await actions.token.mintSync(client, {
1435
+ token: address,
1436
+ to: account3.address,
1437
+ amount: parseEther('50'),
1438
+ })
1439
+
1440
+ // Mint to account2 again (should be captured)
1441
+ await actions.token.mintSync(client, {
1442
+ token: address,
1443
+ to: account2.address,
1444
+ amount: parseEther('75'),
1445
+ })
1446
+
1447
+ await setTimeout(100)
1448
+
1449
+ // Should only receive 2 events (for account2)
1450
+ expect(receivedMints).toHaveLength(2)
1451
+
1452
+ expect(receivedMints.at(0)!.args).toMatchInlineSnapshot(`
1453
+ {
1454
+ "amount": 100000000000000000000n,
1455
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1456
+ }
1457
+ `)
1458
+ expect(receivedMints.at(1)!.args).toMatchInlineSnapshot(`
1459
+ {
1460
+ "amount": 75000000000000000000n,
1461
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1462
+ }
1463
+ `)
1464
+
1465
+ // Verify all received mints are to account2
1466
+ for (const mint of receivedMints) {
1467
+ expect(mint.args.to).toBe(account2.address)
1468
+ }
1469
+ } finally {
1470
+ if (unwatch) unwatch()
1471
+ }
1472
+ })
1473
+ })
1474
+
1475
+ describe('watchApprove', () => {
1476
+ test('default', async () => {
1477
+ // Create a new token for testing
1478
+ const { token: address } = await actions.token.createSync(client, {
1479
+ currency: 'USD',
1480
+ name: 'Approval Watch Token',
1481
+ symbol: 'APPR',
1482
+ })
1483
+
1484
+ const receivedApprovals: Array<{
1485
+ args: actions.token.watchApprove.Args
1486
+ log: actions.token.watchApprove.Log
1487
+ }> = []
1488
+
1489
+ // Start watching for approval events
1490
+ const unwatch = actions.token.watchApprove(client, {
1491
+ token: address,
1492
+ onApproval: (args, log) => {
1493
+ receivedApprovals.push({ args, log })
1494
+ },
1495
+ })
1496
+
1497
+ try {
1498
+ // Approve account2
1499
+ await actions.token.approveSync(client, {
1500
+ token: address,
1501
+ spender: account2.address,
1502
+ amount: parseEther('100'),
1503
+ })
1504
+
1505
+ // Approve account3
1506
+ await actions.token.approveSync(client, {
1507
+ token: address,
1508
+ spender: account3.address,
1509
+ amount: parseEther('50'),
1510
+ })
1511
+
1512
+ await setTimeout(100)
1513
+
1514
+ expect(receivedApprovals).toHaveLength(2)
1515
+
1516
+ expect(receivedApprovals.at(0)!.args).toMatchInlineSnapshot(`
1517
+ {
1518
+ "amount": 100000000000000000000n,
1519
+ "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1520
+ "spender": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1521
+ }
1522
+ `)
1523
+ expect(receivedApprovals.at(1)!.args).toMatchInlineSnapshot(`
1524
+ {
1525
+ "amount": 50000000000000000000n,
1526
+ "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1527
+ "spender": "0x98e503f35D0a019cB0a251aD243a4cCFCF371F46",
1528
+ }
1529
+ `)
1530
+ } finally {
1531
+ if (unwatch) unwatch()
1532
+ }
1533
+ })
1534
+
1535
+ test('behavior: filter by spender address', async () => {
1536
+ // Create a new token for testing
1537
+ const { token: address } = await actions.token.createSync(client, {
1538
+ currency: 'USD',
1539
+ name: 'Filtered Approval Token',
1540
+ symbol: 'FAPPR',
1541
+ })
1542
+
1543
+ const receivedApprovals: Array<{
1544
+ args: actions.token.watchApprove.Args
1545
+ log: actions.token.watchApprove.Log
1546
+ }> = []
1547
+
1548
+ // Start watching for approval events only to account2
1549
+ const unwatch = actions.token.watchApprove(client, {
1550
+ token: address,
1551
+ args: {
1552
+ spender: account2.address,
1553
+ },
1554
+ onApproval: (args, log) => {
1555
+ receivedApprovals.push({ args, log })
1556
+ },
1557
+ })
1558
+
1559
+ try {
1560
+ // Approve account2 (should be captured)
1561
+ await actions.token.approveSync(client, {
1562
+ token: address,
1563
+ spender: account2.address,
1564
+ amount: parseEther('100'),
1565
+ })
1566
+
1567
+ // Approve account3 (should NOT be captured)
1568
+ await actions.token.approveSync(client, {
1569
+ token: address,
1570
+ spender: account3.address,
1571
+ amount: parseEther('50'),
1572
+ })
1573
+
1574
+ // Approve account2 again (should be captured)
1575
+ await actions.token.approveSync(client, {
1576
+ token: address,
1577
+ spender: account2.address,
1578
+ amount: parseEther('75'),
1579
+ })
1580
+
1581
+ await setTimeout(100)
1582
+
1583
+ // Should only receive 2 events (for account2)
1584
+ expect(receivedApprovals).toHaveLength(2)
1585
+
1586
+ expect(receivedApprovals.at(0)!.args).toMatchInlineSnapshot(`
1587
+ {
1588
+ "amount": 100000000000000000000n,
1589
+ "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1590
+ "spender": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1591
+ }
1592
+ `)
1593
+ expect(receivedApprovals.at(1)!.args).toMatchInlineSnapshot(`
1594
+ {
1595
+ "amount": 75000000000000000000n,
1596
+ "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1597
+ "spender": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1598
+ }
1599
+ `)
1600
+
1601
+ // Verify all received approvals are for account2
1602
+ for (const approval of receivedApprovals) {
1603
+ expect(approval.args.spender).toBe(account2.address)
1604
+ }
1605
+ } finally {
1606
+ if (unwatch) unwatch()
1607
+ }
1608
+ })
1609
+ })
1610
+
1611
+ describe('watchBurn', () => {
1612
+ test('default', async () => {
1613
+ // Create a new token for testing
1614
+ const { token: address } = await actions.token.createSync(client, {
1615
+ currency: 'USD',
1616
+ name: 'Burn Watch Token',
1617
+ symbol: 'BURN',
1618
+ })
1619
+
1620
+ // Grant issuer role to mint/burn tokens
1621
+ await actions.token.grantRolesSync(client, {
1622
+ token: address,
1623
+ roles: ['issuer'],
1624
+ to: client.account.address,
1625
+ })
1626
+
1627
+ // Grant issuer role to mint/burn tokens
1628
+ await actions.token.grantRolesSync(client, {
1629
+ token: address,
1630
+ roles: ['issuer'],
1631
+ to: account2.address,
1632
+ })
1633
+
1634
+ // Mint tokens to burn later
1635
+ await actions.token.mintSync(client, {
1636
+ token: address,
1637
+ to: client.account.address,
1638
+ amount: parseEther('200'),
1639
+ })
1640
+
1641
+ await actions.token.mintSync(client, {
1642
+ token: address,
1643
+ to: account2.address,
1644
+ amount: parseEther('100'),
1645
+ })
1646
+
1647
+ const receivedBurns: Array<{
1648
+ args: actions.token.watchBurn.Args
1649
+ log: actions.token.watchBurn.Log
1650
+ }> = []
1651
+
1652
+ // Start watching for burn events
1653
+ const unwatch = actions.token.watchBurn(client, {
1654
+ token: address,
1655
+ onBurn: (args, log) => {
1656
+ receivedBurns.push({ args, log })
1657
+ },
1658
+ })
1659
+
1660
+ try {
1661
+ // Burn first batch
1662
+ await actions.token.burnSync(client, {
1663
+ token: address,
1664
+ amount: parseEther('50'),
1665
+ })
1666
+
1667
+ // Transfer gas to account2
1668
+ await writeContractSync(client, {
1669
+ abi: tip20Abi,
1670
+ address: usdAddress,
1671
+ functionName: 'transfer',
1672
+ args: [account2.address, parseEther('1')],
1673
+ })
1674
+
1675
+ // Burn second batch from account2
1676
+ await actions.token.burnSync(client, {
1677
+ account: account2,
1678
+ token: address,
1679
+ amount: parseEther('25'),
1680
+ })
1681
+
1682
+ await setTimeout(100)
1683
+
1684
+ expect(receivedBurns).toHaveLength(2)
1685
+
1686
+ expect(receivedBurns.at(0)!.args).toMatchInlineSnapshot(`
1687
+ {
1688
+ "amount": 50000000000000000000n,
1689
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1690
+ }
1691
+ `)
1692
+ expect(receivedBurns.at(1)!.args).toMatchInlineSnapshot(`
1693
+ {
1694
+ "amount": 25000000000000000000n,
1695
+ "from": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
1696
+ }
1697
+ `)
1698
+ } finally {
1699
+ if (unwatch) unwatch()
1700
+ }
1701
+ })
1702
+
1703
+ test('behavior: filter by from address', async () => {
1704
+ // Create a new token for testing
1705
+ const { token: address } = await actions.token.createSync(client, {
1706
+ currency: 'USD',
1707
+ name: 'Filtered Burn Token',
1708
+ symbol: 'FBURN',
1709
+ })
1710
+
1711
+ // Grant issuer role
1712
+ await actions.token.grantRolesSync(client, {
1713
+ token: address,
1714
+ roles: ['issuer'],
1715
+ to: client.account.address,
1716
+ })
1717
+
1718
+ // Grant issuer role
1719
+ await actions.token.grantRolesSync(client, {
1720
+ token: address,
1721
+ roles: ['issuer'],
1722
+ to: account2.address,
1723
+ })
1724
+
1725
+ // Mint tokens to multiple accounts
1726
+ await actions.token.mintSync(client, {
1727
+ token: address,
1728
+ to: client.account.address,
1729
+ amount: parseEther('200'),
1730
+ })
1731
+
1732
+ await actions.token.mintSync(client, {
1733
+ token: address,
1734
+ to: account2.address,
1735
+ amount: parseEther('200'),
1736
+ })
1737
+
1738
+ const receivedBurns: Array<{
1739
+ args: actions.token.watchBurn.Args
1740
+ log: actions.token.watchBurn.Log
1741
+ }> = []
1742
+
1743
+ // Start watching for burn events only from client.account
1744
+ const unwatch = actions.token.watchBurn(client, {
1745
+ token: address,
1746
+ args: {
1747
+ from: client.account.address,
1748
+ },
1749
+ onBurn: (args, log) => {
1750
+ receivedBurns.push({ args, log })
1751
+ },
1752
+ })
1753
+
1754
+ try {
1755
+ // Burn from client.account (should be captured)
1756
+ await actions.token.burnSync(client, {
1757
+ token: address,
1758
+ amount: parseEther('50'),
1759
+ })
1760
+
1761
+ // Transfer gas to account2
1762
+ await writeContractSync(client, {
1763
+ abi: tip20Abi,
1764
+ address: usdAddress,
1765
+ functionName: 'transfer',
1766
+ args: [account2.address, parseEther('1')],
1767
+ })
1768
+
1769
+ // Burn from account2 (should NOT be captured)
1770
+ await actions.token.burnSync(client, {
1771
+ account: account2,
1772
+ token: address,
1773
+ amount: parseEther('25'),
1774
+ })
1775
+
1776
+ // Burn from client.account again (should be captured)
1777
+ await actions.token.burnSync(client, {
1778
+ token: address,
1779
+ amount: parseEther('75'),
1780
+ })
1781
+
1782
+ await setTimeout(100)
1783
+
1784
+ // Should only receive 2 events (from client.account)
1785
+ expect(receivedBurns).toHaveLength(2)
1786
+
1787
+ expect(receivedBurns.at(0)!.args).toMatchInlineSnapshot(`
1788
+ {
1789
+ "amount": 50000000000000000000n,
1790
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1791
+ }
1792
+ `)
1793
+ expect(receivedBurns.at(1)!.args).toMatchInlineSnapshot(`
1794
+ {
1795
+ "amount": 75000000000000000000n,
1796
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1797
+ }
1798
+ `)
1799
+
1800
+ // Verify all received burns are from client.account
1801
+ for (const burn of receivedBurns) {
1802
+ expect(burn.args.from).toBe(client.account.address)
1803
+ }
1804
+ } finally {
1805
+ if (unwatch) unwatch()
1806
+ }
1807
+ })
1808
+ })
1809
+
1810
+ describe('watchAdminRole', () => {
1811
+ test('default', async () => {
1812
+ // Create a new token for testing
1813
+ const { token: address } = await actions.token.createSync(client, {
1814
+ currency: 'USD',
1815
+ name: 'Admin Role Watch Token',
1816
+ symbol: 'ADMIN',
1817
+ })
1818
+
1819
+ const receivedAdminUpdates: Array<{
1820
+ args: actions.token.watchAdminRole.Args
1821
+ log: actions.token.watchAdminRole.Log
1822
+ }> = []
1823
+
1824
+ // Start watching for role admin updates
1825
+ const unwatch = actions.token.watchAdminRole(client, {
1826
+ token: address,
1827
+ onRoleAdminUpdated: (args, log) => {
1828
+ receivedAdminUpdates.push({ args, log })
1829
+ },
1830
+ })
1831
+
1832
+ try {
1833
+ // Set role admin for issuer role
1834
+ const { receipt: setRoleAdmin1Receipt, ...setRoleAdmin1Result } =
1835
+ await actions.token.setRoleAdminSync(client, {
1836
+ token: address,
1837
+ role: 'issuer',
1838
+ adminRole: 'pause',
1839
+ })
1840
+ expect(setRoleAdmin1Receipt).toBeDefined()
1841
+ expect(setRoleAdmin1Result).toMatchInlineSnapshot(`
1842
+ {
1843
+ "newAdminRole": "0x139c2898040ef16910dc9f44dc697df79363da767d8bc92f2e310312b816e46d",
1844
+ "role": "0x114e74f6ea3bd819998f78687bfcb11b140da08e9b7d222fa9c1f1ba1f2aa122",
1845
+ "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1846
+ }
1847
+ `)
1848
+
1849
+ // Set role admin for pause role
1850
+ await actions.token.setRoleAdminSync(client, {
1851
+ token: address,
1852
+ role: 'pause',
1853
+ adminRole: 'unpause',
1854
+ })
1855
+
1856
+ await setTimeout(100)
1857
+
1858
+ expect(receivedAdminUpdates).toHaveLength(2)
1859
+
1860
+ expect(receivedAdminUpdates.at(0)!.args.sender).toBe(
1861
+ client.account.address,
1862
+ )
1863
+ expect(receivedAdminUpdates.at(1)!.args.sender).toBe(
1864
+ client.account.address,
1865
+ )
1866
+ } finally {
1867
+ if (unwatch) unwatch()
1868
+ }
1869
+ })
1870
+ })
1871
+
1872
+ describe('watchRole', () => {
1873
+ test('default', async () => {
1874
+ // Create a new token for testing
1875
+ const { token: address } = await actions.token.createSync(client, {
1876
+ currency: 'USD',
1877
+ name: 'Role Watch Token',
1878
+ symbol: 'ROLE',
1879
+ })
1880
+
1881
+ const receivedRoleUpdates: Array<{
1882
+ args: actions.token.watchRole.Args
1883
+ log: actions.token.watchRole.Log
1884
+ }> = []
1885
+
1886
+ // Start watching for role membership updates
1887
+ const unwatch = actions.token.watchRole(client, {
1888
+ token: address,
1889
+ onRoleUpdated: (args, log) => {
1890
+ receivedRoleUpdates.push({ args, log })
1891
+ },
1892
+ })
1893
+
1894
+ try {
1895
+ // Grant issuer role to account2
1896
+ await actions.token.grantRolesSync(client, {
1897
+ token: address,
1898
+ roles: ['issuer'],
1899
+ to: account2.address,
1900
+ })
1901
+
1902
+ // Grant pause role to account3
1903
+ await actions.token.grantRolesSync(client, {
1904
+ token: address,
1905
+ roles: ['pause'],
1906
+ to: account3.address,
1907
+ })
1908
+
1909
+ // Revoke issuer role from account2
1910
+ await actions.token.revokeRolesSync(client, {
1911
+ token: address,
1912
+ roles: ['issuer'],
1913
+ from: account2.address,
1914
+ })
1915
+
1916
+ await setTimeout(100)
1917
+
1918
+ expect(receivedRoleUpdates).toHaveLength(3)
1919
+
1920
+ // First event: grant issuer
1921
+ expect(receivedRoleUpdates.at(0)!.args.type).toBe('granted')
1922
+ expect(receivedRoleUpdates.at(0)!.args.account).toBe(account2.address)
1923
+ expect(receivedRoleUpdates.at(0)!.args.hasRole).toBe(true)
1924
+
1925
+ // Second event: grant pause
1926
+ expect(receivedRoleUpdates.at(1)!.args.type).toBe('granted')
1927
+ expect(receivedRoleUpdates.at(1)!.args.account).toBe(account3.address)
1928
+ expect(receivedRoleUpdates.at(1)!.args.hasRole).toBe(true)
1929
+
1930
+ // Third event: revoke issuer
1931
+ expect(receivedRoleUpdates.at(2)!.args.type).toBe('revoked')
1932
+ expect(receivedRoleUpdates.at(2)!.args.account).toBe(account2.address)
1933
+ expect(receivedRoleUpdates.at(2)!.args.hasRole).toBe(false)
1934
+ } finally {
1935
+ if (unwatch) unwatch()
1936
+ }
1937
+ })
1938
+
1939
+ test('behavior: filter by account address', async () => {
1940
+ // Create a new token for testing
1941
+ const { token: address } = await actions.token.createSync(client, {
1942
+ currency: 'USD',
1943
+ name: 'Filtered Role Token',
1944
+ symbol: 'FROLE',
1945
+ })
1946
+
1947
+ const receivedRoleUpdates: Array<{
1948
+ args: actions.token.watchRole.Args
1949
+ log: actions.token.watchRole.Log
1950
+ }> = []
1951
+
1952
+ // Start watching for role updates only for account2
1953
+ const unwatch = actions.token.watchRole(client, {
1954
+ token: address,
1955
+ args: {
1956
+ account: account2.address,
1957
+ },
1958
+ onRoleUpdated: (args, log) => {
1959
+ receivedRoleUpdates.push({ args, log })
1960
+ },
1961
+ })
1962
+
1963
+ try {
1964
+ // Grant issuer role to account2 (should be captured)
1965
+ await actions.token.grantRolesSync(client, {
1966
+ token: address,
1967
+ roles: ['issuer'],
1968
+ to: account2.address,
1969
+ })
1970
+
1971
+ // Grant pause role to account3 (should NOT be captured)
1972
+ await actions.token.grantRolesSync(client, {
1973
+ token: address,
1974
+ roles: ['pause'],
1975
+ to: account3.address,
1976
+ })
1977
+
1978
+ // Revoke issuer role from account2 (should be captured)
1979
+ await actions.token.revokeRolesSync(client, {
1980
+ token: address,
1981
+ roles: ['issuer'],
1982
+ from: account2.address,
1983
+ })
1984
+
1985
+ await setTimeout(100)
1986
+
1987
+ // Should only receive 2 events (for account2)
1988
+ expect(receivedRoleUpdates).toHaveLength(2)
1989
+
1990
+ // First: grant to account2
1991
+ expect(receivedRoleUpdates.at(0)!.args.type).toBe('granted')
1992
+ expect(receivedRoleUpdates.at(0)!.args.account).toBe(account2.address)
1993
+ expect(receivedRoleUpdates.at(0)!.args.hasRole).toBe(true)
1994
+
1995
+ // Second: revoke from account2
1996
+ expect(receivedRoleUpdates.at(1)!.args.type).toBe('revoked')
1997
+ expect(receivedRoleUpdates.at(1)!.args.account).toBe(account2.address)
1998
+ expect(receivedRoleUpdates.at(1)!.args.hasRole).toBe(false)
1999
+
2000
+ // Verify all received events are for account2
2001
+ for (const update of receivedRoleUpdates) {
2002
+ expect(update.args.account).toBe(account2.address)
2003
+ }
2004
+ } finally {
2005
+ if (unwatch) unwatch()
2006
+ }
2007
+ })
2008
+ })
2009
+
2010
+ describe('watchTransfer', () => {
2011
+ test('default', async () => {
2012
+ // Create a new token for testing
2013
+ const { token: address } = await actions.token.createSync(client, {
2014
+ currency: 'USD',
2015
+ name: 'Transfer Watch Token',
2016
+ symbol: 'XFER',
2017
+ })
2018
+
2019
+ // Grant issuer role to mint tokens
2020
+ await actions.token.grantRolesSync(client, {
2021
+ token: address,
2022
+ roles: ['issuer'],
2023
+ to: client.account.address,
2024
+ })
2025
+
2026
+ // Mint tokens to transfer
2027
+ await actions.token.mintSync(client, {
2028
+ token: address,
2029
+ to: client.account.address,
2030
+ amount: parseEther('500'),
2031
+ })
2032
+
2033
+ const receivedTransfers: Array<{
2034
+ args: actions.token.watchTransfer.Args
2035
+ log: actions.token.watchTransfer.Log
2036
+ }> = []
2037
+
2038
+ // Start watching for transfer events
2039
+ const unwatch = actions.token.watchTransfer(client, {
2040
+ token: address,
2041
+ onTransfer: (args, log) => {
2042
+ receivedTransfers.push({ args, log })
2043
+ },
2044
+ })
2045
+
2046
+ try {
2047
+ // Transfer to account2
2048
+ await actions.token.transferSync(client, {
2049
+ token: address,
2050
+ to: account2.address,
2051
+ amount: parseEther('100'),
2052
+ })
2053
+
2054
+ // Transfer to account3
2055
+ await actions.token.transferSync(client, {
2056
+ token: address,
2057
+ to: account3.address,
2058
+ amount: parseEther('50'),
2059
+ })
2060
+
2061
+ await setTimeout(200)
2062
+
2063
+ expect(receivedTransfers).toHaveLength(2)
2064
+
2065
+ expect(receivedTransfers.at(0)!.args).toMatchInlineSnapshot(`
2066
+ {
2067
+ "amount": 100000000000000000000n,
2068
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
2069
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
2070
+ }
2071
+ `)
2072
+ expect(receivedTransfers.at(1)!.args).toMatchInlineSnapshot(`
2073
+ {
2074
+ "amount": 50000000000000000000n,
2075
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
2076
+ "to": "0x98e503f35D0a019cB0a251aD243a4cCFCF371F46",
2077
+ }
2078
+ `)
2079
+ } finally {
2080
+ if (unwatch) unwatch()
2081
+ }
2082
+ })
2083
+
2084
+ test('behavior: filter by to address', async () => {
2085
+ // Create a new token for testing
2086
+ const { token: address } = await actions.token.createSync(client, {
2087
+ currency: 'USD',
2088
+ name: 'Filtered Transfer Token',
2089
+ symbol: 'FXFER',
2090
+ })
2091
+
2092
+ // Grant issuer role
2093
+ await actions.token.grantRolesSync(client, {
2094
+ token: address,
2095
+ roles: ['issuer'],
2096
+ to: client.account.address,
2097
+ })
2098
+
2099
+ // Mint tokens
2100
+ await actions.token.mintSync(client, {
2101
+ token: address,
2102
+ to: client.account.address,
2103
+ amount: parseEther('500'),
2104
+ })
2105
+
2106
+ const receivedTransfers: Array<{
2107
+ args: actions.token.watchTransfer.Args
2108
+ log: actions.token.watchTransfer.Log
2109
+ }> = []
2110
+
2111
+ // Start watching for transfer events only to account2
2112
+ const unwatch = actions.token.watchTransfer(client, {
2113
+ token: address,
2114
+ args: {
2115
+ to: account2.address,
2116
+ },
2117
+ onTransfer: (args, log) => {
2118
+ receivedTransfers.push({ args, log })
2119
+ },
2120
+ })
2121
+
2122
+ try {
2123
+ // Transfer to account2 (should be captured)
2124
+ await actions.token.transferSync(client, {
2125
+ token: address,
2126
+ to: account2.address,
2127
+ amount: parseEther('100'),
2128
+ })
2129
+
2130
+ // Transfer to account3 (should NOT be captured)
2131
+ await actions.token.transferSync(client, {
2132
+ token: address,
2133
+ to: account3.address,
2134
+ amount: parseEther('50'),
2135
+ })
2136
+
2137
+ // Transfer to account2 again (should be captured)
2138
+ await actions.token.transferSync(client, {
2139
+ token: address,
2140
+ to: account2.address,
2141
+ amount: parseEther('75'),
2142
+ })
2143
+
2144
+ await setTimeout(100)
2145
+
2146
+ // Should only receive 2 events (to account2)
2147
+ expect(receivedTransfers).toHaveLength(2)
2148
+
2149
+ expect(receivedTransfers.at(0)!.args).toMatchInlineSnapshot(`
2150
+ {
2151
+ "amount": 100000000000000000000n,
2152
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
2153
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
2154
+ }
2155
+ `)
2156
+ expect(receivedTransfers.at(1)!.args).toMatchInlineSnapshot(`
2157
+ {
2158
+ "amount": 75000000000000000000n,
2159
+ "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
2160
+ "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650",
2161
+ }
2162
+ `)
2163
+
2164
+ // Verify all received transfers are to account2
2165
+ for (const transfer of receivedTransfers) {
2166
+ expect(transfer.args.to).toBe(account2.address)
2167
+ }
2168
+ } finally {
2169
+ if (unwatch) unwatch()
2170
+ }
2171
+ })
2172
+ })