@ocap/tx-protocols 1.21.3 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +8 -0
- package/lib/protocols/token/create.js +1 -0
- package/lib/protocols/token-factory/burn.js +255 -0
- package/lib/protocols/token-factory/create.js +205 -0
- package/lib/protocols/token-factory/mint.js +248 -0
- package/lib/protocols/token-factory/pipes/calc-reserve.js +40 -0
- package/lib/protocols/token-factory/update.js +108 -0
- package/package.json +16 -16
package/lib/index.js
CHANGED
|
@@ -31,6 +31,10 @@ const resumeRollup = require('./protocols/rollup/resume');
|
|
|
31
31
|
const createRollupBlock = require('./protocols/rollup/create-block');
|
|
32
32
|
const claimBlockReward = require('./protocols/rollup/claim-reward');
|
|
33
33
|
const migrateRollup = require('./protocols/rollup/migrate');
|
|
34
|
+
const createTokenFactory = require('./protocols/token-factory/create');
|
|
35
|
+
const updateTokenFactory = require('./protocols/token-factory/update');
|
|
36
|
+
const mintToken = require('./protocols/token-factory/mint');
|
|
37
|
+
const burnToken = require('./protocols/token-factory/burn');
|
|
34
38
|
|
|
35
39
|
const executor = require('./execute');
|
|
36
40
|
|
|
@@ -62,6 +66,10 @@ const createExecutor = ({ filter, runAsLambda }) => {
|
|
|
62
66
|
createToken,
|
|
63
67
|
depositTokenV2,
|
|
64
68
|
withdrawTokenV2,
|
|
69
|
+
createTokenFactory,
|
|
70
|
+
updateTokenFactory,
|
|
71
|
+
mintToken,
|
|
72
|
+
burnToken,
|
|
65
73
|
|
|
66
74
|
// governance
|
|
67
75
|
stake,
|
|
@@ -31,6 +31,7 @@ const schema = Joi.object({
|
|
|
31
31
|
totalSupply: Joi.BN().greater(0).max(MAX_TOTAL_SUPPLY).required(),
|
|
32
32
|
initialSupply: Joi.BN().greater(0).max(Joi.ref('totalSupply')).required(),
|
|
33
33
|
foreignToken: schemas.foreignToken.optional().allow(null).default(null),
|
|
34
|
+
tokenFactoryAddress: Joi.forbidden(),
|
|
34
35
|
data: Joi.any().optional().allow(null),
|
|
35
36
|
}).options({ stripUnknown: true, noDefaults: false });
|
|
36
37
|
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
2
|
+
const { Joi, schemas } = require('@arcblock/validator');
|
|
3
|
+
const { BN, isSameDid, fromUnitToToken } = require('@ocap/util');
|
|
4
|
+
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
5
|
+
const { account, tokenFactory, token } = require('@ocap/state');
|
|
6
|
+
|
|
7
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
8
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
9
|
+
const CalcReserveAmount = require('./pipes/calc-reserve');
|
|
10
|
+
const { applyTokenChange } = require('../../util');
|
|
11
|
+
|
|
12
|
+
const runner = new Runner();
|
|
13
|
+
|
|
14
|
+
runner.use(pipes.VerifyMultiSig(0));
|
|
15
|
+
|
|
16
|
+
const schema = Joi.object({
|
|
17
|
+
tokenFactory: Joi.DID().prefix().role('ROLE_TOKEN_FACTORY').required(),
|
|
18
|
+
receiver: schemas.tokenHolder.required(),
|
|
19
|
+
amount: Joi.BN().greater(0).required(),
|
|
20
|
+
minReserve: Joi.alternatives().try(Joi.BN().greater(0), Joi.equal('')).optional(),
|
|
21
|
+
data: Joi.any().optional().allow(null),
|
|
22
|
+
}).options({ stripUnknown: true, noDefaults: false });
|
|
23
|
+
|
|
24
|
+
// verify itx
|
|
25
|
+
runner.use(({ itx }, next) => {
|
|
26
|
+
const { error } = schema.validate(itx);
|
|
27
|
+
if (error) {
|
|
28
|
+
return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
|
|
29
|
+
}
|
|
30
|
+
return next();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// verify token factory
|
|
34
|
+
runner.use(
|
|
35
|
+
pipes.ExtractState({
|
|
36
|
+
from: 'itx.tokenFactory',
|
|
37
|
+
to: 'tokenFactoryState',
|
|
38
|
+
status: 'INVALID_TOKEN_FACTORY',
|
|
39
|
+
table: 'tokenFactory',
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// ensure sender
|
|
44
|
+
runner.use(
|
|
45
|
+
pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
|
|
46
|
+
);
|
|
47
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
48
|
+
|
|
49
|
+
// ensure receiver
|
|
50
|
+
runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'OK', table: 'account' }));
|
|
51
|
+
|
|
52
|
+
// ensure owner
|
|
53
|
+
runner.use(
|
|
54
|
+
pipes.ExtractState({
|
|
55
|
+
from: 'tokenFactoryState.owner',
|
|
56
|
+
to: 'ownerState',
|
|
57
|
+
status: 'INVALID_OWNER_STATE',
|
|
58
|
+
table: 'account',
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// verify blocked
|
|
63
|
+
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState', 'receiverState'] }));
|
|
64
|
+
|
|
65
|
+
// ensure token state
|
|
66
|
+
runner.use(
|
|
67
|
+
pipes.ExtractState({
|
|
68
|
+
from: 'tokenFactoryState.tokenAddress',
|
|
69
|
+
to: 'tokenState',
|
|
70
|
+
status: 'INVALID_TOKEN',
|
|
71
|
+
table: 'token',
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// calculate reserve amount
|
|
76
|
+
runner.use(
|
|
77
|
+
CalcReserveAmount({
|
|
78
|
+
tokenFactoryKey: 'tokenFactoryState',
|
|
79
|
+
tokenStateKey: 'tokenState',
|
|
80
|
+
reserveKey: 'reserveAmount',
|
|
81
|
+
feeKey: 'reserveFee',
|
|
82
|
+
direction: 'burn',
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// verify slippage
|
|
87
|
+
runner.use((context, next) => {
|
|
88
|
+
const { reserveAmount, reserveFee } = context;
|
|
89
|
+
const { minReserve } = context.itx;
|
|
90
|
+
|
|
91
|
+
if (minReserve && new BN(reserveAmount).lt(new BN(minReserve).add(new BN(reserveFee)))) {
|
|
92
|
+
return next(
|
|
93
|
+
new Error(
|
|
94
|
+
'SLIPPAGE_EXCEEDED',
|
|
95
|
+
`Burn token failed due to price movement. Expected minimum: ${fromUnitToToken(minReserve)}, actual: ${fromUnitToToken(reserveAmount)}. Try reducing your minReserve.`
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
next();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// verify sender tokens
|
|
104
|
+
runner.use((context, next) => {
|
|
105
|
+
const { tokenFactoryState, itx, reserveFee } = context;
|
|
106
|
+
|
|
107
|
+
context.tokenConditions = {
|
|
108
|
+
owner: context.senderState.address,
|
|
109
|
+
tokens: [{ address: tokenFactoryState.tokenAddress, value: itx.amount }],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (reserveFee) {
|
|
113
|
+
context.tokenConditions.tokens.push({ address: tokenFactoryState.reserveAddress, value: reserveFee });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
next();
|
|
117
|
+
});
|
|
118
|
+
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'tokenConditions' }));
|
|
119
|
+
|
|
120
|
+
// Ensure tx fee and gas
|
|
121
|
+
runner.use(
|
|
122
|
+
EnsureTxGas((context) => {
|
|
123
|
+
const result = { create: 0, update: 3, payment: 0 };
|
|
124
|
+
|
|
125
|
+
if (context.receiverState) {
|
|
126
|
+
result.update += 1;
|
|
127
|
+
}
|
|
128
|
+
if (context.reserveFee) {
|
|
129
|
+
result.update += 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result;
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
136
|
+
|
|
137
|
+
// Save context snapshot before updating states
|
|
138
|
+
runner.use(pipes.TakeStateSnapshot());
|
|
139
|
+
|
|
140
|
+
// update statedb: transfer tokens to new owner
|
|
141
|
+
runner.use(
|
|
142
|
+
async (context, next) => {
|
|
143
|
+
const {
|
|
144
|
+
tx,
|
|
145
|
+
senderState,
|
|
146
|
+
receiverState,
|
|
147
|
+
ownerState,
|
|
148
|
+
statedb,
|
|
149
|
+
senderChange,
|
|
150
|
+
updateVaults,
|
|
151
|
+
tokenFactoryState,
|
|
152
|
+
tokenState,
|
|
153
|
+
reserveAmount,
|
|
154
|
+
reserveFee,
|
|
155
|
+
itx,
|
|
156
|
+
} = context;
|
|
157
|
+
const { amount, receiver } = itx;
|
|
158
|
+
const { reserveAddress, tokenAddress } = tokenFactoryState;
|
|
159
|
+
|
|
160
|
+
const { tokens: senderTokens = {} } = senderState;
|
|
161
|
+
const { tokens: receiverTokens = {} } = receiverState || {};
|
|
162
|
+
const { tokens: ownerTokens = {} } = ownerState;
|
|
163
|
+
|
|
164
|
+
senderTokens[tokenAddress] = new BN(senderTokens[tokenAddress]).sub(new BN(amount)).toString();
|
|
165
|
+
receiverTokens[reserveAddress] = new BN(receiverTokens[reserveAddress] || '0')
|
|
166
|
+
.add(new BN(reserveAmount))
|
|
167
|
+
.toString();
|
|
168
|
+
|
|
169
|
+
if (isSameDid(senderState.address, receiver)) {
|
|
170
|
+
senderTokens[reserveAddress] = receiverTokens[reserveAddress];
|
|
171
|
+
}
|
|
172
|
+
if (reserveFee) {
|
|
173
|
+
const fee = new BN(reserveFee);
|
|
174
|
+
senderTokens[reserveAddress] = new BN(senderTokens[reserveAddress]).sub(fee).toString();
|
|
175
|
+
|
|
176
|
+
if (isSameDid(receiver, ownerState.address)) {
|
|
177
|
+
receiverTokens[reserveAddress] = new BN(receiverTokens[reserveAddress] || '0').add(fee).toString();
|
|
178
|
+
} else {
|
|
179
|
+
ownerTokens[reserveAddress] = new BN(ownerTokens[reserveAddress] || '0').add(fee).toString();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const senderUpdates = senderChange
|
|
184
|
+
? applyTokenChange({ tokens: senderTokens }, senderChange)
|
|
185
|
+
: { tokens: senderTokens };
|
|
186
|
+
|
|
187
|
+
const [newSenderState, newReceiverState, newOwnerState, newTokenFactoryState, newTokenState] = await Promise.all([
|
|
188
|
+
// Update sender state
|
|
189
|
+
statedb.account.update(
|
|
190
|
+
senderState.address,
|
|
191
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
192
|
+
context
|
|
193
|
+
),
|
|
194
|
+
|
|
195
|
+
// Update receiver state
|
|
196
|
+
!isSameDid(senderState.address, receiver)
|
|
197
|
+
? statedb.account.updateOrCreate(
|
|
198
|
+
receiverState,
|
|
199
|
+
account.updateOrCreate(receiverState, { address: receiver, tokens: receiverTokens }, context),
|
|
200
|
+
context
|
|
201
|
+
)
|
|
202
|
+
: null,
|
|
203
|
+
|
|
204
|
+
// Update owner state
|
|
205
|
+
!isSameDid(senderState.address, ownerState.address) && !isSameDid(receiver, ownerState.address)
|
|
206
|
+
? statedb.account.update(
|
|
207
|
+
ownerState.address,
|
|
208
|
+
account.update(ownerState, { tokens: ownerTokens }, context),
|
|
209
|
+
context
|
|
210
|
+
)
|
|
211
|
+
: null,
|
|
212
|
+
|
|
213
|
+
// Update token factory state
|
|
214
|
+
statedb.tokenFactory.update(
|
|
215
|
+
tokenFactoryState.address,
|
|
216
|
+
tokenFactory.update(
|
|
217
|
+
tokenFactoryState,
|
|
218
|
+
{
|
|
219
|
+
currentSupply: new BN(tokenFactoryState.currentSupply).sub(new BN(amount)).toString(),
|
|
220
|
+
reserveBalance: new BN(tokenFactoryState.reserveBalance).sub(new BN(reserveAmount)).toString(),
|
|
221
|
+
},
|
|
222
|
+
context
|
|
223
|
+
),
|
|
224
|
+
context
|
|
225
|
+
),
|
|
226
|
+
|
|
227
|
+
statedb.token.update(
|
|
228
|
+
tokenAddress,
|
|
229
|
+
token.update(
|
|
230
|
+
tokenState,
|
|
231
|
+
{
|
|
232
|
+
totalSupply: new BN(tokenState.totalSupply).sub(new BN(amount)).toString(),
|
|
233
|
+
},
|
|
234
|
+
context
|
|
235
|
+
),
|
|
236
|
+
context
|
|
237
|
+
),
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
context.senderState = newSenderState;
|
|
241
|
+
context.receiverState = newReceiverState;
|
|
242
|
+
context.ownerState = newOwnerState;
|
|
243
|
+
context.tokenFactoryState = newTokenFactoryState;
|
|
244
|
+
context.tokenState = newTokenState;
|
|
245
|
+
|
|
246
|
+
await updateVaults();
|
|
247
|
+
|
|
248
|
+
next();
|
|
249
|
+
},
|
|
250
|
+
{ persistError: true }
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
runner.use(pipes.VerifyStateDiff());
|
|
254
|
+
|
|
255
|
+
module.exports = runner;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const isEmpty = require('empty-value');
|
|
2
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
3
|
+
const { Joi } = require('@arcblock/validator');
|
|
4
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
5
|
+
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
6
|
+
const { account, token, tokenFactory } = require('@ocap/state');
|
|
7
|
+
const { toTokenFactoryAddress, toTokenAddress } = require('@arcblock/did-util');
|
|
8
|
+
const { applyTokenChange } = require('../../util');
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line global-require, import/order
|
|
11
|
+
const debug = require('debug')(`${require('../../../package.json').name}:create-token-factory`);
|
|
12
|
+
|
|
13
|
+
const { decodeAnySafe } = require('../../util');
|
|
14
|
+
|
|
15
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
16
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
17
|
+
|
|
18
|
+
const runner = new Runner();
|
|
19
|
+
|
|
20
|
+
runner.use(pipes.VerifyMultiSig(0));
|
|
21
|
+
|
|
22
|
+
const schema = Joi.object({
|
|
23
|
+
address: Joi.DID().prefix().role('ROLE_TOKEN_FACTORY').required(),
|
|
24
|
+
feeRate: Joi.number().min(0).max(2000).required(),
|
|
25
|
+
curve: tokenFactory.curveSchema.required(),
|
|
26
|
+
reserveAddress: Joi.DID().prefix().role('ROLE_TOKEN').required(),
|
|
27
|
+
token: Joi.object({
|
|
28
|
+
name: Joi.string().min(1).max(32).required(),
|
|
29
|
+
description: Joi.string().min(1).max(128).required(),
|
|
30
|
+
symbol: Joi.string().min(2).max(6).uppercase().required(),
|
|
31
|
+
unit: Joi.string().min(1).max(6).lowercase().required(),
|
|
32
|
+
decimal: Joi.number().min(6).max(18).required(),
|
|
33
|
+
icon: Joi.string().optional().allow(null).valid(''),
|
|
34
|
+
}).required(),
|
|
35
|
+
data: Joi.any().optional().allow(null),
|
|
36
|
+
}).options({ stripUnknown: true, noDefaults: false });
|
|
37
|
+
|
|
38
|
+
// 1. verify itx
|
|
39
|
+
runner.use((context, next) => {
|
|
40
|
+
const { error, value } = schema.validate(context.itx);
|
|
41
|
+
if (error) {
|
|
42
|
+
return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// filter by curve type
|
|
46
|
+
context.itx.curve = value.curve;
|
|
47
|
+
|
|
48
|
+
return next();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Ensure factory and token address is valid
|
|
52
|
+
runner.use(
|
|
53
|
+
pipes.VerifyInfo([
|
|
54
|
+
{
|
|
55
|
+
error: 'INVALID_TOKEN',
|
|
56
|
+
message: 'Token address is not valid',
|
|
57
|
+
fn: (context) => {
|
|
58
|
+
return toTokenAddress({ ...context.itx.token, address: '' }) === context.itx.token.address;
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
error: 'INVALID_TOKEN_FACTORY',
|
|
63
|
+
message: 'Token factory address is not valid',
|
|
64
|
+
fn: (context) => {
|
|
65
|
+
const itx = cloneDeep(context.itx);
|
|
66
|
+
itx.data = decodeAnySafe(itx.data);
|
|
67
|
+
itx.address = '';
|
|
68
|
+
|
|
69
|
+
return toTokenFactoryAddress(itx) === context.itx.address;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Ensure reserve address is config.token.address
|
|
76
|
+
runner.use(
|
|
77
|
+
pipes.VerifyInfo([
|
|
78
|
+
{
|
|
79
|
+
error: 'INVALID_RESERVE_ADDRESS',
|
|
80
|
+
message: 'Reserve address is not valid',
|
|
81
|
+
fn: (context) => context.itx.reserveAddress === context.config.token.address,
|
|
82
|
+
},
|
|
83
|
+
])
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Ensure token factory not exist
|
|
87
|
+
runner.use(pipes.ExtractState({ from: 'itx.address', to: 'tokenFactoryState', table: 'tokenFactory', status: 'OK' }));
|
|
88
|
+
runner.use(
|
|
89
|
+
pipes.VerifyInfo([
|
|
90
|
+
{
|
|
91
|
+
error: 'DUPLICATE_TOKEN_FACTORY',
|
|
92
|
+
message: 'Token factory address already exists on chain',
|
|
93
|
+
fn: (context) => isEmpty(context.tokenFactoryState),
|
|
94
|
+
},
|
|
95
|
+
])
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Ensure token not exist
|
|
99
|
+
runner.use(pipes.ExtractState({ from: 'itx.token.address', to: 'tokenState', table: 'token', status: 'OK' }));
|
|
100
|
+
runner.use(
|
|
101
|
+
pipes.VerifyInfo([
|
|
102
|
+
{
|
|
103
|
+
error: 'DUPLICATE_TOKEN',
|
|
104
|
+
message: 'Token address already exists on chain',
|
|
105
|
+
fn: (context) => isEmpty(context.tokenState),
|
|
106
|
+
},
|
|
107
|
+
])
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Ensure sender exist
|
|
111
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
112
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
113
|
+
|
|
114
|
+
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState'] }));
|
|
115
|
+
|
|
116
|
+
// Ensure uniqueness of token symbol
|
|
117
|
+
runner.use(
|
|
118
|
+
async function EnsureTokenSymbol(context, next) {
|
|
119
|
+
const { symbol } = context.config.token;
|
|
120
|
+
const itxToken = context.itx.token;
|
|
121
|
+
if (symbol.toLowerCase() === itxToken.symbol.toLowerCase()) {
|
|
122
|
+
return next(new Error('DUPLICATE_SYMBOL', `Token symbol can not be ${symbol}`));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const exist = await context.statedb.token.existBySymbol(itxToken.symbol, context);
|
|
126
|
+
if (exist) {
|
|
127
|
+
return next(new Error('DUPLICATE_SYMBOL', 'Token symbol already exists'));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return next();
|
|
131
|
+
},
|
|
132
|
+
{ persistError: true }
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Ensure tx fee and gas
|
|
136
|
+
runner.use(
|
|
137
|
+
EnsureTxGas(() => {
|
|
138
|
+
return { create: 1, update: 2, payment: 0 };
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
142
|
+
|
|
143
|
+
// Save context snapshot before updating states
|
|
144
|
+
runner.use(pipes.TakeStateSnapshot());
|
|
145
|
+
|
|
146
|
+
// Update sender state, token factory state
|
|
147
|
+
runner.use(
|
|
148
|
+
async (context, next) => {
|
|
149
|
+
const { tx, itx, statedb, senderState, senderChange, updateVaults } = context;
|
|
150
|
+
const data = decodeAnySafe(itx.data);
|
|
151
|
+
const owner = senderState.address;
|
|
152
|
+
|
|
153
|
+
const itxToken = itx.token;
|
|
154
|
+
|
|
155
|
+
const { tokens: senderTokens = {} } = senderState;
|
|
156
|
+
|
|
157
|
+
const senderUpdates = senderChange
|
|
158
|
+
? applyTokenChange({ tokens: senderTokens }, senderChange)
|
|
159
|
+
: { tokens: senderTokens };
|
|
160
|
+
|
|
161
|
+
const [newSenderState, newTokenState, newTokenFactoryState] = await Promise.all([
|
|
162
|
+
statedb.account.update(
|
|
163
|
+
senderState.address,
|
|
164
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
165
|
+
context
|
|
166
|
+
),
|
|
167
|
+
|
|
168
|
+
statedb.token.create(
|
|
169
|
+
itxToken.address,
|
|
170
|
+
token.create(
|
|
171
|
+
{
|
|
172
|
+
...cloneDeep(itxToken),
|
|
173
|
+
totalSupply: '0',
|
|
174
|
+
initialSupply: '0',
|
|
175
|
+
issuer: owner,
|
|
176
|
+
tokenFactoryAddress: itx.address,
|
|
177
|
+
},
|
|
178
|
+
context
|
|
179
|
+
),
|
|
180
|
+
context
|
|
181
|
+
),
|
|
182
|
+
|
|
183
|
+
statedb.tokenFactory.create(
|
|
184
|
+
itx.address,
|
|
185
|
+
tokenFactory.create({ ...cloneDeep(itx), owner, tokenAddress: itxToken.address, data }, context),
|
|
186
|
+
context
|
|
187
|
+
),
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
await updateVaults();
|
|
191
|
+
|
|
192
|
+
context.senderState = newSenderState;
|
|
193
|
+
context.tokenState = newTokenState;
|
|
194
|
+
context.tokenFactoryState = newTokenFactoryState;
|
|
195
|
+
|
|
196
|
+
debug('create token factory', newTokenFactoryState);
|
|
197
|
+
|
|
198
|
+
next();
|
|
199
|
+
},
|
|
200
|
+
{ persistError: true }
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
runner.use(pipes.VerifyStateDiff());
|
|
204
|
+
|
|
205
|
+
module.exports = runner;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
2
|
+
const { Joi, schemas } = require('@arcblock/validator');
|
|
3
|
+
const { BN, isSameDid, fromUnitToToken } = require('@ocap/util');
|
|
4
|
+
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
5
|
+
const { account, tokenFactory, token } = require('@ocap/state');
|
|
6
|
+
|
|
7
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
8
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
9
|
+
const CalcReserve = require('./pipes/calc-reserve');
|
|
10
|
+
const { applyTokenChange } = require('../../util');
|
|
11
|
+
|
|
12
|
+
const runner = new Runner();
|
|
13
|
+
|
|
14
|
+
runner.use(pipes.VerifyMultiSig(0));
|
|
15
|
+
|
|
16
|
+
const schema = Joi.object({
|
|
17
|
+
tokenFactory: Joi.DID().prefix().role('ROLE_TOKEN_FACTORY').required(),
|
|
18
|
+
receiver: schemas.tokenHolder.required(),
|
|
19
|
+
amount: Joi.BN().greater(0).required(),
|
|
20
|
+
maxReserve: Joi.alternatives().try(Joi.BN().greater(0), Joi.equal('')).optional(),
|
|
21
|
+
data: Joi.any().optional().allow(null),
|
|
22
|
+
}).options({ stripUnknown: true, noDefaults: false });
|
|
23
|
+
|
|
24
|
+
// verify itx
|
|
25
|
+
runner.use(({ itx }, next) => {
|
|
26
|
+
const { error } = schema.validate(itx);
|
|
27
|
+
if (error) {
|
|
28
|
+
return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
|
|
29
|
+
}
|
|
30
|
+
return next();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// verify token factory
|
|
34
|
+
runner.use(
|
|
35
|
+
pipes.ExtractState({
|
|
36
|
+
from: 'itx.tokenFactory',
|
|
37
|
+
to: 'tokenFactoryState',
|
|
38
|
+
status: 'INVALID_TOKEN_FACTORY',
|
|
39
|
+
table: 'tokenFactory',
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// ensure sender
|
|
44
|
+
runner.use(
|
|
45
|
+
pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
|
|
46
|
+
);
|
|
47
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
48
|
+
|
|
49
|
+
// ensure receiver
|
|
50
|
+
runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'OK', table: 'account' }));
|
|
51
|
+
|
|
52
|
+
// ensure owner
|
|
53
|
+
runner.use(
|
|
54
|
+
pipes.ExtractState({
|
|
55
|
+
from: 'tokenFactoryState.owner',
|
|
56
|
+
to: 'ownerState',
|
|
57
|
+
status: 'INVALID_OWNER_STATE',
|
|
58
|
+
table: 'account',
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// verify blocked
|
|
63
|
+
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState', 'receiverState'] }));
|
|
64
|
+
|
|
65
|
+
// ensure token state
|
|
66
|
+
runner.use(
|
|
67
|
+
pipes.ExtractState({
|
|
68
|
+
from: 'tokenFactoryState.tokenAddress',
|
|
69
|
+
to: 'tokenState',
|
|
70
|
+
status: 'INVALID_TOKEN',
|
|
71
|
+
table: 'token',
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// calculate reserve amount
|
|
76
|
+
runner.use(
|
|
77
|
+
CalcReserve({ tokenFactoryKey: 'tokenFactoryState', tokenStateKey: 'tokenState', reserveKey: 'reserveAmount' })
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// verify slippage
|
|
81
|
+
runner.use((context, next) => {
|
|
82
|
+
const { reserveAmount, reserveFee } = context;
|
|
83
|
+
const { maxReserve } = context.itx;
|
|
84
|
+
|
|
85
|
+
const actual = new BN(reserveAmount).add(new BN(reserveFee || '0'));
|
|
86
|
+
if (maxReserve && actual.gt(new BN(maxReserve))) {
|
|
87
|
+
return next(
|
|
88
|
+
new Error(
|
|
89
|
+
'SLIPPAGE_EXCEEDED',
|
|
90
|
+
`Mint token failed due to price movement. Expected maximum: ${fromUnitToToken(maxReserve)}, actual: ${fromUnitToToken(actual)}. Try increasing your maxReserve.`
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
next();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// verify sender balance
|
|
99
|
+
runner.use((context, next) => {
|
|
100
|
+
const { tokenFactoryState } = context;
|
|
101
|
+
context.tokenConditions = {
|
|
102
|
+
owner: context.senderState.address,
|
|
103
|
+
tokens: [
|
|
104
|
+
{
|
|
105
|
+
address: tokenFactoryState.reserveAddress,
|
|
106
|
+
value: new BN(context.reserveAmount).add(new BN(context.reserveFee || '0')).toString(),
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
next();
|
|
111
|
+
});
|
|
112
|
+
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'tokenConditions' }));
|
|
113
|
+
|
|
114
|
+
// Ensure tx fee and gas
|
|
115
|
+
runner.use(
|
|
116
|
+
EnsureTxGas((context) => {
|
|
117
|
+
const result = { create: 0, update: 3, payment: 0 };
|
|
118
|
+
|
|
119
|
+
if (context.receiverState) {
|
|
120
|
+
result.update += 1;
|
|
121
|
+
}
|
|
122
|
+
if (context.reserveFee) {
|
|
123
|
+
result.update += 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
130
|
+
|
|
131
|
+
// Save context snapshot before updating states
|
|
132
|
+
runner.use(pipes.TakeStateSnapshot());
|
|
133
|
+
|
|
134
|
+
// update statedb: transfer tokens to new owner
|
|
135
|
+
runner.use(
|
|
136
|
+
async (context, next) => {
|
|
137
|
+
const {
|
|
138
|
+
tx,
|
|
139
|
+
senderState,
|
|
140
|
+
receiverState,
|
|
141
|
+
ownerState,
|
|
142
|
+
statedb,
|
|
143
|
+
senderChange,
|
|
144
|
+
updateVaults,
|
|
145
|
+
tokenFactoryState,
|
|
146
|
+
tokenState,
|
|
147
|
+
reserveAmount,
|
|
148
|
+
reserveFee,
|
|
149
|
+
itx,
|
|
150
|
+
} = context;
|
|
151
|
+
const { amount, receiver } = itx;
|
|
152
|
+
const { reserveAddress, tokenAddress } = tokenFactoryState;
|
|
153
|
+
|
|
154
|
+
const { tokens: senderTokens = {} } = senderState;
|
|
155
|
+
const { tokens: receiverTokens = {} } = receiverState || {};
|
|
156
|
+
const { tokens: ownerTokens = {} } = ownerState;
|
|
157
|
+
|
|
158
|
+
senderTokens[reserveAddress] = new BN(senderTokens[reserveAddress]).sub(new BN(reserveAmount)).toString();
|
|
159
|
+
receiverTokens[tokenAddress] = new BN(receiverTokens[tokenAddress] || '0').add(new BN(amount)).toString();
|
|
160
|
+
|
|
161
|
+
// merge receiver tokens to sender tokens
|
|
162
|
+
if (isSameDid(senderState.address, receiver)) {
|
|
163
|
+
senderTokens[tokenAddress] = receiverTokens[tokenAddress];
|
|
164
|
+
}
|
|
165
|
+
if (reserveFee) {
|
|
166
|
+
const fee = new BN(reserveFee);
|
|
167
|
+
senderTokens[reserveAddress] = new BN(senderTokens[reserveAddress]).sub(fee).toString();
|
|
168
|
+
|
|
169
|
+
if (isSameDid(receiver, ownerState.address)) {
|
|
170
|
+
receiverTokens[reserveAddress] = new BN(receiverTokens[reserveAddress] || '0').add(fee).toString();
|
|
171
|
+
} else {
|
|
172
|
+
ownerTokens[reserveAddress] = new BN(ownerTokens[reserveAddress] || '0').add(fee).toString();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const senderUpdates = senderChange
|
|
177
|
+
? applyTokenChange({ tokens: senderTokens }, senderChange)
|
|
178
|
+
: { tokens: senderTokens };
|
|
179
|
+
|
|
180
|
+
const [newSenderState, newReceiverState, newOwnerState, newTokenFactoryState, newTokenState] = await Promise.all([
|
|
181
|
+
// Update sender state
|
|
182
|
+
statedb.account.update(
|
|
183
|
+
senderState.address,
|
|
184
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
185
|
+
context
|
|
186
|
+
),
|
|
187
|
+
|
|
188
|
+
// Update receiver state
|
|
189
|
+
!isSameDid(senderState.address, receiver)
|
|
190
|
+
? statedb.account.updateOrCreate(
|
|
191
|
+
receiverState,
|
|
192
|
+
account.updateOrCreate(receiverState, { address: receiver, tokens: receiverTokens }, context),
|
|
193
|
+
context
|
|
194
|
+
)
|
|
195
|
+
: null,
|
|
196
|
+
|
|
197
|
+
// Update owner state
|
|
198
|
+
!isSameDid(senderState.address, ownerState.address) && !isSameDid(receiver, ownerState.address)
|
|
199
|
+
? statedb.account.update(
|
|
200
|
+
ownerState.address,
|
|
201
|
+
account.update(ownerState, { tokens: ownerTokens }, context),
|
|
202
|
+
context
|
|
203
|
+
)
|
|
204
|
+
: null,
|
|
205
|
+
|
|
206
|
+
// Update token factory state
|
|
207
|
+
statedb.tokenFactory.update(
|
|
208
|
+
tokenFactoryState.address,
|
|
209
|
+
tokenFactory.update(
|
|
210
|
+
tokenFactoryState,
|
|
211
|
+
{
|
|
212
|
+
currentSupply: new BN(tokenFactoryState.currentSupply).add(new BN(amount)).toString(),
|
|
213
|
+
reserveBalance: new BN(tokenFactoryState.reserveBalance).add(new BN(reserveAmount)).toString(),
|
|
214
|
+
},
|
|
215
|
+
context
|
|
216
|
+
),
|
|
217
|
+
context
|
|
218
|
+
),
|
|
219
|
+
|
|
220
|
+
statedb.token.update(
|
|
221
|
+
tokenAddress,
|
|
222
|
+
token.update(
|
|
223
|
+
tokenState,
|
|
224
|
+
{
|
|
225
|
+
totalSupply: new BN(tokenState.totalSupply).add(new BN(amount)).toString(),
|
|
226
|
+
},
|
|
227
|
+
context
|
|
228
|
+
),
|
|
229
|
+
context
|
|
230
|
+
),
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
context.senderState = newSenderState;
|
|
234
|
+
context.receiverState = newReceiverState;
|
|
235
|
+
context.ownerState = newOwnerState;
|
|
236
|
+
context.tokenFactoryState = newTokenFactoryState;
|
|
237
|
+
context.tokenState = newTokenState;
|
|
238
|
+
|
|
239
|
+
await updateVaults();
|
|
240
|
+
|
|
241
|
+
next();
|
|
242
|
+
},
|
|
243
|
+
{ persistError: true }
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
runner.use(pipes.VerifyStateDiff());
|
|
247
|
+
|
|
248
|
+
module.exports = runner;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
2
|
+
const get = require('lodash/get');
|
|
3
|
+
const set = require('lodash/set');
|
|
4
|
+
const { calcCost, calcFee } = require('@ocap/util/lib/curve');
|
|
5
|
+
|
|
6
|
+
module.exports =
|
|
7
|
+
({
|
|
8
|
+
tokenFactoryKey = 'tokenFactoryState',
|
|
9
|
+
tokenStateKey = 'tokenState',
|
|
10
|
+
senderStateKey = 'senderState',
|
|
11
|
+
reserveKey = 'reserveAmount',
|
|
12
|
+
feeKey = 'reserveFee',
|
|
13
|
+
direction = 'mint',
|
|
14
|
+
} = {}) =>
|
|
15
|
+
(context, next) => {
|
|
16
|
+
const tokenFactoryState = get(context, tokenFactoryKey);
|
|
17
|
+
const tokenState = get(context, tokenStateKey);
|
|
18
|
+
const senderState = get(context, senderStateKey);
|
|
19
|
+
|
|
20
|
+
if (!tokenFactoryState) {
|
|
21
|
+
return next(new Error('INVALID_TOKEN_FACTORY', 'Token factory state not found'));
|
|
22
|
+
}
|
|
23
|
+
if (!tokenState) {
|
|
24
|
+
return next(new Error('INVALID_TOKEN', 'Token state not found'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { curve, currentSupply, feeRate } = tokenFactoryState;
|
|
28
|
+
const { decimal } = tokenState;
|
|
29
|
+
const { amount } = context.itx;
|
|
30
|
+
|
|
31
|
+
const reserveAmount = calcCost({ amount, decimal, currentSupply, direction, curve });
|
|
32
|
+
set(context, reserveKey, reserveAmount.toString());
|
|
33
|
+
|
|
34
|
+
if (senderState.address !== tokenFactoryState.owner) {
|
|
35
|
+
const reserveFee = calcFee({ reserveAmount, feeRate });
|
|
36
|
+
set(context, feeKey, reserveFee.toString());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
next();
|
|
40
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { Joi } = require('@arcblock/validator');
|
|
2
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
3
|
+
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
4
|
+
const { account, tokenFactory } = require('@ocap/state');
|
|
5
|
+
const { applyTokenChange } = require('../../util');
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line global-require, import/order
|
|
8
|
+
const debug = require('debug')(`${require('../../../package.json').name}:update-token-factory`);
|
|
9
|
+
|
|
10
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
11
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
12
|
+
|
|
13
|
+
const runner = new Runner();
|
|
14
|
+
|
|
15
|
+
runner.use(pipes.VerifyMultiSig(0));
|
|
16
|
+
|
|
17
|
+
const schema = Joi.object({
|
|
18
|
+
address: Joi.DID().prefix().role('ROLE_TOKEN_FACTORY').required(),
|
|
19
|
+
feeRate: Joi.number().min(0).max(2000).required(),
|
|
20
|
+
data: Joi.any().optional().allow(null),
|
|
21
|
+
}).options({ stripUnknown: true, noDefaults: false });
|
|
22
|
+
|
|
23
|
+
// 1. verify itx
|
|
24
|
+
runner.use((context, next) => {
|
|
25
|
+
const { error } = schema.validate(context.itx);
|
|
26
|
+
if (error) {
|
|
27
|
+
return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return next();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ensure token factory exists
|
|
34
|
+
runner.use(
|
|
35
|
+
pipes.ExtractState({
|
|
36
|
+
from: 'itx.address',
|
|
37
|
+
to: 'tokenFactoryState',
|
|
38
|
+
table: 'tokenFactory',
|
|
39
|
+
status: 'INVALID_TOKEN_FACTORY',
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// verify owner
|
|
44
|
+
runner.use((context, next) => {
|
|
45
|
+
const { tx, tokenFactoryState } = context;
|
|
46
|
+
if (tx.from !== tokenFactoryState.owner) {
|
|
47
|
+
return next(new Error('FORBIDDEN', 'Token factory can only be updated by owner'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return next();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Ensure sender
|
|
54
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
55
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
56
|
+
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState'] }));
|
|
57
|
+
|
|
58
|
+
// Ensure tx fee and gas
|
|
59
|
+
runner.use(
|
|
60
|
+
EnsureTxGas(() => {
|
|
61
|
+
return { create: 0, update: 2, payment: 0 };
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
65
|
+
|
|
66
|
+
// Save context snapshot before updating states
|
|
67
|
+
runner.use(pipes.TakeStateSnapshot());
|
|
68
|
+
|
|
69
|
+
// Update sender state, token factory state
|
|
70
|
+
runner.use(
|
|
71
|
+
async (context, next) => {
|
|
72
|
+
const { tx, itx, statedb, senderState, tokenFactoryState, senderChange, updateVaults } = context;
|
|
73
|
+
|
|
74
|
+
const { tokens: senderTokens = {} } = senderState;
|
|
75
|
+
|
|
76
|
+
const senderUpdates = senderChange
|
|
77
|
+
? applyTokenChange({ tokens: senderTokens }, senderChange)
|
|
78
|
+
: { tokens: senderTokens };
|
|
79
|
+
|
|
80
|
+
const [newSenderState, newTokenFactoryState] = await Promise.all([
|
|
81
|
+
statedb.account.update(
|
|
82
|
+
senderState.address,
|
|
83
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
84
|
+
context
|
|
85
|
+
),
|
|
86
|
+
|
|
87
|
+
statedb.tokenFactory.update(
|
|
88
|
+
tokenFactoryState.address,
|
|
89
|
+
tokenFactory.update(tokenFactoryState, { feeRate: itx.feeRate }, context),
|
|
90
|
+
context
|
|
91
|
+
),
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
await updateVaults();
|
|
95
|
+
|
|
96
|
+
context.senderState = newSenderState;
|
|
97
|
+
context.tokenFactoryState = newTokenFactoryState;
|
|
98
|
+
|
|
99
|
+
debug('update token factory', newTokenFactoryState);
|
|
100
|
+
|
|
101
|
+
next();
|
|
102
|
+
},
|
|
103
|
+
{ persistError: true }
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
runner.use(pipes.VerifyStateDiff());
|
|
107
|
+
|
|
108
|
+
module.exports = runner;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.22.0",
|
|
7
7
|
"description": "Predefined tx pipeline sets to execute certain type of transactions",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -18,19 +18,19 @@
|
|
|
18
18
|
"empty-value": "^1.0.1",
|
|
19
19
|
"lodash": "^4.17.21",
|
|
20
20
|
"url-join": "^4.0.1",
|
|
21
|
-
"@arcblock/did": "1.
|
|
22
|
-
"@arcblock/did-util": "1.
|
|
23
|
-
"@arcblock/jwt": "1.
|
|
24
|
-
"@arcblock/validator": "1.
|
|
25
|
-
"@ocap/asset": "1.
|
|
26
|
-
"@ocap/client": "1.
|
|
27
|
-
"@ocap/mcrypto": "1.
|
|
28
|
-
"@ocap/
|
|
29
|
-
"@ocap/
|
|
30
|
-
"@ocap/
|
|
31
|
-
"@ocap/tx-pipeline": "1.
|
|
32
|
-
"@ocap/util": "1.
|
|
33
|
-
"@ocap/wallet": "1.
|
|
21
|
+
"@arcblock/did": "1.22.0",
|
|
22
|
+
"@arcblock/did-util": "1.22.0",
|
|
23
|
+
"@arcblock/jwt": "1.22.0",
|
|
24
|
+
"@arcblock/validator": "1.22.0",
|
|
25
|
+
"@ocap/asset": "1.22.0",
|
|
26
|
+
"@ocap/client": "1.22.0",
|
|
27
|
+
"@ocap/mcrypto": "1.22.0",
|
|
28
|
+
"@ocap/state": "1.22.0",
|
|
29
|
+
"@ocap/merkle-tree": "1.22.0",
|
|
30
|
+
"@ocap/message": "1.22.0",
|
|
31
|
+
"@ocap/tx-pipeline": "1.22.0",
|
|
32
|
+
"@ocap/util": "1.22.0",
|
|
33
|
+
"@ocap/wallet": "1.22.0"
|
|
34
34
|
},
|
|
35
35
|
"resolutions": {
|
|
36
36
|
"bn.js": "5.2.1",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"jest": "^29.7.0",
|
|
41
41
|
"start-server-and-test": "^1.14.0",
|
|
42
|
-
"@ocap/e2e-test": "1.
|
|
43
|
-
"@ocap/statedb-memory": "1.
|
|
42
|
+
"@ocap/e2e-test": "1.22.0",
|
|
43
|
+
"@ocap/statedb-memory": "1.22.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"lint": "eslint tests lib",
|