@ocap/state 1.28.8 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/esm/_virtual/rolldown_runtime.mjs +18 -0
  2. package/esm/contexts/state.d.mts +15 -0
  3. package/esm/contexts/state.mjs +17 -0
  4. package/esm/index.d.mts +20 -0
  5. package/esm/index.mjs +47 -0
  6. package/esm/states/account.d.mts +18 -0
  7. package/esm/states/account.mjs +91 -0
  8. package/esm/states/asset.d.mts +14 -0
  9. package/esm/states/asset.mjs +80 -0
  10. package/esm/states/blacklist.d.mts +36 -0
  11. package/esm/states/blacklist.mjs +71 -0
  12. package/esm/states/chain.d.mts +30 -0
  13. package/esm/states/chain.mjs +52 -0
  14. package/esm/states/delegation.d.mts +11 -0
  15. package/esm/states/delegation.mjs +42 -0
  16. package/esm/states/evidence.d.mts +12 -0
  17. package/esm/states/evidence.mjs +35 -0
  18. package/esm/states/factory.d.mts +12 -0
  19. package/esm/states/factory.mjs +76 -0
  20. package/esm/states/rollup-block.d.mts +13 -0
  21. package/esm/states/rollup-block.mjs +75 -0
  22. package/esm/states/rollup.d.mts +18 -0
  23. package/esm/states/rollup.mjs +215 -0
  24. package/esm/states/stake.d.mts +13 -0
  25. package/esm/states/stake.mjs +89 -0
  26. package/esm/states/token-factory.d.mts +13 -0
  27. package/esm/states/token-factory.mjs +76 -0
  28. package/esm/states/token.d.mts +14 -0
  29. package/esm/states/token.mjs +109 -0
  30. package/esm/states/tx.d.mts +233 -0
  31. package/esm/states/tx.mjs +867 -0
  32. package/esm/util.d.mts +6 -0
  33. package/esm/util.mjs +18 -0
  34. package/lib/_virtual/rolldown_runtime.cjs +43 -0
  35. package/lib/contexts/state.cjs +19 -0
  36. package/lib/contexts/state.d.cts +15 -0
  37. package/lib/index.cjs +121 -0
  38. package/lib/index.d.cts +20 -0
  39. package/lib/states/account.cjs +106 -0
  40. package/lib/states/account.d.cts +18 -0
  41. package/lib/states/asset.cjs +91 -0
  42. package/lib/states/asset.d.cts +14 -0
  43. package/lib/states/blacklist.cjs +74 -0
  44. package/lib/states/blacklist.d.cts +36 -0
  45. package/lib/states/chain.cjs +62 -0
  46. package/lib/states/chain.d.cts +30 -0
  47. package/lib/states/delegation.cjs +50 -0
  48. package/lib/states/delegation.d.cts +11 -0
  49. package/lib/states/evidence.cjs +44 -0
  50. package/lib/states/evidence.d.cts +12 -0
  51. package/lib/states/factory.cjs +85 -0
  52. package/lib/states/factory.d.cts +12 -0
  53. package/lib/states/rollup-block.cjs +85 -0
  54. package/lib/states/rollup-block.d.cts +13 -0
  55. package/lib/states/rollup.cjs +230 -0
  56. package/lib/states/rollup.d.cts +18 -0
  57. package/lib/states/stake.cjs +99 -0
  58. package/lib/states/stake.d.cts +13 -0
  59. package/lib/states/token-factory.cjs +86 -0
  60. package/lib/states/token-factory.d.cts +13 -0
  61. package/lib/states/token.cjs +121 -0
  62. package/lib/states/token.d.cts +14 -0
  63. package/lib/states/tx.cjs +889 -0
  64. package/lib/states/tx.d.cts +233 -0
  65. package/lib/util.cjs +19 -0
  66. package/lib/util.d.cts +6 -0
  67. package/package.json +46 -14
  68. package/lib/contexts/state.js +0 -19
  69. package/lib/index.js +0 -63
  70. package/lib/states/account.js +0 -95
  71. package/lib/states/asset.js +0 -91
  72. package/lib/states/blacklist.js +0 -103
  73. package/lib/states/chain.js +0 -49
  74. package/lib/states/delegation.js +0 -46
  75. package/lib/states/evidence.js +0 -35
  76. package/lib/states/factory.js +0 -92
  77. package/lib/states/rollup-block.js +0 -84
  78. package/lib/states/rollup.js +0 -297
  79. package/lib/states/stake.js +0 -83
  80. package/lib/states/token-factory.js +0 -74
  81. package/lib/states/token.js +0 -124
  82. package/lib/states/tx.js +0 -896
  83. package/lib/util.js +0 -28
package/lib/states/tx.js DELETED
@@ -1,896 +0,0 @@
1
- const get = require('lodash/get');
2
- const pick = require('lodash/pick');
3
- const merge = require('lodash/merge');
4
- const groupBy = require('lodash/groupBy');
5
- const upperFirst = require('lodash/upperFirst');
6
- const camelCase = require('lodash/camelCase');
7
- const { fromTypeUrl, formatMessage, decodeAny } = require('@ocap/message');
8
- const { toBase64, BN, isBN, fromTokenToUnit } = require('@ocap/util');
9
- const { getListField } = require('@ocap/util/lib/get-list-field');
10
- const { CustomError: Error } = require('@ocap/util/lib/error');
11
- const { getRelatedAddresses } = require('@ocap/util/lib/get-related-addr');
12
- const { toTypeInfo, types } = require('@arcblock/did');
13
-
14
- const ZERO = new BN(0);
15
- const { RoleType } = types;
16
-
17
- const FORGE_TOKEN_HOLDER = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz';
18
- const FORGE_FEE_RECEIVER = 'z1EJUBdbPYqMiWHfRZvSAvbYeWcEHB19Xyfc';
19
- const QLDB_MIGRATION_TIME = new Date('2021-05-03T12:46:56.245Z');
20
-
21
- function eachReceipts(receipts, callback) {
22
- const result = (receipts || []).map((receipt) => {
23
- return (receipt.changes || []).map((change) => callback(receipt.address, change));
24
- });
25
-
26
- return result.flat();
27
- }
28
-
29
- function groupReceiptTokenChanges(receipts) {
30
- /**
31
- * Map of address to token changes
32
- * @type {Record<string, {[tokenAddress: string]: string}>}
33
- */
34
- const tokenChanges = {};
35
- // get token changes for each account by receipts
36
- eachReceipts(receipts || [], (address, change) => {
37
- if (toTypeInfo(change.target).role !== RoleType.ROLE_TOKEN) return;
38
- if (change.value === '0') return;
39
- if (!tokenChanges[address]) {
40
- tokenChanges[address] = {};
41
- }
42
- const tokenChange = tokenChanges[address];
43
- tokenChange[change.target] = new BN(tokenChange[change.target] || 0).add(new BN(change.value)).toString();
44
- });
45
-
46
- return tokenChanges;
47
- }
48
-
49
- // Receiver = Whose asset have net gain
50
- const getTxReceiver = ({ tx, itx, typeUrl }) => {
51
- switch (typeUrl) {
52
- case 'TransferTx':
53
- case 'ExchangeTx':
54
- case 'TransferV2Tx':
55
- case 'ExchangeV2Tx':
56
- return itx.to;
57
- case 'MintAssetTx':
58
- return itx.owner;
59
- case 'CreateTokenTx':
60
- case 'CreateAssetTx':
61
- case 'AcquireAssetV2Tx':
62
- return tx.delegator || tx.from;
63
- case 'AcquireAssetV3Tx':
64
- return itx.owner;
65
- case 'SetupSwapTx':
66
- return itx.receiver;
67
- case 'StakeTx':
68
- return itx.address;
69
- default:
70
- return '';
71
- }
72
- };
73
-
74
- // Sender = Whose asset have net lose
75
- const getTxSender = ({ tx, typeUrl }) => {
76
- switch (typeUrl) {
77
- case 'TransferTx':
78
- case 'TransferV2Tx':
79
- case 'ExchangeTx':
80
- case 'ExchangeV2Tx':
81
- case 'AcquireAssetV2Tx':
82
- case 'SetupSwapTx':
83
- case 'WithdrawTokenTx':
84
- case 'StakeTx':
85
- case 'ReturnStakeTx':
86
- case 'SlashStakeTx':
87
- return tx.from;
88
- default:
89
- return '';
90
- }
91
- };
92
-
93
- const getReceiptChange = (receipts, { address, action }) => {
94
- const receipt = receipts.filter((x) => !address || x.address === address);
95
- const changes = receipt.flatMap((x) => x.changes.filter((c) => !action || c.action === action));
96
- return changes;
97
- };
98
-
99
- const getTransferReceipts = (tx) => {
100
- const senderReceipt = { address: tx.from, changes: [] };
101
- const receiverReceipt = { address: tx.itxJson.to, changes: [] };
102
-
103
- if (new BN(tx.itxJson.value).gt(ZERO)) {
104
- senderReceipt.changes.push({ target: '', action: 'transfer', value: `-${tx.itxJson.value}` });
105
- receiverReceipt.changes.push({ target: '', action: 'transfer', value: tx.itxJson.value });
106
- }
107
- if (Array.isArray(tx.itxJson.tokens)) {
108
- tx.itxJson.tokens.forEach((x) => {
109
- senderReceipt.changes.push({ target: x.address, action: 'transfer', value: `-${x.value}` });
110
- receiverReceipt.changes.push({ target: x.address, action: 'transfer', value: x.value });
111
- });
112
- }
113
- if (Array.isArray(tx.itxJson.assets)) {
114
- tx.itxJson.assets.forEach((x) => {
115
- senderReceipt.changes.push({ target: x, action: 'transfer', value: '-1' });
116
- receiverReceipt.changes.push({ target: x, action: 'transfer', value: '1' });
117
- });
118
- }
119
-
120
- return [senderReceipt, receiverReceipt];
121
- };
122
-
123
- const getReceiptsFromTxInput = (inputs, symbol, action = 'transfer') => {
124
- const receipts = [];
125
- inputs.forEach((x) => {
126
- const receipt = { address: x.owner, changes: [] };
127
- getListField(x, 'tokens').forEach(({ address, value }) => {
128
- receipt.changes.push({ target: address, action, value: `${symbol}${value}` });
129
- });
130
- getListField(x, 'assets').forEach((address) =>
131
- receipt.changes.push({ target: address, action, value: `${symbol}1` })
132
- );
133
- receipts.push(receipt);
134
- });
135
-
136
- return receipts;
137
- };
138
-
139
- // TODO: this logic does not support assets yet
140
- const getReceiptsFromContext = (ctx) => {
141
- if (Array.isArray(ctx.updatedAccounts)) {
142
- const grouped = groupBy(
143
- ctx.updatedAccounts.filter((x) => x.address && x.delta),
144
- 'address'
145
- );
146
- return Object.keys(grouped).map((k) => ({
147
- address: k,
148
- changes: grouped[k].map((x) => ({ target: x.token, action: x.action || 'transfer', value: x.delta })),
149
- }));
150
- }
151
-
152
- return [];
153
- };
154
-
155
- /**
156
- * Add receipts for tx that have gasPaid but no receipts
157
- * This is used to handle legacy data that gas receipts were not properly recorded
158
- */
159
- const getReceiptsFromGasPaid = (tx, { config, typeUrl }) => {
160
- const gasReceipt = getReceiptChange(tx.receipts || [], { action: 'gas' });
161
- const gasPaid = new BN(tx.gasPaid || 0);
162
-
163
- // For new transactions being created, gas receipts are usually attached by getReceiptsFromContext before,
164
- // so we don't add duplicate gas receipts here
165
- if (gasReceipt.length) return [];
166
- if (gasPaid.lte(ZERO)) return [];
167
-
168
- const gasPayerMap = {
169
- AccountMigrateTx: tx.itxJson?.address,
170
- };
171
- const gasPayer = gasPayerMap[typeUrl] || tx.from;
172
- // Cannot determine who received the gas fee in legacy data
173
- const gasReceiver = config?.vaults?.txGas?.[0] || FORGE_FEE_RECEIVER;
174
-
175
- if (!gasPayer) return [];
176
-
177
- return [
178
- { address: gasPayer, changes: [{ target: '', action: 'gas', value: gasPaid.neg().toString() }] },
179
- { address: gasReceiver, changes: [{ target: '', action: 'gas', value: gasPaid.toString() }] },
180
- ];
181
- };
182
-
183
- const getTransferV3Receipts = (tx) => [
184
- ...getReceiptsFromTxInput(tx.itxJson.inputs, '-', 'transfer'),
185
- ...getReceiptsFromTxInput(tx.itxJson.outputs, '', 'transfer'),
186
- ];
187
-
188
- const getExchangeReceipts = (tx, ctx) => {
189
- const { senderState, receiverState } = ctx;
190
- const { sender, receiver } = tx.itxJson;
191
- const senderReceipt = { address: senderState ? senderState.address : tx.from, changes: [] };
192
- const receiverReceipt = { address: receiverState ? receiverState.address : tx.itxJson.to, changes: [] };
193
-
194
- if (new BN(sender.value).gt(ZERO)) {
195
- senderReceipt.changes.push({ target: '', action: 'transfer', value: `-${sender.value}` });
196
- receiverReceipt.changes.push({ target: '', action: 'transfer', value: sender.value });
197
- }
198
- if (new BN(receiver.value).gt(ZERO)) {
199
- receiverReceipt.changes.push({ target: '', action: 'transfer', value: `-${receiver.value}` });
200
- senderReceipt.changes.push({ target: '', action: 'transfer', value: receiver.value });
201
- }
202
-
203
- if (Array.isArray(sender.tokens)) {
204
- sender.tokens.forEach((x) => {
205
- senderReceipt.changes.push({ target: x.address, action: 'transfer', value: `-${x.value}` });
206
- receiverReceipt.changes.push({ target: x.address, action: 'transfer', value: x.value });
207
- });
208
- }
209
- if (Array.isArray(receiver.tokens)) {
210
- receiver.tokens.forEach((x) => {
211
- receiverReceipt.changes.push({ target: x.address, action: 'transfer', value: `-${x.value}` });
212
- senderReceipt.changes.push({ target: x.address, action: 'transfer', value: x.value });
213
- });
214
- }
215
-
216
- if (Array.isArray(sender.assets)) {
217
- sender.assets.forEach((x) => {
218
- senderReceipt.changes.push({ target: x, action: 'transfer', value: '-1' });
219
- receiverReceipt.changes.push({ target: x, action: 'transfer', value: '1' });
220
- });
221
- }
222
- if (Array.isArray(receiver.assets)) {
223
- receiver.assets.forEach((x) => {
224
- receiverReceipt.changes.push({ target: x, action: 'transfer', value: '-1' });
225
- senderReceipt.changes.push({ target: x, action: 'transfer', value: '1' });
226
- });
227
- }
228
-
229
- return [senderReceipt, receiverReceipt];
230
- };
231
-
232
- const getCreateAssetReceipts = (tx) => {
233
- const owner = tx.delegator || tx.from;
234
- const ownerReceipt = { address: owner, changes: [{ target: tx.itxJson.address, action: 'create', value: '1' }] };
235
- return [ownerReceipt];
236
- };
237
-
238
- const getMintAssetReceipts = (tx, ctx) => {
239
- const { assets = [], address, owner } = tx.itxJson;
240
- const ownerReceipt = {
241
- address: ctx.ownerAddress || owner,
242
- changes: [{ target: address, action: 'mint', value: '1' }],
243
- };
244
- assets.map((x) => ownerReceipt.changes.push({ target: x, action: 'consume', value: '-1' }));
245
- return [ownerReceipt];
246
- };
247
-
248
- const getAcquireAssetReceipts = (tx, ctx) => {
249
- const { assets = [], address } = tx.itxJson;
250
- const delegator = tx.delegator || tx.from;
251
-
252
- const ownerReceipt = { address: delegator, changes: [{ target: address, action: 'mint', value: '1' }] }; // who owns the minted asset
253
- const senderReceipt = { address: tx.from, changes: [] }; // who paid for this tx
254
-
255
- assets.map((x) => senderReceipt.changes.push({ target: x, action: 'consume', value: '-1' }));
256
-
257
- if (ctx.factoryState) {
258
- const { value, tokens } = ctx.factoryState.input;
259
- if (new BN(value).gt(ZERO)) {
260
- senderReceipt.changes.push({ target: '', action: 'consume', value: `-${value}` });
261
- }
262
- tokens.map((x) => senderReceipt.changes.push({ target: x.address, action: 'consume', value: `-${x.value}` }));
263
- }
264
-
265
- let mergedReceipts = [ownerReceipt, senderReceipt];
266
- if (ownerReceipt.address === senderReceipt.address) {
267
- senderReceipt.changes = [...ownerReceipt.changes, ...senderReceipt.changes];
268
- mergedReceipts = [senderReceipt];
269
- }
270
-
271
- return [...mergedReceipts];
272
- };
273
-
274
- const getAcquireAssetV3Receipts = (tx) => {
275
- const { inputs, owner, address } = tx.itxJson;
276
- const ownerReceipt = { address: owner, changes: [{ target: address, action: 'mint', value: '1' }] };
277
- return [ownerReceipt, ...getReceiptsFromTxInput(inputs, '-', 'consume')];
278
- };
279
-
280
- const getStakeReceipts = (tx) => {
281
- const { inputs, address } = tx.itxJson;
282
-
283
- // aggregated stake changes
284
- const tokens = {};
285
- const assets = [];
286
- inputs.forEach((input) => {
287
- input.tokens.forEach((x) => {
288
- if (typeof tokens[x.address] === 'undefined') {
289
- tokens[x.address] = new BN(0);
290
- }
291
- tokens[x.address] = tokens[x.address].add(new BN(x.value));
292
- });
293
- assets.push(...input.assets);
294
- });
295
-
296
- const stakeReceipt = { address, changes: [] };
297
- assets.forEach((x) => stakeReceipt.changes.push({ target: x, action: 'stake', value: '1' }));
298
- Object.keys(tokens).forEach((x) =>
299
- stakeReceipt.changes.push({ target: x, action: 'stake', value: tokens[x].toString(10) })
300
- );
301
-
302
- return [stakeReceipt, ...getReceiptsFromTxInput(inputs, '-', 'stake')];
303
- };
304
-
305
- const getClaimStakeReceipts = (tx, ctx, action) => {
306
- const { address } = tx.itxJson;
307
- const { outputs } = ctx;
308
-
309
- // aggregated stake changes
310
- const tokens = {};
311
- const assets = [];
312
- outputs.forEach((output) => {
313
- getListField(output, 'tokens').forEach((x) => {
314
- if (typeof tokens[x.address] === 'undefined') {
315
- tokens[x.address] = new BN(0);
316
- }
317
- tokens[x.address] = tokens[x.address].add(new BN(x.value));
318
- });
319
- assets.push(...getListField(output, 'assets'));
320
- });
321
-
322
- const stakeReceipt = { address, changes: [] };
323
- assets.forEach((x) => stakeReceipt.changes.push({ target: x, action, value: '-1' }));
324
- Object.keys(tokens).forEach((x) =>
325
- stakeReceipt.changes.push({ target: x, action, value: `-${tokens[x].toString(10)}` })
326
- );
327
-
328
- return [stakeReceipt, ...getReceiptsFromTxInput(outputs, '', action)];
329
- };
330
-
331
- const getCreateTokenReceipts = (tx) => {
332
- const { initialSupply, address } = tx.itxJson;
333
- const balance = new BN(initialSupply).toString();
334
-
335
- const issuerReceipt = {
336
- address: tx.delegator || tx.from,
337
- changes: [{ target: address, action: 'mint', value: balance }],
338
- };
339
-
340
- return [issuerReceipt];
341
- };
342
-
343
- const getMintTokenReceipts = (tx, ctx) => {
344
- const { inputChanges, tokenFactoryState } = ctx;
345
- const { reserveAddress, tokenAddress, owner } = tokenFactoryState;
346
- const hasFee = new BN(ctx.reserveFee || '0').gt(ZERO);
347
-
348
- const inputReceipts = inputChanges.map((input) => {
349
- return {
350
- address: input.address,
351
- changes: [
352
- {
353
- target: reserveAddress,
354
- action: 'swap',
355
- value: input.delta,
356
- },
357
- ],
358
- };
359
- });
360
-
361
- return inputReceipts
362
- .concat([
363
- {
364
- address: tx.itxJson.receiver,
365
- changes: [
366
- {
367
- target: tokenAddress,
368
- action: 'mint',
369
- value: `${tx.itxJson.amount}`,
370
- },
371
- ],
372
- },
373
- ])
374
- .concat(
375
- hasFee
376
- ? [
377
- {
378
- address: owner,
379
- changes: [{ target: reserveAddress, action: 'fee', value: `${ctx.reserveFee}` }],
380
- },
381
- ]
382
- : []
383
- );
384
- };
385
-
386
- const getBurnTokenReceipts = (tx, ctx) => {
387
- const { inputChanges, tokenFactoryState } = ctx;
388
- const { reserveAddress, tokenAddress, owner } = tokenFactoryState;
389
-
390
- const fee = new BN(ctx.reserveFee || '0');
391
- const receiveAmount = new BN(ctx.reserveAmount || '0').sub(fee);
392
-
393
- const inputReceipts = inputChanges.map((input) => {
394
- return {
395
- address: input.address,
396
- changes: [
397
- {
398
- target: tokenAddress,
399
- action: 'burn',
400
- value: input.delta,
401
- },
402
- ],
403
- };
404
- });
405
-
406
- return inputReceipts
407
- .concat(
408
- receiveAmount.gt(ZERO)
409
- ? [
410
- {
411
- address: tx.itxJson.receiver,
412
- changes: [{ target: reserveAddress, action: 'swap', value: receiveAmount.toString() }],
413
- },
414
- ]
415
- : []
416
- )
417
- .concat(
418
- fee.gt(ZERO)
419
- ? [
420
- {
421
- address: owner,
422
- changes: [{ target: reserveAddress, action: 'fee', value: fee.toString() }],
423
- },
424
- ]
425
- : []
426
- );
427
- };
428
-
429
- // Following only exist for legacy support purpose
430
- const getDepositTokenReceipts = (tx) => [
431
- {
432
- address: tx.itxJson.address,
433
- changes: [{ target: '', action: 'mint', value: tx.itxJson.value }],
434
- },
435
- ];
436
-
437
- const getWithdrawFee = (value) => {
438
- let fee = new BN(value).abs().mul(new BN('1')).div(new BN('1000')); // 0.1% fee
439
- fee = BN.min(fee, new BN('100000000000000000000')); // max 100 ABT
440
- fee = BN.max(fee, new BN('1000000000000000000')); // min 1 ABT
441
- return fee;
442
- };
443
-
444
- const getWithdrawTokenReceipts = (tx) => {
445
- const fee = getWithdrawFee(tx.itxJson.value);
446
-
447
- return [
448
- {
449
- address: tx.from,
450
- changes: [
451
- { target: '', action: 'lock', value: `-${tx.itxJson.value}` },
452
- { target: '', action: 'fee', value: `-${fee.toString()}` },
453
- ],
454
- },
455
- {
456
- address: FORGE_TOKEN_HOLDER,
457
- changes: [
458
- { target: '', action: 'lock', value: tx.itxJson.value },
459
- { target: '', action: 'fee', value: fee.toString() },
460
- ],
461
- },
462
- ];
463
- };
464
-
465
- const getRevokeWithdrawReceipts = (tx, { withdrawTx }) => {
466
- if (!withdrawTx?.tx?.from) return [];
467
- if (withdrawTx.hash !== tx.itxJson.withdraw_tx_hash) return [];
468
-
469
- const account = withdrawTx.tx.from;
470
- const withdrawReceipt = withdrawTx.receipts?.find((x) => x.address === account);
471
- const lockChange = withdrawReceipt?.changes?.find((x) => x.action === 'lock');
472
-
473
- if (!lockChange) return [];
474
-
475
- const value = new BN(lockChange.value).abs();
476
- const fee = getWithdrawFee(value.toString());
477
- const revokeFee = fee.mul(new BN('5')).div(new BN('100')); // 5% fee
478
-
479
- return [
480
- {
481
- address: account,
482
- changes: [
483
- { target: '', action: 'unlock', value: value.toString() },
484
- { target: '', action: 'fee', value: fee.sub(revokeFee).toString() },
485
- ],
486
- },
487
- {
488
- address: FORGE_TOKEN_HOLDER,
489
- changes: [
490
- { target: '', action: 'unlock', value: `-${value.toString()}` },
491
- { target: '', action: 'fee', value: `-${fee.toString()}` },
492
- ],
493
- },
494
- {
495
- address: FORGE_FEE_RECEIVER,
496
- changes: [{ target: '', action: 'fee', value: revokeFee.toString() }],
497
- },
498
- ];
499
- };
500
-
501
- const getApproveWithdrawReceipts = (tx, { withdrawTx }) => {
502
- if (!withdrawTx?.tx?.itxJson?.value) return [];
503
- if (withdrawTx.hash !== tx.itxJson.withdraw_tx_hash) return [];
504
-
505
- const { value } = withdrawTx.tx.itxJson;
506
- const fee = getWithdrawFee(value);
507
-
508
- return [
509
- {
510
- address: FORGE_TOKEN_HOLDER,
511
- changes: [
512
- { target: '', action: 'burn', value: `-${value}` },
513
- { target: '', action: 'fee', value: `-${fee.toString()}` },
514
- ],
515
- },
516
- {
517
- address: FORGE_FEE_RECEIVER,
518
- changes: [{ target: '', action: 'fee', value: fee.toString() }],
519
- },
520
- ];
521
- };
522
-
523
- const getSetupSwapReceipts = (tx) => [
524
- {
525
- address: tx.from,
526
- changes: [
527
- new BN(tx.itxJson.value).gt(ZERO) ? { target: '', action: 'swap', value: `-${tx.itxJson.value}` } : null,
528
- ...tx.itxJson.assets.map((x) => ({
529
- target: x,
530
- action: 'swap',
531
- value: '-1',
532
- })),
533
- ].filter(Boolean),
534
- },
535
- ];
536
- const getRetrieveSwapReceipts = (tx) => [
537
- {
538
- address: tx.from,
539
- changes: [
540
- new BN(tx.itxJson.value).gt(ZERO) ? { target: '', action: 'swap', value: tx.itxJson.value } : null,
541
- ...tx.itxJson.assets.map((x) => ({
542
- target: x,
543
- action: 'swap',
544
- value: '1',
545
- })),
546
- ].filter(Boolean),
547
- },
548
- ];
549
-
550
- const getMigrateReceipts = (tx, context) => {
551
- const fromState = context.fromState || context.senderState;
552
-
553
- if (!fromState?.tokens) return [];
554
- if (!tx.itxJson?.address) return [];
555
-
556
- const changes = Object.entries(fromState.tokens)
557
- .filter(([, value]) => value && value !== '0')
558
- .map(([address, value]) => ({ target: address, action: 'migrate', value }));
559
-
560
- return changes.length
561
- ? [
562
- {
563
- address: tx.itxJson.address,
564
- changes,
565
- },
566
- ]
567
- : [];
568
- };
569
-
570
- const getDeclareReceipts = (tx, { config, time }) => {
571
- const txTime = new Date(time);
572
-
573
- if (!tx.itxJson?.issuer) return [];
574
- if (!config?.token) return [];
575
- if (!time) return [];
576
- if (txTime >= QLDB_MIGRATION_TIME) return [];
577
-
578
- const gas = fromTokenToUnit('0.5', config.token.decimal);
579
-
580
- return [
581
- {
582
- address: tx.itxJson?.issuer,
583
- changes: [{ target: config.token.address, action: 'gas', value: `-${gas.toString()}` }],
584
- },
585
- {
586
- address: FORGE_FEE_RECEIVER,
587
- changes: [{ target: config.token.address, action: 'gas', value: gas.toString() }],
588
- },
589
- ];
590
- };
591
-
592
- const mergeTxReceipts = (receipts) => {
593
- const merged = [];
594
- receipts.forEach((x) => {
595
- if (merged[x.address] === undefined) {
596
- merged[x.address] = x;
597
- } else {
598
- merged[x.address].changes = [...merged[x.address].changes, ...x.changes];
599
- }
600
- });
601
-
602
- return Object.values(merged);
603
- };
604
-
605
- /**
606
- * Get list of accounts with migration history
607
- * @param {Object} ctx - transaction execution context
608
- * @returns {Promise<string[][]>} adress[][]
609
- */
610
- const getAccountMigrationsFromContext = (ctx) => {
611
- const migrationAddresses = [];
612
- for (const [key, state] of Object.entries(ctx)) {
613
- if (key.endsWith('State') && state?.address) {
614
- migrationAddresses.push(getRelatedAddresses(state));
615
- }
616
- if (key.endsWith('States') && Array.isArray(state)) {
617
- state.forEach((x) => {
618
- if (x?.address) {
619
- migrationAddresses.push(getRelatedAddresses(x));
620
- }
621
- });
622
- }
623
- }
624
- return migrationAddresses;
625
- };
626
-
627
- /**
628
- * Group receipts by target
629
- * @param {Object[]} receipts - transaction receipts
630
- * @returns {Record<string, { address: string, value: BN, action: string }[]>}
631
- */
632
- const groupReceiptsByTarget = (receipts) => {
633
- const targets = {};
634
-
635
- for (const { address, changes } of receipts) {
636
- for (const { target, value, action } of changes) {
637
- if (!targets[target]) {
638
- targets[target] = [];
639
- }
640
- targets[target].push({ address, value: new BN(value), action });
641
- }
642
- }
643
- return targets;
644
- };
645
-
646
- /**
647
- * Verify transaction receipts
648
- * @param {Object[]} receipts - transaction receipts
649
- * @param {String} typeUrl - transaction type
650
- * @param {Object} ctx - transaction execution context
651
- * @returns {Promise<boolean>}
652
- */
653
- const verifyTxReceipts = (receipts, typeUrl, ctx = {}) => {
654
- const targets = groupReceiptsByTarget(receipts);
655
- const accountsWithMigration = getAccountMigrationsFromContext(ctx);
656
- const skipActions =
657
- {
658
- MintAssetTx: ['mint', 'consume'],
659
- AcquireAssetV2Tx: ['mint', 'consume'],
660
- AcquireAssetV3Tx: ['mint', 'consume'],
661
- CreateAssetTx: ['create'],
662
- CreateTokenTx: ['mint'],
663
- CreateRollupBlockTx: ['burn', 'mint'],
664
- AccountMigrateTx: ['migrate'],
665
- ApproveWithdrawTx: ['burn'],
666
- SetupSwapTx: ['swap'],
667
- RevokeSwapTx: ['swap'],
668
- RetrieveSwapTx: ['swap'],
669
- MintTokenTx: ['swap', 'mint', 'fee'],
670
- BurnTokenTx: ['burn', 'swap', 'fee'],
671
- }[typeUrl] || [];
672
-
673
- for (const [target, changes] of Object.entries(targets)) {
674
- // These actions will skip zero-sum calculation
675
- const filteredChanges = changes.filter(({ action }) => {
676
- if (!skipActions.includes(action)) return true;
677
- if (action === 'consume' && toTypeInfo(target).role !== RoleType.ROLE_ASSET) return true;
678
- return false;
679
- });
680
-
681
- // Ensure token/asset amounts are zero-sum
682
- const sum = filteredChanges.reduce((prev, current) => prev.add(current.value), new BN(0));
683
- if (!sum.eq(ZERO)) {
684
- throw new Error('INVALID_RECEIPTS_VALUE', `Receipts are not zero-sum, target: ${target}, sum: ${sum}`);
685
- }
686
-
687
- // Ensure address is unique
688
- const accountValue = {};
689
- for (const { address, value, action } of filteredChanges) {
690
- const accounts = accountsWithMigration.find((x) => x.includes(address)) || [address];
691
- for (const account of accounts) {
692
- const key = `${action}-${account}`;
693
- const existingValue = accountValue[key];
694
- if (
695
- existingValue &&
696
- ((existingValue.gt(ZERO) && value.lt(ZERO)) || (existingValue.lt(ZERO) && value.gt(ZERO)))
697
- ) {
698
- throw new Error(
699
- 'INVALID_RECEIPTS_ADDRESS',
700
- `Duplicate accounts in receipts, target: ${target}, address: ${address}, action: ${action}`
701
- );
702
- }
703
-
704
- accountValue[key] = value;
705
- }
706
- }
707
- }
708
-
709
- return true;
710
- };
711
-
712
- /**
713
- * Create transaction receipts, each receipt has following properties:
714
- *
715
- * - address: the entity did that changed
716
- * - changes:
717
- * - target: the target address that was changed, can be token address, asset address
718
- * - action: why the target has changed, such as consume, burn and transfer
719
- * - value: the amount that has changed
720
- *
721
- * @param {Object} tx - decoded transaction
722
- * @param {Object} ctx - transaction execution context
723
- * @return {Array} list of receipts
724
- */
725
- const getTxReceipts = ({ tx, code }, ctx = {}) => {
726
- const typeUrl = upperFirst(camelCase(tx?.itxJson?._type || ''));
727
-
728
- if (code !== 'OK') {
729
- const receiptsFromContext = ctx.gasPaid ? getReceiptsFromContext(ctx) : [];
730
- const receiptsFromGas = getReceiptsFromGasPaid(tx, { ...ctx, typeUrl });
731
- return receiptsFromContext.concat(receiptsFromGas);
732
- }
733
-
734
- let receipts = Array.isArray(ctx.receipts) ? ctx.receipts : [];
735
-
736
- if (['TransferTx', 'TransferV2Tx'].includes(typeUrl)) {
737
- receipts = receipts.concat(getTransferReceipts(tx, ctx));
738
- } else if (['TransferV3Tx'].includes(typeUrl)) {
739
- receipts = receipts.concat(getTransferV3Receipts(tx, ctx));
740
- } else if (['ExchangeTx', 'ExchangeV2Tx'].includes(typeUrl)) {
741
- receipts = receipts.concat(getExchangeReceipts(tx, ctx));
742
- } else if (['CreateAssetTx'].includes(typeUrl)) {
743
- receipts = receipts.concat(getCreateAssetReceipts(tx, ctx));
744
- } else if (['MintAssetTx'].includes(typeUrl)) {
745
- receipts = receipts.concat(getMintAssetReceipts(tx, ctx));
746
- } else if (['AcquireAssetV2Tx'].includes(typeUrl)) {
747
- receipts = receipts.concat(getAcquireAssetReceipts(tx, ctx));
748
- } else if (['AcquireAssetV3Tx'].includes(typeUrl)) {
749
- receipts = receipts.concat(getAcquireAssetV3Receipts(tx, ctx));
750
- } else if (['CreateTokenTx'].includes(typeUrl)) {
751
- receipts = receipts.concat(getCreateTokenReceipts(tx, ctx));
752
- } else if (['MintTokenTx'].includes(typeUrl)) {
753
- receipts = receipts.concat(getMintTokenReceipts(tx, ctx));
754
- } else if (['BurnTokenTx'].includes(typeUrl)) {
755
- receipts = receipts.concat(getBurnTokenReceipts(tx, ctx));
756
- } else if (['DepositTokenTx'].includes(typeUrl)) {
757
- receipts = receipts.concat(getDepositTokenReceipts(tx, ctx));
758
- } else if (['WithdrawTokenTx'].includes(typeUrl)) {
759
- receipts = receipts.concat(getWithdrawTokenReceipts(tx, ctx));
760
- } else if (['SetupSwapTx'].includes(typeUrl)) {
761
- receipts = receipts.concat(getSetupSwapReceipts(tx, ctx));
762
- } else if (['RetrieveSwapTx', 'RevokeSwapTx'].includes(typeUrl)) {
763
- receipts = receipts.concat(getRetrieveSwapReceipts(tx, ctx));
764
- } else if (['StakeTx'].includes(typeUrl)) {
765
- receipts = receipts.concat(getStakeReceipts(tx, ctx));
766
- } else if (['ClaimStakeTx'].includes(typeUrl)) {
767
- receipts = receipts.concat(getClaimStakeReceipts(tx, ctx, 'claim'));
768
- } else if (['SlashStakeTx'].includes(typeUrl)) {
769
- receipts = receipts.concat(getClaimStakeReceipts(tx, ctx, 'slash'));
770
- } else if (['ReturnStakeTx'].includes(typeUrl)) {
771
- receipts = receipts.concat(getClaimStakeReceipts(tx, ctx, 'stake'));
772
- } else if (['RevokeWithdrawTx'].includes(typeUrl)) {
773
- receipts = receipts.concat(getRevokeWithdrawReceipts(tx, ctx));
774
- } else if (['ApproveWithdrawTx'].includes(typeUrl)) {
775
- receipts = receipts.concat(getApproveWithdrawReceipts(tx, ctx));
776
- } else if (['AccountMigrateTx'].includes(typeUrl)) {
777
- receipts = receipts.concat(getMigrateReceipts(tx, ctx));
778
- } else if (['DeclareTx'].includes(typeUrl)) {
779
- receipts = receipts.concat(getDeclareReceipts(tx, ctx));
780
- }
781
-
782
- receipts = receipts.concat(getReceiptsFromContext(ctx));
783
- receipts = receipts.concat(getReceiptsFromGasPaid(tx, { ...ctx, typeUrl }));
784
-
785
- const merged = mergeTxReceipts(receipts).filter((x) => x.address && Array.isArray(x.changes) && x.changes.length);
786
-
787
- // Attach default token address
788
- const tokenAddr = get(ctx, 'config.token.address', '');
789
- if (tokenAddr) {
790
- merged.forEach((x) =>
791
- x.changes.forEach((c) => {
792
- if (c.target === '') {
793
- c.target = tokenAddr;
794
- }
795
- })
796
- );
797
- }
798
-
799
- return merged;
800
- };
801
-
802
- const create = (context, code = 'OK', verifyReceipts = true) => {
803
- const { txHash, txTime, tx, totalGas, itxExtras = {}, extra = {} } = context;
804
- const itx = decodeAny(tx.itx).value; // we should decode here because `context.itx` maybe changed
805
- const typeUrl = fromTypeUrl(tx.itx.typeUrl);
806
- const typeName = tx.itx.typeUrl.split(':').pop();
807
- const itxJson = formatMessage(typeUrl, itx);
808
-
809
- const result = {
810
- code,
811
- hash: txHash,
812
- height: 0,
813
- index: 0,
814
- time: txTime,
815
- sender: getTxSender({ tx, itx, typeUrl }),
816
- receiver: getTxReceiver({ tx, itx, typeUrl }),
817
- type: typeName,
818
- tx: {
819
- chainId: tx.chainId,
820
- delegator: tx.delegator,
821
- from: tx.from,
822
- nonce: tx.nonce,
823
- serviceFee: tx.serviceFee || '0',
824
- gasFee: isBN(totalGas) ? totalGas.toString(10) : '0',
825
- pk: toBase64(tx.pk),
826
- signature: toBase64(tx.signature),
827
- signatures: getListField(tx, 'signatures').map((x) => formatMessage('Multisig', x)),
828
- itx: {
829
- // Hack: this is required because of required by interface type inference on graphql
830
- __typename: typeUrl,
831
- },
832
- itxJson: {
833
- _type: typeUrl,
834
- encoded_value: tx.itx.value,
835
- type_url: tx.itx.typeUrl,
836
- ...itxJson,
837
- ...itxExtras,
838
- data: itx.data || null,
839
- },
840
- extra: extra.txExtra || null,
841
- },
842
- };
843
-
844
- result.receipts = getTxReceipts(result, context);
845
-
846
- if (code === 'OK' && verifyReceipts) {
847
- try {
848
- verifyTxReceipts(result.receipts, typeUrl, context);
849
- result.receiptsVerified = true;
850
- } catch (err) {
851
- console.warn(JSON.stringify(result, null));
852
- console.error('verifyTxReceipts error', err);
853
- result.receiptsVerified = false;
854
- }
855
- }
856
-
857
- // determine gasPaid
858
- attachPaidTxGas(result);
859
-
860
- return result;
861
- };
862
-
863
- const attachPaidTxGas = (tx) => {
864
- if (tx.tx.gasPaid) {
865
- return tx;
866
- }
867
-
868
- // determine gasPaid
869
- let gasChange = null;
870
- tx.receipts.forEach((r) => {
871
- const tmp = r.changes.find((c) => c.action === 'gas' && c.value === tx.tx.gasFee);
872
- if (tmp) {
873
- gasChange = tmp;
874
- }
875
- });
876
- tx.tx.gasPaid = gasChange ? tx.tx.gasFee : '0';
877
-
878
- return tx;
879
- };
880
-
881
- // Only a few props are allowed to be changed in tx state
882
- const update = (state, updates) => merge(state, pick(updates, ['finalized', 'tx.itxJson.actualFee']));
883
-
884
- module.exports = {
885
- create,
886
- update,
887
- getTxReceiver,
888
- getTxSender,
889
- getTxReceipts,
890
- mergeTxReceipts,
891
- attachPaidTxGas,
892
- verifyTxReceipts,
893
- eachReceipts,
894
- groupReceiptTokenChanges,
895
- FORGE_TOKEN_HOLDER,
896
- };