@msafe/sui3-sdk 0.0.35 → 0.0.36
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/dist/index.cjs +141 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -8
- package/dist/index.d.ts +17 -8
- package/dist/index.js +141 -33
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/backend/BackendImpl.ts +16 -3
- package/src/backend/interface.ts +3 -0
- package/src/core/MSafeAccount.ts +61 -30
- package/src/globals/const.ts +0 -1
- package/src/simulate/simulator.ts +131 -0
- package/src/types/msafe.ts +9 -6
package/src/core/MSafeAccount.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { appHelpers } from '@msafe/sui-app-store';
|
|
1
2
|
import {
|
|
2
3
|
CoinTransferIntention,
|
|
4
|
+
IGasOption,
|
|
3
5
|
IMSafeConfig,
|
|
4
6
|
IProposeIntentionRequest,
|
|
5
7
|
MultiSigAccount,
|
|
@@ -21,6 +23,7 @@ import { TransactionBlock } from '@mysten/sui.js/transactions';
|
|
|
21
23
|
import { normalizeStructTag } from '@mysten/sui.js/utils';
|
|
22
24
|
|
|
23
25
|
import { MSafeGlobals } from '@/globals/MSafeGlobals';
|
|
26
|
+
import { Simulator } from '@/simulate/simulator';
|
|
24
27
|
import { OwnedCoin } from '@/types/assets';
|
|
25
28
|
import { Pagination, PendingTx, SimulationResult } from '@/types/msafe';
|
|
26
29
|
import { CoinHelper } from '@/utils';
|
|
@@ -32,6 +35,8 @@ export class MSafeAccount {
|
|
|
32
35
|
|
|
33
36
|
private coinHelper: CoinHelper;
|
|
34
37
|
|
|
38
|
+
private simulator: Simulator;
|
|
39
|
+
|
|
35
40
|
constructor(
|
|
36
41
|
public readonly globals: MSafeGlobals,
|
|
37
42
|
public readonly info: IMSafeConfig,
|
|
@@ -42,6 +47,7 @@ export class MSafeAccount {
|
|
|
42
47
|
creationNonce: info.creationNonce,
|
|
43
48
|
});
|
|
44
49
|
this.coinHelper = new CoinHelper(this.suiClient);
|
|
50
|
+
this.simulator = new Simulator(globals);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
static async New(globals: MSafeGlobals, address: string) {
|
|
@@ -78,7 +84,6 @@ export class MSafeAccount {
|
|
|
78
84
|
return getAllOwnedObjects(this.suiClient, this.address, filterCoinObjectOptions);
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
// TODO: Calculate the votes
|
|
82
87
|
async pendingTransaction(): Promise<PendingTx | undefined> {
|
|
83
88
|
const res = await this.backend.getPendingTransactions({ msafeAddress: this.address });
|
|
84
89
|
if (!res.pending) {
|
|
@@ -110,7 +115,6 @@ export class MSafeAccount {
|
|
|
110
115
|
};
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
// TODO: Calculate the votes.
|
|
114
118
|
async historyTransaction(pagination?: Pagination) {
|
|
115
119
|
return this.backend.getHistoryTransactions({
|
|
116
120
|
msafeAddress: this.address,
|
|
@@ -130,6 +134,23 @@ export class MSafeAccount {
|
|
|
130
134
|
return this.backend.getNextSequenceNumber(this.address);
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
async simulateIntention(request: Omit<IProposeIntentionRequest, 'msafeAddress' | 'signature'>) {
|
|
138
|
+
const appHelper = appHelpers.getAppHelper(request.application);
|
|
139
|
+
if (!appHelper) {
|
|
140
|
+
throw new Error(`Can't find app helper for application ${request.application}`);
|
|
141
|
+
}
|
|
142
|
+
const txb = await appHelper.build({
|
|
143
|
+
intentionData: request.intention,
|
|
144
|
+
txType: request.txType,
|
|
145
|
+
txSubType: request.txSubType,
|
|
146
|
+
suiClient: this.globals.suiClient,
|
|
147
|
+
account: {
|
|
148
|
+
address: this.address,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
return this.simulator.simulate({ txb, sender: this.address });
|
|
152
|
+
}
|
|
153
|
+
|
|
133
154
|
async proposeIntention(input: Omit<IProposeIntentionRequest, 'signature' | 'msafeAddress'>) {
|
|
134
155
|
const message = SigningMessageHelper.proposeIntentionMessage({
|
|
135
156
|
msafeAddress: this.address,
|
|
@@ -140,13 +161,24 @@ export class MSafeAccount {
|
|
|
140
161
|
messageStr: message,
|
|
141
162
|
});
|
|
142
163
|
|
|
143
|
-
|
|
164
|
+
return this.backend.proposeIntention({
|
|
144
165
|
...input,
|
|
145
166
|
msafeAddress: this.address,
|
|
146
167
|
signature: signature.signature,
|
|
147
168
|
});
|
|
148
169
|
}
|
|
149
170
|
|
|
171
|
+
async simulateCoinTransferIntention(intention: CoinTransferIntention) {
|
|
172
|
+
const sn = await this.nextSequenceNumber();
|
|
173
|
+
return this.simulateIntention({
|
|
174
|
+
application: TransactionDefaultApplication,
|
|
175
|
+
txType: TransactionType.Assets,
|
|
176
|
+
txSubType: TransactionSubTypes.assets.coin.send,
|
|
177
|
+
sequenceNumber: sn,
|
|
178
|
+
intention,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
150
182
|
async proposeCoinTransferIntention(intention: CoinTransferIntention) {
|
|
151
183
|
const sn = await this.nextSequenceNumber();
|
|
152
184
|
return this.proposeIntention({
|
|
@@ -234,37 +266,36 @@ export class MSafeAccount {
|
|
|
234
266
|
});
|
|
235
267
|
}
|
|
236
268
|
|
|
237
|
-
async
|
|
238
|
-
|
|
239
|
-
}
|
|
269
|
+
async simulateBuildTransaction(): Promise<SimulationResult> {
|
|
270
|
+
const intention = await this.backend.getNextIntention({ msafeAddress: this.address });
|
|
240
271
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
async simulateIntention(): Promise<SimulationResult> {
|
|
246
|
-
// Todo, move to API
|
|
247
|
-
const txb = new TransactionBlock(); // await buildIntentionTransaction(this.suiClient, intention, this.address);
|
|
248
|
-
txb.setSender(this.address);
|
|
249
|
-
if (!txb.blockData.gasConfig.price) {
|
|
250
|
-
const refGas = await this.suiClient.getReferenceGasPrice();
|
|
251
|
-
txb.setGasPrice(refGas);
|
|
272
|
+
const appHelper = appHelpers.getAppHelper(intention.application);
|
|
273
|
+
if (!appHelper) {
|
|
274
|
+
throw new Error(`Can't find app helper for application ${intention.application}`);
|
|
252
275
|
}
|
|
253
|
-
const
|
|
254
|
-
|
|
276
|
+
const txb = await appHelper.build({
|
|
277
|
+
intentionData: intention.intention,
|
|
278
|
+
txType: intention.txType as TransactionType,
|
|
279
|
+
txSubType: intention.txSubType,
|
|
280
|
+
suiClient: this.globals.suiClient,
|
|
281
|
+
account: {
|
|
282
|
+
address: this.address,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
return this.simulator.simulate({ txb, sender: this.address });
|
|
286
|
+
}
|
|
255
287
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
288
|
+
async buildNextIntentionAndAddToPending(gasOption: IGasOption & { forceBuild: boolean }) {
|
|
289
|
+
return this.backend.buildNextIntentionAndAddToPending({
|
|
290
|
+
msafeAddress: this.address,
|
|
291
|
+
forceBuild: gasOption.forceBuild,
|
|
292
|
+
gasPrice: gasOption.gasPrice,
|
|
293
|
+
gasBudget: gasOption.gasBudget,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
260
296
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
errorMsg,
|
|
264
|
-
gasPrice,
|
|
265
|
-
gasUsed,
|
|
266
|
-
dryRunResult,
|
|
267
|
-
};
|
|
297
|
+
async skipNextFailedIntention() {
|
|
298
|
+
return this.backend.skipNextFailedIntention({ msafeAddress: this.address });
|
|
268
299
|
}
|
|
269
300
|
|
|
270
301
|
async executePendingTx(pending: PendingTx, isRejectTx: boolean = false) {
|
package/src/globals/const.ts
CHANGED
|
@@ -24,7 +24,6 @@ export interface MSafeConfigOptions {
|
|
|
24
24
|
url?: string;
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
export const MSAFE_APPLICATION = 'msafe';
|
|
28
27
|
|
|
29
28
|
export const TESTNET_RPC_URL = 'https://sui-testnet.blockvision.org/v1/2Sgk89ivT64MnKdcGzjmyjY2ndD';
|
|
30
29
|
export const MAINNET_RPC_URL = 'https://sui-mainnet.blockvision.org/v1/2Sgk7NPvqkd7mESYkxF01yX15l7';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { GasCostSummary } from '@mysten/sui.js/client';
|
|
2
|
+
import { TransactionBlock } from '@mysten/sui.js/transactions';
|
|
3
|
+
|
|
4
|
+
// Default to 120% o the reference gas price
|
|
5
|
+
import { MSafeGlobals } from '@/globals/MSafeGlobals';
|
|
6
|
+
import { SimulationResult } from '@/types';
|
|
7
|
+
|
|
8
|
+
// Gas price option can be either a multiplier or a fixed amount
|
|
9
|
+
export type GasPriceOption = MultiplierOption | FixedValueOption;
|
|
10
|
+
|
|
11
|
+
// Gas budget option can be either a multiplier or a fixed amount
|
|
12
|
+
export type GasBudgetOption = MultiplierOption | FixedValueOption;
|
|
13
|
+
|
|
14
|
+
export interface MultiplierOption {
|
|
15
|
+
multiplier: {
|
|
16
|
+
numerator: bigint;
|
|
17
|
+
denominator: bigint;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FixedValueOption {
|
|
22
|
+
fixedVal: bigint;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const GAS_SAFE_OVERHEAD = 1000n;
|
|
26
|
+
// Default to 120% of the reference gas price
|
|
27
|
+
export const DEF_GAS_PRICE_NUM = 120n;
|
|
28
|
+
export const DEF_GAS_PRICE_DEN = 100n;
|
|
29
|
+
|
|
30
|
+
export const DEF_GAS_PRICE_OPTION: GasPriceOption = {
|
|
31
|
+
multiplier: {
|
|
32
|
+
numerator: DEF_GAS_PRICE_NUM,
|
|
33
|
+
denominator: DEF_GAS_PRICE_DEN,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Default to 120% of the reference gas budget
|
|
38
|
+
export const DEF_GAS_BUDGET_NUM = 120n;
|
|
39
|
+
export const DEF_GAS_BUDGET_DEN = 100n;
|
|
40
|
+
|
|
41
|
+
export const DEF_GAS_BUDGET_OPTION: GasBudgetOption = {
|
|
42
|
+
multiplier: {
|
|
43
|
+
numerator: DEF_GAS_BUDGET_NUM,
|
|
44
|
+
denominator: DEF_GAS_BUDGET_DEN,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class Simulator {
|
|
49
|
+
constructor(private globals: MSafeGlobals) {}
|
|
50
|
+
|
|
51
|
+
public async simulate(
|
|
52
|
+
input: {
|
|
53
|
+
txb: TransactionBlock;
|
|
54
|
+
sender: string;
|
|
55
|
+
},
|
|
56
|
+
options: GasPriceOption = DEF_GAS_PRICE_OPTION,
|
|
57
|
+
): Promise<SimulationResult> {
|
|
58
|
+
const tx = this.copyTransactionBlock(input.txb);
|
|
59
|
+
|
|
60
|
+
const { suiClient } = this.globals;
|
|
61
|
+
|
|
62
|
+
const gasPrice = await this.getGasPrice(options);
|
|
63
|
+
tx.setGasPrice(gasPrice);
|
|
64
|
+
|
|
65
|
+
const inspectResult = await suiClient.devInspectTransactionBlock({ transactionBlock: tx, sender: input.sender });
|
|
66
|
+
const { gasUsed } = inspectResult.effects;
|
|
67
|
+
const gasBudget = this.toGasBudget(gasUsed, gasPrice);
|
|
68
|
+
const success = inspectResult.effects.status.status === 'success';
|
|
69
|
+
let error: string | undefined;
|
|
70
|
+
|
|
71
|
+
if (!success) {
|
|
72
|
+
error = inspectResult.effects.status.error;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
success,
|
|
77
|
+
gasPrice,
|
|
78
|
+
gasObject: inspectResult.effects.gasObject.reference,
|
|
79
|
+
gasUsed,
|
|
80
|
+
gasBudget,
|
|
81
|
+
simulationError: error,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async getGasPrice(option: GasPriceOption) {
|
|
86
|
+
if (this.isFixedValue(option)) {
|
|
87
|
+
return option.fixedVal;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const refGasPrice = await this.globals.suiClient.getReferenceGasPrice();
|
|
91
|
+
|
|
92
|
+
return this.applyGasOption(refGasPrice, option);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private toGasBudget(gasUsed: GasCostSummary, gasPrice: bigint) {
|
|
96
|
+
const { computationCost, storageCost } = gasUsed;
|
|
97
|
+
const safeOverhead = GAS_SAFE_OVERHEAD * gasPrice;
|
|
98
|
+
|
|
99
|
+
const baseComputationCostWithOverhead = BigInt(computationCost) + safeOverhead;
|
|
100
|
+
|
|
101
|
+
// do not subtract rebate for now, because it may cause normally tx gas to be lower than empty tx gas
|
|
102
|
+
// const gasBudget = baseComputationCostWithOverhead + BigInt(storageCost) - BigInt(storageRebate);
|
|
103
|
+
const gasBudget = baseComputationCostWithOverhead + BigInt(storageCost);
|
|
104
|
+
|
|
105
|
+
// Set the budget to max(computation, computation + storage - rebate)
|
|
106
|
+
return gasBudget > baseComputationCostWithOverhead ? gasBudget : baseComputationCostWithOverhead;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private isFixedValue(option: GasPriceOption | GasBudgetOption): option is FixedValueOption {
|
|
110
|
+
return 'fixedVal' in option;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private isMultiplier(option: GasPriceOption | GasBudgetOption): option is MultiplierOption {
|
|
114
|
+
return 'multiplier' in option;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private applyGasOption(val: bigint, option: GasPriceOption) {
|
|
118
|
+
if (this.isFixedValue(option)) {
|
|
119
|
+
return option.fixedVal;
|
|
120
|
+
}
|
|
121
|
+
if (this.isMultiplier(option)) {
|
|
122
|
+
const { multiplier } = option;
|
|
123
|
+
return (val * multiplier.numerator) / multiplier.denominator;
|
|
124
|
+
}
|
|
125
|
+
throw new Error('Sanity: Unknown gas price option');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private copyTransactionBlock(tx: TransactionBlock): TransactionBlock {
|
|
129
|
+
return TransactionBlock.from(tx.serialize());
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/types/msafe.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TransactionType, TxIntention } from '@msafe/sui3-utils';
|
|
2
|
-
import {
|
|
1
|
+
import { IGasOption, TransactionType, TxIntention } from '@msafe/sui3-utils';
|
|
2
|
+
import { GasCostSummary, SuiObjectRef } from '@mysten/sui.js/client';
|
|
3
3
|
import { SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
4
4
|
|
|
5
5
|
export interface PendingTx {
|
|
@@ -81,12 +81,15 @@ export interface ProposeIntention {
|
|
|
81
81
|
|
|
82
82
|
export interface SimulationResult {
|
|
83
83
|
success: boolean;
|
|
84
|
-
errorMsg?: string;
|
|
85
|
-
// gas price for the simulation
|
|
86
84
|
gasPrice: bigint;
|
|
87
|
-
// detailed gas cost summary
|
|
88
85
|
gasUsed: GasCostSummary;
|
|
89
|
-
|
|
86
|
+
gasBudget: bigint;
|
|
87
|
+
gasObject: SuiObjectRef;
|
|
88
|
+
simulationError?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface SimulationOption extends IGasOption {
|
|
92
|
+
simulation: boolean;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
export interface Pagination {
|