@msafe/sui3-sdk 0.0.35 → 0.0.37

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.
@@ -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
- await this.backend.proposeIntention({
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 buildNextIntentionAndAddToPending() {
238
- return this.backend.buildNextIntentionAndAddToPending({ msafeAddress: this.address });
239
- }
269
+ async simulateBuildTransaction(): Promise<SimulationResult> {
270
+ const intention = await this.backend.getNextIntention({ msafeAddress: this.address });
240
271
 
241
- async skipNextFailedIntention() {
242
- return this.backend.skipNextFailedIntention({ msafeAddress: this.address });
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 payload = await txb.build({ client: this.suiClient });
254
- const dryRunResult = await this.suiClient.dryRunTransactionBlock({ transactionBlock: payload });
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
- const success = dryRunResult.effects.status.status === 'success';
257
- const errorMsg = dryRunResult.effects.status.error;
258
- const gasPrice = BigInt(txb.blockData.gasConfig.price as string);
259
- const { gasUsed } = dryRunResult.effects;
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
- return {
262
- success,
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) {
@@ -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
+ }
@@ -1,5 +1,5 @@
1
- import { TransactionType, TxIntention } from '@msafe/sui3-utils';
2
- import { DryRunTransactionBlockResponse, GasCostSummary } from '@mysten/sui.js/client';
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
- dryRunResult: DryRunTransactionBlockResponse;
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 {