@ocap/tx-protocols 1.6.3 → 1.6.10

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 (52) hide show
  1. package/README.md +36 -0
  2. package/lib/execute.js +97 -30
  3. package/lib/index.js +56 -14
  4. package/lib/protocols/account/declare.js +36 -30
  5. package/lib/protocols/account/delegate.js +78 -40
  6. package/lib/protocols/account/migrate.js +65 -49
  7. package/lib/protocols/account/revoke-delegate.js +39 -23
  8. package/lib/protocols/asset/acquire-v2.js +159 -0
  9. package/lib/protocols/asset/acquire-v3.js +242 -0
  10. package/lib/protocols/asset/calls/README.md +5 -0
  11. package/lib/protocols/asset/calls/transfer-token.js +37 -0
  12. package/lib/protocols/asset/calls/transfer.js +29 -0
  13. package/lib/protocols/asset/create.js +85 -36
  14. package/lib/protocols/asset/mint.js +133 -0
  15. package/lib/protocols/asset/pipes/exec-mint-hook.js +59 -0
  16. package/lib/protocols/asset/pipes/extract-factory-tokens.js +18 -0
  17. package/lib/protocols/asset/pipes/verify-acquire-params.js +30 -0
  18. package/lib/protocols/asset/pipes/verify-itx-address.js +41 -0
  19. package/lib/protocols/asset/pipes/verify-itx-assets.js +49 -0
  20. package/lib/protocols/asset/pipes/verify-itx-variables.js +26 -0
  21. package/lib/protocols/asset/pipes/verify-mint-limit.js +13 -0
  22. package/lib/protocols/asset/update.js +76 -44
  23. package/lib/protocols/factory/create.js +146 -0
  24. package/lib/protocols/governance/claim-stake.js +219 -0
  25. package/lib/protocols/governance/revoke-stake.js +136 -0
  26. package/lib/protocols/governance/stake.js +176 -0
  27. package/lib/protocols/rollup/claim-reward.js +283 -0
  28. package/lib/protocols/rollup/create-block.js +333 -0
  29. package/lib/protocols/rollup/create.js +169 -0
  30. package/lib/protocols/rollup/join.js +156 -0
  31. package/lib/protocols/rollup/leave.js +127 -0
  32. package/lib/protocols/rollup/migrate-contract.js +53 -0
  33. package/lib/protocols/rollup/migrate-token.js +66 -0
  34. package/lib/protocols/rollup/pause.js +52 -0
  35. package/lib/protocols/rollup/pipes/ensure-service-fee.js +37 -0
  36. package/lib/protocols/rollup/pipes/ensure-validator.js +10 -0
  37. package/lib/protocols/rollup/pipes/verify-evidence.js +37 -0
  38. package/lib/protocols/rollup/pipes/verify-paused.js +10 -0
  39. package/lib/protocols/rollup/pipes/verify-signers.js +88 -0
  40. package/lib/protocols/rollup/resume.js +52 -0
  41. package/lib/protocols/rollup/update.js +98 -0
  42. package/lib/protocols/token/create.js +150 -0
  43. package/lib/protocols/token/deposit-v2.js +241 -0
  44. package/lib/protocols/token/withdraw-v2.js +255 -0
  45. package/lib/protocols/trade/exchange-v2.js +179 -0
  46. package/lib/protocols/trade/transfer-v2.js +136 -0
  47. package/lib/protocols/trade/transfer-v3.js +241 -0
  48. package/lib/util.js +325 -2
  49. package/package.json +23 -16
  50. package/lib/protocols/misc/poke.js +0 -106
  51. package/lib/protocols/trade/exchange.js +0 -139
  52. package/lib/protocols/trade/transfer.js +0 -101
@@ -0,0 +1,179 @@
1
+ const isEmpty = require('empty-value');
2
+ const getListField = require('@ocap/util/lib/get-list-field');
3
+ const createSortedList = require('@ocap/util/lib/create-sorted-list');
4
+ const { decodeBigInt } = require('@ocap/message');
5
+ const { BN } = require('@ocap/util');
6
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
7
+ const { account } = require('@ocap/state');
8
+
9
+ // eslint-disable-next-line global-require
10
+ const debug = require('debug')(`${require('../../../package.json').name}:exchange-v2`);
11
+
12
+ const runner = new Runner();
13
+
14
+ runner.use(pipes.VerifyMultiSig(1));
15
+ runner.use(pipes.ExtractReceiver({ from: ['tx.signaturesList', 'tx.signatures', 'itx.to'] }));
16
+
17
+ runner.use(
18
+ pipes.VerifyInfo([
19
+ {
20
+ error: 'INSUFFICIENT_DATA',
21
+ message: 'itx.sender, itx.receiver and itx.to can not be empty',
22
+ fn: ({ itx }) => itx.sender && itx.receiver && itx.to,
23
+ },
24
+ {
25
+ error: 'INVALID_TX',
26
+ message: 'Can not exchange without any token or assets',
27
+ fn: (context) => {
28
+ const zero = new BN(0);
29
+ const { itx, config } = context;
30
+
31
+ context.senderAssets = getListField(itx, 'sender.assets');
32
+ context.receiverAssets = getListField(itx, 'receiver.assets');
33
+
34
+ context.senderTokens = getListField(itx, 'sender.tokens');
35
+ context.receiverTokens = getListField(itx, 'receiver.tokens');
36
+
37
+ const senderAmount = itx.sender.value ? decodeBigInt(itx.sender.value) : 0;
38
+ if (new BN(senderAmount).gt(zero)) {
39
+ context.senderTokens.push({ address: config.token.address, value: senderAmount });
40
+ itx.sender.value = '0';
41
+ }
42
+
43
+ const receiverAmount = itx.receiver.value ? decodeBigInt(itx.receiver.value) : 0;
44
+ if (new BN(receiverAmount).gt(zero)) {
45
+ context.receiverTokens.push({ address: config.token.address, value: receiverAmount });
46
+ itx.receiver.value = '0';
47
+ }
48
+
49
+ context.tokenAddress = createSortedList(
50
+ context.senderTokens.concat(context.receiverTokens).map((x) => x.address)
51
+ );
52
+
53
+ if (isEmpty(context.senderAssets) && isEmpty(context.senderTokens)) {
54
+ return false;
55
+ }
56
+ if (isEmpty(context.receiverAssets) && isEmpty(context.receiverTokens)) {
57
+ return false;
58
+ }
59
+
60
+ return true;
61
+ },
62
+ },
63
+ {
64
+ error: 'INVALID_TX',
65
+ message: 'Sender token value must be positive',
66
+ fn: (context) => context.senderTokens.every((x) => new BN(x.value).gt(new BN(0))),
67
+ },
68
+ {
69
+ error: 'INVALID_TX',
70
+ message: 'Receiver token value must be positive',
71
+ fn: (context) => context.receiverTokens.every((x) => new BN(x.value).gt(new BN(0))),
72
+ },
73
+ ])
74
+ );
75
+
76
+ // TODO: verify-expiration
77
+
78
+ runner.use(pipes.VerifySigner({ signer: 'itx.to' }));
79
+ runner.use(pipes.VerifyListSize({ listKey: ['senderAssets', 'receiverAssets'] }));
80
+ runner.use(pipes.VerifyListSize({ listKey: ['senderTokens', 'receiverTokens'] }));
81
+
82
+ // TODO: anti-replay-exchange-attack
83
+
84
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
85
+ runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
86
+ runner.use(pipes.VerifyDelegation({ type: 'signature', signerKey: 'senderState', delegatorKey: 'delegatorState' }));
87
+ runner.use(pipes.ExtractSigner({ signerKey: 'signerStates', delegatorKey: 'delegatorStates' }));
88
+ runner.use(
89
+ pipes.VerifyDelegation({
90
+ type: 'multisig',
91
+ signerKey: 'signerStates',
92
+ delegatorKey: 'delegatorStates',
93
+ delegationKey: 'delegationStates',
94
+ })
95
+ );
96
+
97
+ runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
98
+
99
+ runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE', table: 'account' })); // prettier-ignore
100
+ runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState', signerKey: 'signerStates' }));
101
+ runner.use((context, next) => {
102
+ context.senderTokenConditions = {
103
+ owner: context.senderState.address,
104
+ tokens: context.senderTokens,
105
+ };
106
+ context.receiverTokenConditions = {
107
+ owner: context.receiverState.address,
108
+ tokens: context.receiverTokens,
109
+ };
110
+ next();
111
+ });
112
+ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'senderTokenConditions' }));
113
+ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'receiverState', conditionKey: 'receiverTokenConditions' }));
114
+
115
+ runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
116
+
117
+ runner.use(pipes.ExtractState({ from: 'senderAssets', to: 'priv.senderAssets', status: 'INVALID_ASSET', table: 'asset' })); // prettier-ignore
118
+ runner.use(pipes.VerifyTransferrable({ assets: 'priv.senderAssets' }));
119
+ runner.use(pipes.VerifyUpdater({ assetKey: 'priv.senderAssets', ownerKey: 'senderState' }));
120
+
121
+ runner.use(pipes.ExtractState({ from: 'receiverAssets', to: 'priv.receiverAssets', status: 'INVALID_ASSET', table: 'asset' })); // prettier-ignore
122
+ runner.use(pipes.VerifyTransferrable({ assets: 'priv.receiverAssets' }));
123
+ runner.use(pipes.VerifyUpdater({ assetKey: 'priv.receiverAssets', ownerKey: 'receiverState' }));
124
+
125
+ runner.use(pipes.UpdateOwner({ assets: 'priv.senderAssets', owner: 'receiverState' }));
126
+ runner.use(pipes.UpdateOwner({ assets: 'priv.receiverAssets', owner: 'senderState' }));
127
+
128
+ runner.use(
129
+ async (context, next) => {
130
+ const { tx, itx, senderAssets, receiverAssets, senderTokens, receiverTokens, senderState, receiverState, statedb } =
131
+ context;
132
+
133
+ const senderStateTokens = senderState.tokens || {};
134
+ const receiverStateTokens = receiverState.tokens || {};
135
+
136
+ for (const { address, value } of senderTokens) {
137
+ const delta = new BN(value);
138
+ senderStateTokens[address] = new BN(senderStateTokens[address]).sub(delta).toString();
139
+ receiverStateTokens[address] = new BN(receiverStateTokens[address] || '0').add(delta).toString();
140
+ }
141
+
142
+ for (const { address, value } of receiverTokens) {
143
+ const delta = new BN(value);
144
+ senderStateTokens[address] = new BN(senderStateTokens[address]).add(delta).toString();
145
+ receiverStateTokens[address] = new BN(receiverStateTokens[address] || '0').sub(delta).toString();
146
+ }
147
+
148
+ const [newSenderState, newReceiverState] = await Promise.all([
149
+ // Update sender state
150
+ statedb.account.update(
151
+ senderState.address,
152
+ account.update(senderState, { nonce: tx.nonce, tokens: senderStateTokens }, context),
153
+ context
154
+ ),
155
+
156
+ // Update receiver state
157
+ statedb.account.update(
158
+ receiverState.address,
159
+ account.update(receiverState, { tokens: receiverStateTokens }, context),
160
+ context
161
+ ),
162
+ ]);
163
+
164
+ context.senderState = newSenderState;
165
+ context.receiverState = newReceiverState;
166
+
167
+ debug('exchange', {
168
+ from: tx.from,
169
+ to: itx.to,
170
+ sender: { assets: senderAssets.length, token: senderTokens.length },
171
+ receiver: { assets: receiverAssets.length, token: receiverTokens.length },
172
+ });
173
+
174
+ next();
175
+ },
176
+ { persistError: true }
177
+ );
178
+
179
+ module.exports = runner;
@@ -0,0 +1,136 @@
1
+ const isEmpty = require('empty-value');
2
+ const getListField = require('@ocap/util/lib/get-list-field');
3
+ const { decodeBigInt } = require('@ocap/message');
4
+ const { BN } = require('@ocap/util');
5
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
6
+ const { account } = require('@ocap/state');
7
+
8
+ // eslint-disable-next-line global-require
9
+ const debug = require('debug')(`${require('../../../package.json').name}:transfer-v2`);
10
+
11
+ const runner = new Runner();
12
+
13
+ runner.use(pipes.VerifyMultiSig(0));
14
+
15
+ runner.use((context, next) => {
16
+ context.assets = getListField(context, 'itx.assets');
17
+ context.tokens = getListField(context, 'itx.tokens');
18
+ context.tokenAddress = context.tokens.map((x) => x.address);
19
+ next();
20
+ });
21
+
22
+ runner.use(
23
+ pipes.VerifyInfo([
24
+ {
25
+ error: 'INSUFFICIENT_DATA',
26
+ message: 'Can not transfer without any primary token or assets or secondary tokens',
27
+ fn: ({ itx, assets, tokens }) => {
28
+ if (itx.to && (itx.value || isEmpty(assets) === false || isEmpty(tokens) === false)) {
29
+ return true;
30
+ }
31
+ return false;
32
+ },
33
+ },
34
+ {
35
+ error: 'INVALID_TX',
36
+ message: 'Can not transfer primary token smaller than 0',
37
+ fn: ({ itx, assets, tokens }) => {
38
+ if (isEmpty(tokens) && isEmpty(assets)) {
39
+ const amount = itx.value ? decodeBigInt(itx.value) : 0;
40
+ if (new BN(amount).lte(new BN(0))) {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ return true;
46
+ },
47
+ },
48
+ ])
49
+ );
50
+
51
+ runner.use(pipes.VerifyListSize({ listKey: ['assets', 'tokens'] }));
52
+
53
+ // For backwards compatibility: merge primary and secondary tokens
54
+ runner.use((context, next) => {
55
+ if (context.itx.value) {
56
+ const amount = new BN(decodeBigInt(context.itx.value));
57
+ if (amount.gt(new BN(0))) {
58
+ context.tokens.push({ address: context.config.token.address, value: amount.toString() });
59
+ context.itx.value = undefined;
60
+ }
61
+ }
62
+ next();
63
+ });
64
+
65
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
66
+ runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
67
+
68
+ runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
69
+ runner.use((context, next) => {
70
+ context.tokenConditions = {
71
+ owner: context.senderState.address,
72
+ tokens: context.tokens,
73
+ };
74
+ next();
75
+ });
76
+ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'tokenConditions' }));
77
+
78
+ runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
79
+ runner.use(pipes.VerifyDelegation({ type: 'signature', signerKey: 'senderState', delegatorKey: 'delegatorState' }));
80
+
81
+ runner.use(pipes.ExtractReceiver({ from: 'itx.to', to: 'receiver' }));
82
+ runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'OK', table: 'account' }));
83
+ runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
84
+
85
+ runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'INVALID_ASSET', table: 'asset' }));
86
+ runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
87
+ runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'senderState' }));
88
+
89
+ // transfer assets to new owner
90
+ runner.use((context, next) => {
91
+ const { itx, receiverState } = context;
92
+ context.receiverAddr = receiverState ? receiverState.address : itx.to;
93
+ return next();
94
+ });
95
+ runner.use(pipes.UpdateOwner({ assets: 'assetStates', owner: 'receiverAddr' }));
96
+
97
+ // update statedb: transfer tokens to new owner
98
+ runner.use(
99
+ async (context, next) => {
100
+ const { tx, itx, tokens, senderState, receiverAddr, receiverState, statedb } = context;
101
+
102
+ const { tokens: senderTokens = {} } = senderState;
103
+ const { tokens: receiverTokens = {} } = receiverState || {};
104
+ for (const token of tokens) {
105
+ const delta = new BN(token.value);
106
+ senderTokens[token.address] = new BN(senderTokens[token.address]).sub(delta).toString();
107
+ receiverTokens[token.address] = new BN(receiverTokens[token.address] || '0').add(delta).toString();
108
+ }
109
+
110
+ const [newSenderState, newReceiverState] = await Promise.all([
111
+ // Update sender state
112
+ statedb.account.update(
113
+ senderState.address,
114
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, tokens: senderTokens }, context),
115
+ context
116
+ ),
117
+
118
+ // Update receiver state
119
+ statedb.account.updateOrCreate(
120
+ receiverState,
121
+ account.updateOrCreate(receiverState, { address: receiverAddr, tokens: receiverTokens }, context),
122
+ context
123
+ ),
124
+ ]);
125
+
126
+ context.senderState = newSenderState;
127
+ context.receiverState = newReceiverState;
128
+
129
+ debug('transfer-v2', { from: tx.from, to: itx.to, tokens });
130
+
131
+ next();
132
+ },
133
+ { persistError: true }
134
+ );
135
+
136
+ module.exports = runner;
@@ -0,0 +1,241 @@
1
+ /* eslint-disable function-paren-newline */
2
+ /* eslint-disable prefer-object-spread */
3
+ /* eslint-disable no-restricted-syntax */
4
+ const { promisify } = require('util');
5
+ const isEqual = require('lodash/isEqual');
6
+ const { BN } = require('@ocap/util');
7
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
8
+ const { account, asset } = require('@ocap/state');
9
+ const getRelatedAddresses = require('@ocap/util/lib/get-related-addr');
10
+
11
+ // eslint-disable-next-line global-require
12
+ const debug = require('debug')(`${require('../../../package.json').name}:transfer-v2`);
13
+
14
+ const { applyTokenUpdates } = require('../../util');
15
+
16
+ const runner = new Runner();
17
+
18
+ const verifyAssetOwner = promisify(pipes.VerifyUpdater({ assetKey: 'assets', ownerKey: 'owner' }));
19
+
20
+ // 0. extract itx
21
+ runner.use(
22
+ pipes.VerifyTxInput({
23
+ fieldKey: 'itx.inputs',
24
+ inputsKey: 'inputs',
25
+ sendersKey: 'senders',
26
+ tokensKey: 'inputTokens',
27
+ assetsKey: 'inputAssets',
28
+ })
29
+ );
30
+ runner.use(
31
+ pipes.VerifyTxInput({
32
+ fieldKey: 'itx.outputs',
33
+ inputsKey: 'outputs',
34
+ sendersKey: 'receivers',
35
+ tokensKey: 'outputTokens',
36
+ assetsKey: 'outputAssets',
37
+ })
38
+ );
39
+
40
+ // 1. verify itx size: set hard limit here because more inputs leads to longer tx execute time
41
+ runner.use(
42
+ pipes.VerifyListSize({
43
+ listKey: [
44
+ 'inputs',
45
+ 'outputs',
46
+ 'senders',
47
+ 'receivers',
48
+ 'inputTokens',
49
+ 'inputAssets',
50
+ 'outputTokens',
51
+ 'outputAssets',
52
+ ],
53
+ })
54
+ );
55
+
56
+ // 2. verify itx payload
57
+ runner.use(
58
+ pipes.VerifyInfo([
59
+ {
60
+ error: 'INVALID_TX',
61
+ message: 'An owner can not exist in tx input and output at the same time',
62
+ fn: ({ senders, receivers }) => senders.every((x) => receivers.includes(x) === false),
63
+ },
64
+ {
65
+ error: 'INVALID_TX',
66
+ message: 'Input and output token address does not match',
67
+ fn: ({ inputTokens, outputTokens }) => isEqual(inputTokens, outputTokens),
68
+ },
69
+ {
70
+ error: 'INVALID_TX',
71
+ message: 'Input and output asset address does not match',
72
+ fn: ({ inputAssets, outputAssets }) => isEqual(inputAssets, outputAssets),
73
+ },
74
+
75
+ // If we made this far, token address must match with input and output
76
+ // Then we can safely sum all inputs and outputs and ensure that they match in amount
77
+ {
78
+ error: 'INVALID_TX',
79
+ message: 'Input and output token amount does not match',
80
+ fn: ({ inputs, outputs, inputTokens }) => {
81
+ const inputMap = {};
82
+ const outputMap = {};
83
+
84
+ inputs.forEach(({ tokensList }) => {
85
+ tokensList.forEach(({ address, value }) => {
86
+ if (typeof inputMap[address] === 'undefined') {
87
+ inputMap[address] = new BN(0);
88
+ }
89
+ inputMap[address] = inputMap[address].add(new BN(value));
90
+ });
91
+ });
92
+
93
+ outputs.forEach(({ tokensList }) => {
94
+ tokensList.forEach(({ address, value }) => {
95
+ if (typeof outputMap[address] === 'undefined') {
96
+ outputMap[address] = new BN(0);
97
+ }
98
+ outputMap[address] = outputMap[address].add(new BN(value));
99
+ });
100
+ });
101
+
102
+ return inputTokens.every((address) => inputMap[address].eq(outputMap[address]));
103
+ },
104
+ },
105
+ ])
106
+ );
107
+
108
+ // 3. verify multi sig
109
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
110
+
111
+ // 4. verify sender & signer & receiver
112
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
113
+ runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
114
+ runner.use(pipes.ExtractState({ from: 'receivers', to: 'receiverStates', status: 'OK', table: 'account' }));
115
+ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
116
+
117
+ // 5. verify token state and balance
118
+ runner.use(pipes.ExtractState({ from: 'inputTokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
119
+ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }));
120
+
121
+ // 6. verify asset state and transferrable, ownership
122
+ runner.use(pipes.ExtractState({ from: 'inputAssets', to: 'assetStates', status: 'INVALID_ASSET', table: 'asset' }));
123
+ runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
124
+ runner.use(async (context, next) => {
125
+ const { inputs, assetStates = [], signerStates } = context;
126
+ for (const input of inputs) {
127
+ const { owner, assetsList } = input;
128
+ const states = assetStates.filter((x) => assetsList.includes(x.address));
129
+ const signer = signerStates.find((x) => x.address === owner);
130
+
131
+ try {
132
+ // eslint-disable-next-line no-await-in-loop
133
+ await verifyAssetOwner({ assets: states, owner: signer });
134
+ } catch (err) {
135
+ return next(err);
136
+ }
137
+ }
138
+
139
+ return next();
140
+ });
141
+
142
+ runner.use(
143
+ async (context, next) => {
144
+ const {
145
+ tx,
146
+ inputs,
147
+ outputs,
148
+ receivers,
149
+ senderState,
150
+ signerStates,
151
+ receiverStates = [],
152
+ assetStates = [],
153
+ statedb,
154
+ } = context;
155
+
156
+ const signerUpdates = {};
157
+ inputs.forEach((x) => {
158
+ const { owner, tokensList } = x;
159
+ signerUpdates[owner] = applyTokenUpdates(
160
+ tokensList,
161
+ signerStates.find((s) => s.address === owner),
162
+ 'sub'
163
+ );
164
+ });
165
+
166
+ const receiverUpdates = {};
167
+ const assetUpdates = {};
168
+ outputs.forEach((x) => {
169
+ const { owner, tokensList, assetsList } = x;
170
+ const ownerState = receiverStates.find((s) => getRelatedAddresses(s).includes(owner)) || { address: owner };
171
+ receiverUpdates[ownerState.address] = applyTokenUpdates(tokensList, ownerState, 'add');
172
+ assetsList.forEach((address) => {
173
+ assetUpdates[address] = { owner: ownerState.address };
174
+ });
175
+ });
176
+
177
+ const sender = senderState ? senderState.address : tx.from;
178
+ const isAlsoSigner = !!signerUpdates[sender];
179
+ const isAlsoReceiver = !!receiverUpdates[sender];
180
+
181
+ debug('transfer-v3', { signerUpdates, receiverUpdates, assetUpdates, isAlsoSigner, isAlsoReceiver });
182
+
183
+ const [newSenderState, newSignerStates, newReceiverStates, newAssetStates] = await Promise.all([
184
+ // Update sender state
185
+ statedb.account.updateOrCreate(
186
+ senderState,
187
+ account.updateOrCreate(
188
+ senderState,
189
+ Object.assign(
190
+ { address: sender, nonce: tx.nonce, pk: tx.pk },
191
+ signerUpdates[sender] || {},
192
+ receiverUpdates[sender] || {}
193
+ ),
194
+ context
195
+ ),
196
+ context
197
+ ),
198
+
199
+ // Update signer state
200
+ Promise.all(
201
+ signerStates
202
+ .filter((x) => x.address !== sender)
203
+ .map((x) => statedb.account.update(x.address, account.update(x, signerUpdates[x.address], context), context))
204
+ ),
205
+
206
+ // UpdateOrCreate receiver state
207
+ Promise.all(
208
+ receivers
209
+ .filter((x) => x !== sender)
210
+ .map((x) => {
211
+ const receiverState = receiverStates.find((s) => getRelatedAddresses(s).includes(x));
212
+ const owner = receiverState ? receiverState.address : x;
213
+ return statedb.account.updateOrCreate(
214
+ receiverState,
215
+ account.updateOrCreate(receiverState, { ...receiverUpdates[owner], address: owner }, context),
216
+ context
217
+ );
218
+ })
219
+ ),
220
+
221
+ // Update asset state
222
+ Promise.all(
223
+ assetStates.map((x) =>
224
+ statedb.asset.update(x.address, asset.update(x, assetUpdates[x.address], context), context)
225
+ )
226
+ ),
227
+ ]);
228
+
229
+ context.senderState = newSenderState;
230
+ context.signerStates = isAlsoSigner ? newSignerStates.concat(newSenderState) : newSignerStates;
231
+ context.receiverStates = isAlsoReceiver ? newReceiverStates.concat(newSenderState) : newReceiverStates;
232
+ context.assetStates = newAssetStates;
233
+
234
+ debug('transfer-v3', { inputs, outputs });
235
+
236
+ next();
237
+ },
238
+ { persistError: true }
239
+ );
240
+
241
+ module.exports = runner;