@msafe/sui3-sdk 1.0.20 → 1.0.22
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 +200 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +197 -89
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/MSafeAccount.ts +80 -70
- package/src/simulate/simulator.ts +19 -2
- package/src/utils/gasFunding.ts +125 -16
- package/src/utils/transaction.ts +32 -0
package/package.json
CHANGED
package/src/core/MSafeAccount.ts
CHANGED
|
@@ -25,9 +25,9 @@ import { OwnedCoin } from '@/types/assets';
|
|
|
25
25
|
import { Pagination, PendingTx, SimulationResult } from '@/types/msafe';
|
|
26
26
|
import { CoinHelper } from '@/utils';
|
|
27
27
|
import { HexToUint8Array } from '@/utils/buffer';
|
|
28
|
-
import { prepareGasFunding } from '@/utils/gasFunding';
|
|
28
|
+
import { parkedPayloadHex, prepareGasFunding } from '@/utils/gasFunding';
|
|
29
29
|
import { BatchObjectOptions, getAllOwnedObjects, type OwnedObjectData } from '@/utils/iter/object';
|
|
30
|
-
import { toSuiTransaction } from '@/utils/transaction';
|
|
30
|
+
import { getIntentionContent, toSuiTransaction, transactionFromContent } from '@/utils/transaction';
|
|
31
31
|
|
|
32
32
|
export class MSafeAccount {
|
|
33
33
|
public multiSig: MultiSigAccount;
|
|
@@ -138,34 +138,18 @@ export class MSafeAccount {
|
|
|
138
138
|
async simulateIntention(
|
|
139
139
|
request: Omit<IProposeIntentionRequest, 'msafeAddress' | 'signature'> & {
|
|
140
140
|
txb?: Transaction;
|
|
141
|
+
preferAddressBalance?: boolean;
|
|
142
|
+
excludeObjectIds?: Iterable<string>;
|
|
141
143
|
},
|
|
142
144
|
) {
|
|
143
|
-
|
|
144
|
-
if (request.txb) {
|
|
145
|
-
txb = request.txb;
|
|
146
|
-
} else {
|
|
147
|
-
const appHelper = appHelpers.getAppHelper(request.application);
|
|
148
|
-
if (!appHelper) {
|
|
149
|
-
throw new Error(`Can't find app helper for application ${request.application}`);
|
|
150
|
-
}
|
|
151
|
-
txb = toSuiTransaction(
|
|
152
|
-
await appHelper.build({
|
|
153
|
-
network: this.globals.config.network,
|
|
154
|
-
intentionData: request.intention,
|
|
155
|
-
txType: request.txType,
|
|
156
|
-
txSubType: request.txSubType,
|
|
157
|
-
clientUrl: this.globals.config.suiClient.url,
|
|
158
|
-
account: {
|
|
159
|
-
address: this.address,
|
|
160
|
-
publicKey: fromHex(this.address),
|
|
161
|
-
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
162
|
-
features: [],
|
|
163
|
-
},
|
|
164
|
-
}),
|
|
165
|
-
);
|
|
166
|
-
}
|
|
145
|
+
const txb = await this.buildTxFromIntention(request);
|
|
167
146
|
txb.setSender(this.address);
|
|
168
|
-
return this.simulator.simulate({
|
|
147
|
+
return this.simulator.simulate({
|
|
148
|
+
txb,
|
|
149
|
+
sender: this.address,
|
|
150
|
+
preferAddressBalance: request.preferAddressBalance,
|
|
151
|
+
excludeObjectIds: request.excludeObjectIds,
|
|
152
|
+
});
|
|
169
153
|
}
|
|
170
154
|
|
|
171
155
|
async proposeIntention(input: Omit<IProposeIntentionRequest, 'signature' | 'msafeAddress'>) {
|
|
@@ -188,32 +172,11 @@ export class MSafeAccount {
|
|
|
188
172
|
async proposeIntentionAndBuildVote(
|
|
189
173
|
input: Omit<IProposeIntentionAndBuildAndVoteRequest, 'signature' | 'msafeAddress' | 'payload' | 'digest'> & {
|
|
190
174
|
txb?: Transaction;
|
|
175
|
+
preferAddressBalance?: boolean;
|
|
176
|
+
excludeObjectIds?: Iterable<string>;
|
|
191
177
|
},
|
|
192
178
|
): Promise<void> {
|
|
193
|
-
|
|
194
|
-
if (input.txb) {
|
|
195
|
-
txb = input.txb;
|
|
196
|
-
} else {
|
|
197
|
-
const appHelper = appHelpers.getAppHelper(input.application);
|
|
198
|
-
if (!appHelper) {
|
|
199
|
-
throw new Error(`Can't find app helper for application ${input.application}`);
|
|
200
|
-
}
|
|
201
|
-
txb = toSuiTransaction(
|
|
202
|
-
await appHelper.build({
|
|
203
|
-
network: this.globals.config.network,
|
|
204
|
-
intentionData: input.intention,
|
|
205
|
-
txType: input.txType,
|
|
206
|
-
txSubType: input.txSubType,
|
|
207
|
-
clientUrl: this.globals.config.suiClient.url,
|
|
208
|
-
account: {
|
|
209
|
-
address: this.address,
|
|
210
|
-
publicKey: fromHex(this.address),
|
|
211
|
-
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
212
|
-
features: [],
|
|
213
|
-
},
|
|
214
|
-
}),
|
|
215
|
-
);
|
|
216
|
-
}
|
|
179
|
+
const txb = await this.buildTxFromIntention(input);
|
|
217
180
|
txb.setGasPrice(input.gasPrice);
|
|
218
181
|
txb.setSender(this.address);
|
|
219
182
|
await prepareGasFunding({
|
|
@@ -222,14 +185,20 @@ export class MSafeAccount {
|
|
|
222
185
|
owner: this.address,
|
|
223
186
|
gasPrice: input.gasPrice,
|
|
224
187
|
gasBudget: input.gasBudget,
|
|
188
|
+
preferAddressBalance: input.preferAddressBalance,
|
|
189
|
+
excludeObjectIds: input.excludeObjectIds,
|
|
225
190
|
});
|
|
226
191
|
const payload = await txb.build({ client: this.globals.suiClient });
|
|
227
192
|
const digest = await txb.getDigest({ client: this.globals.suiClient });
|
|
228
193
|
|
|
229
194
|
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
230
195
|
|
|
196
|
+
const apiInput = { ...input };
|
|
197
|
+
delete apiInput.txb;
|
|
198
|
+
delete apiInput.preferAddressBalance;
|
|
199
|
+
delete apiInput.excludeObjectIds;
|
|
231
200
|
return this.backend.proposeIntentionAndBuildVote({
|
|
232
|
-
...
|
|
201
|
+
...apiInput,
|
|
233
202
|
payload: toHex(payload),
|
|
234
203
|
digest,
|
|
235
204
|
msafeAddress: this.address,
|
|
@@ -356,25 +325,16 @@ export class MSafeAccount {
|
|
|
356
325
|
|
|
357
326
|
async simulateBuildTransaction(): Promise<SimulationResult> {
|
|
358
327
|
const intention = await this.backend.getNextIntention({ msafeAddress: this.address });
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
361
|
-
|
|
328
|
+
const parked = parkedPayloadHex(intention.intention);
|
|
329
|
+
if (parked) {
|
|
330
|
+
return this.simulator.simulateBuilt(fromHex(parked));
|
|
362
331
|
}
|
|
363
|
-
const txb =
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
clientUrl: this.globals.config.suiClient.url,
|
|
370
|
-
account: {
|
|
371
|
-
address: this.address,
|
|
372
|
-
publicKey: fromHex(this.address),
|
|
373
|
-
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
374
|
-
features: [],
|
|
375
|
-
},
|
|
376
|
-
}),
|
|
377
|
-
);
|
|
332
|
+
const txb = await this.buildTxFromIntention({
|
|
333
|
+
application: intention.application,
|
|
334
|
+
intention: intention.intention,
|
|
335
|
+
txType: intention.txType as TransactionType,
|
|
336
|
+
txSubType: intention.txSubType,
|
|
337
|
+
});
|
|
378
338
|
txb.setSender(this.address);
|
|
379
339
|
return this.simulator.simulate({ txb, sender: this.address });
|
|
380
340
|
}
|
|
@@ -427,6 +387,56 @@ export class MSafeAccount {
|
|
|
427
387
|
});
|
|
428
388
|
}
|
|
429
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Resolve a Transaction from propose/simulate input.
|
|
392
|
+
* 1. Caller-supplied `txb` (generic store path — do not rebuild).
|
|
393
|
+
* 2. Registered app helper `build`.
|
|
394
|
+
* 3. Unregistered app: restore from `intention.content` (JSON or hex).
|
|
395
|
+
*/
|
|
396
|
+
private async buildTxFromIntention(input: {
|
|
397
|
+
application: string;
|
|
398
|
+
txType: TransactionType;
|
|
399
|
+
txSubType: string;
|
|
400
|
+
intention: unknown;
|
|
401
|
+
txb?: Transaction;
|
|
402
|
+
}): Promise<Transaction> {
|
|
403
|
+
if (input.txb) {
|
|
404
|
+
return input.txb;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let appHelper: ReturnType<typeof appHelpers.getAppHelper> | undefined;
|
|
408
|
+
try {
|
|
409
|
+
appHelper = appHelpers.getAppHelper(input.application);
|
|
410
|
+
} catch {
|
|
411
|
+
appHelper = undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (appHelper) {
|
|
415
|
+
return toSuiTransaction(
|
|
416
|
+
await appHelper.build({
|
|
417
|
+
network: this.globals.config.network,
|
|
418
|
+
intentionData: input.intention,
|
|
419
|
+
txType: input.txType,
|
|
420
|
+
txSubType: input.txSubType,
|
|
421
|
+
clientUrl: this.globals.config.suiClient.url,
|
|
422
|
+
account: {
|
|
423
|
+
address: this.address,
|
|
424
|
+
publicKey: fromHex(this.address),
|
|
425
|
+
chains: [SUI_MAINNET_CHAIN, SUI_TESTNET_CHAIN],
|
|
426
|
+
features: [],
|
|
427
|
+
},
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const content = getIntentionContent(input.intention);
|
|
433
|
+
if (content != null) {
|
|
434
|
+
return transactionFromContent(content);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
throw new Error(`Can't find app helper for application ${input.application}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
430
440
|
private calculateWeightFromVotes(votes: { userAddress: string }[]) {
|
|
431
441
|
return this.calculateWeight(votes.map((vote) => vote.userAddress));
|
|
432
442
|
}
|
|
@@ -23,7 +23,12 @@ function gasObjectToReference(gas: SuiClientTypes.ChangedObject | null | undefin
|
|
|
23
23
|
export class Simulator {
|
|
24
24
|
constructor(private globals: MSafeGlobals) {}
|
|
25
25
|
|
|
26
|
-
public async simulate(input: {
|
|
26
|
+
public async simulate(input: {
|
|
27
|
+
txb: Transaction;
|
|
28
|
+
sender: string;
|
|
29
|
+
preferAddressBalance?: boolean;
|
|
30
|
+
excludeObjectIds?: Iterable<string>;
|
|
31
|
+
}): Promise<SimulationResult> {
|
|
27
32
|
const tx = this.copyTransaction(input.txb);
|
|
28
33
|
|
|
29
34
|
const { suiClient } = this.globals;
|
|
@@ -40,6 +45,8 @@ export class Simulator {
|
|
|
40
45
|
suiClient,
|
|
41
46
|
owner: input.sender,
|
|
42
47
|
gasPrice,
|
|
48
|
+
preferAddressBalance: input.preferAddressBalance,
|
|
49
|
+
excludeObjectIds: input.excludeObjectIds,
|
|
43
50
|
});
|
|
44
51
|
} catch (e: unknown) {
|
|
45
52
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -62,9 +69,19 @@ export class Simulator {
|
|
|
62
69
|
};
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
return this.inspectBuilt(built, gasPrice);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Dry-run a frozen payload without re-selecting gas. */
|
|
76
|
+
public async simulateBuilt(built: Uint8Array): Promise<SimulationResult> {
|
|
77
|
+
const gasPrice = await this.getGasPrice();
|
|
78
|
+
return this.inspectBuilt(built, gasPrice);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async inspectBuilt(built: Uint8Array, gasPrice: bigint): Promise<SimulationResult> {
|
|
65
82
|
let inspectResult: SimulateDryRunResponse;
|
|
66
83
|
try {
|
|
67
|
-
inspectResult = await suiClient.simulateTransaction({
|
|
84
|
+
inspectResult = await this.globals.suiClient.simulateTransaction({
|
|
68
85
|
transaction: built,
|
|
69
86
|
include: {
|
|
70
87
|
effects: true,
|
package/src/utils/gasFunding.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SuiClientTypes } from '@mysten/sui/client';
|
|
2
2
|
import type { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
3
3
|
import { Transaction } from '@mysten/sui/transactions';
|
|
4
|
-
import { normalizeStructTag, normalizeSuiObjectId } from '@mysten/sui/utils';
|
|
4
|
+
import { fromHex, normalizeStructTag, normalizeSuiObjectId } from '@mysten/sui/utils';
|
|
5
5
|
|
|
6
6
|
import { createCoinReservationRef } from '@/utils/coinReservation';
|
|
7
7
|
import { getAllCoins, SUI_COIN } from '@/utils/sui';
|
|
@@ -59,18 +59,81 @@ interface SuiGasFunding {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function collectUsedObjectIds(tx: Transaction): Set<string> {
|
|
62
|
-
|
|
62
|
+
const used = tx.getData().inputs.reduce<Set<string>>((acc, input) => {
|
|
63
63
|
const immOrOwned = input.Object?.ImmOrOwnedObject?.objectId;
|
|
64
64
|
if (immOrOwned) {
|
|
65
|
-
|
|
66
|
-
return
|
|
65
|
+
acc.add(normalizeSuiObjectId(immOrOwned));
|
|
66
|
+
return acc;
|
|
67
67
|
}
|
|
68
68
|
const unresolved = input.UnresolvedObject?.objectId;
|
|
69
69
|
if (unresolved) {
|
|
70
|
-
|
|
70
|
+
acc.add(normalizeSuiObjectId(unresolved));
|
|
71
71
|
}
|
|
72
|
-
return
|
|
72
|
+
return acc;
|
|
73
73
|
}, new Set<string>());
|
|
74
|
+
|
|
75
|
+
(tx.getData().gasData.payment ?? []).forEach((payment) => {
|
|
76
|
+
if (payment.objectId) {
|
|
77
|
+
used.add(normalizeSuiObjectId(payment.objectId));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return used;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function addExcludeObjectIds(used: Set<string>, excludeObjectIds?: Iterable<string>) {
|
|
84
|
+
Array.from(excludeObjectIds ?? [], (id) => used.add(normalizeSuiObjectId(id)));
|
|
85
|
+
return used;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function payloadToTransaction(payload: string | Uint8Array): Transaction {
|
|
89
|
+
if (typeof payload !== 'string') {
|
|
90
|
+
return Transaction.from(payload);
|
|
91
|
+
}
|
|
92
|
+
if (/^[0-9a-fA-F]+$/.test(payload) && payload.length % 2 === 0) {
|
|
93
|
+
return Transaction.from(fromHex(payload));
|
|
94
|
+
}
|
|
95
|
+
return Transaction.from(payload);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Object IDs a built payload already pins (inputs + gas payment). */
|
|
99
|
+
export function collectPayloadObjectIds(payload: string | Uint8Array): string[] {
|
|
100
|
+
return [...collectUsedObjectIds(payloadToTransaction(payload))];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parked queue item: intention was saved with a built hex payload + digest
|
|
105
|
+
* so build-next can promote it without rebuilding.
|
|
106
|
+
*/
|
|
107
|
+
export function parkedPayloadHex(intention: unknown): string | undefined {
|
|
108
|
+
if (!intention || typeof intention !== 'object') {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const { content, digest } = intention as { content?: unknown; digest?: unknown };
|
|
112
|
+
if (typeof content !== 'string' || typeof digest !== 'string' || !digest) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
if (!/^[0-9a-fA-F]+$/.test(content) || content.length < 64) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return content;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Coins already pinned by the current pending tx and any parked future payloads. */
|
|
122
|
+
export function collectQueueExcludeObjectIds(input: {
|
|
123
|
+
pendingPayload?: string;
|
|
124
|
+
parkedPayloads?: Array<string | undefined>;
|
|
125
|
+
}): string[] {
|
|
126
|
+
const ids = new Set<string>();
|
|
127
|
+
if (input.pendingPayload) {
|
|
128
|
+
collectPayloadObjectIds(input.pendingPayload).forEach((id) => ids.add(id));
|
|
129
|
+
}
|
|
130
|
+
(input.parkedPayloads ?? []).forEach((payload) => {
|
|
131
|
+
if (!payload) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
collectPayloadObjectIds(payload).forEach((id) => ids.add(id));
|
|
135
|
+
});
|
|
136
|
+
return [...ids];
|
|
74
137
|
}
|
|
75
138
|
|
|
76
139
|
/**
|
|
@@ -96,8 +159,13 @@ function txConsumesOwnedSuiCoins(tx: Transaction): boolean {
|
|
|
96
159
|
});
|
|
97
160
|
}
|
|
98
161
|
|
|
99
|
-
async function loadSuiGasFunding(
|
|
100
|
-
|
|
162
|
+
async function loadSuiGasFunding(
|
|
163
|
+
suiClient: SuiGrpcClient,
|
|
164
|
+
owner: string,
|
|
165
|
+
tx: Transaction,
|
|
166
|
+
excludeObjectIds?: Iterable<string>,
|
|
167
|
+
): Promise<SuiGasFunding> {
|
|
168
|
+
const usedObjectIds = addExcludeObjectIds(collectUsedObjectIds(tx), excludeObjectIds);
|
|
101
169
|
const [coins, balanceRes] = await Promise.all([
|
|
102
170
|
getAllCoins({ suiClient, owner, coinType: SUI_COIN }),
|
|
103
171
|
suiClient.getBalance({ owner, coinType: SUI_COIN }),
|
|
@@ -186,6 +254,7 @@ export async function selectGasFunding(input: {
|
|
|
186
254
|
suiClient: SuiGrpcClient;
|
|
187
255
|
owner: string;
|
|
188
256
|
gasBudget: bigint;
|
|
257
|
+
excludeObjectIds?: Iterable<string>;
|
|
189
258
|
}): Promise<GasFundingResult> {
|
|
190
259
|
const { tx, suiClient, owner, gasBudget } = input;
|
|
191
260
|
const { payment } = tx.getData().gasData;
|
|
@@ -196,7 +265,7 @@ export async function selectGasFunding(input: {
|
|
|
196
265
|
// skips re-selection and fails InsufficientGas under a tight budget.
|
|
197
266
|
// Selection below always overwrites payment via setGasPayment(...).
|
|
198
267
|
if (payment != null && payment.length > 0) {
|
|
199
|
-
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
268
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx, input.excludeObjectIds);
|
|
200
269
|
return {
|
|
201
270
|
mode: 'classic',
|
|
202
271
|
gasBudget,
|
|
@@ -205,7 +274,7 @@ export async function selectGasFunding(input: {
|
|
|
205
274
|
};
|
|
206
275
|
}
|
|
207
276
|
|
|
208
|
-
const funding = await loadSuiGasFunding(suiClient, owner, tx);
|
|
277
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx, input.excludeObjectIds);
|
|
209
278
|
const { paymentCoins, coinBalance, addressBalance, total } = funding;
|
|
210
279
|
|
|
211
280
|
if (total < gasBudget) {
|
|
@@ -289,13 +358,21 @@ export async function estimateGasBudget(input: {
|
|
|
289
358
|
suiClient: SuiGrpcClient;
|
|
290
359
|
owner: string;
|
|
291
360
|
gasPrice: bigint;
|
|
361
|
+
excludeObjectIds?: Iterable<string>;
|
|
362
|
+
preferAddressBalance?: boolean;
|
|
292
363
|
}): Promise<bigint> {
|
|
293
364
|
const { tx, suiClient, owner, gasPrice } = input;
|
|
294
|
-
const funding =
|
|
295
|
-
|
|
365
|
+
const funding = input.preferAddressBalance
|
|
366
|
+
? {
|
|
367
|
+
...(await loadSuiGasFunding(suiClient, owner, tx, input.excludeObjectIds)),
|
|
368
|
+
paymentCoins: [] as PaymentCoinRef[],
|
|
369
|
+
coinBalance: 0n,
|
|
370
|
+
}
|
|
371
|
+
: await loadSuiGasFunding(suiClient, owner, tx, input.excludeObjectIds);
|
|
372
|
+
const fundingTotal = input.preferAddressBalance ? funding.addressBalance : funding.total;
|
|
373
|
+
if (fundingTotal <= 0n) {
|
|
296
374
|
throw new InsufficientGasFundsError(0n, funding.coinBalance, funding.addressBalance);
|
|
297
375
|
}
|
|
298
|
-
|
|
299
376
|
const { budget, payment } = tx.getData().gasData;
|
|
300
377
|
// app-store / Mysten pre-build can leave payment:[] with budget "0".
|
|
301
378
|
// Mysten's setGasBudget skips when budget is the string "0" (truthy), so clear first.
|
|
@@ -341,8 +418,18 @@ export async function prepareGasFunding(input: {
|
|
|
341
418
|
owner: string;
|
|
342
419
|
gasPrice: bigint;
|
|
343
420
|
gasBudget?: bigint;
|
|
421
|
+
/**
|
|
422
|
+
* Force Address Balance gas (`payment: []`). Prefer {@link excludeObjectIds}
|
|
423
|
+
* for queue-behind-pending so unused SUI coins can still pay.
|
|
424
|
+
*/
|
|
425
|
+
preferAddressBalance?: boolean;
|
|
426
|
+
/**
|
|
427
|
+
* Queue-behind-pending: do not pick coins the current pending / parked
|
|
428
|
+
* payloads already pin. Those versions will change when the earlier tx executes.
|
|
429
|
+
*/
|
|
430
|
+
excludeObjectIds?: Iterable<string>;
|
|
344
431
|
}): Promise<GasFundingResult> {
|
|
345
|
-
const { tx, suiClient, owner, gasPrice } = input;
|
|
432
|
+
const { tx, suiClient, owner, gasPrice, excludeObjectIds } = input;
|
|
346
433
|
const { payment, budget: existingBudget } = tx.getData().gasData;
|
|
347
434
|
const bakedAddressBalanceGas = payment != null && payment.length === 0;
|
|
348
435
|
|
|
@@ -358,7 +445,14 @@ export async function prepareGasFunding(input: {
|
|
|
358
445
|
|
|
359
446
|
if (gasBudget == null) {
|
|
360
447
|
if (bakedAddressBalanceGas || existingPositive == null) {
|
|
361
|
-
gasBudget = await estimateGasBudget({
|
|
448
|
+
gasBudget = await estimateGasBudget({
|
|
449
|
+
tx,
|
|
450
|
+
suiClient,
|
|
451
|
+
owner,
|
|
452
|
+
gasPrice,
|
|
453
|
+
excludeObjectIds,
|
|
454
|
+
preferAddressBalance: input.preferAddressBalance,
|
|
455
|
+
});
|
|
362
456
|
} else {
|
|
363
457
|
gasBudget = existingPositive;
|
|
364
458
|
}
|
|
@@ -370,7 +464,22 @@ export async function prepareGasFunding(input: {
|
|
|
370
464
|
if (bakedAddressBalanceGas) {
|
|
371
465
|
tx.setExpiration(null);
|
|
372
466
|
}
|
|
373
|
-
|
|
467
|
+
|
|
468
|
+
if (input.preferAddressBalance) {
|
|
469
|
+
const funding = await loadSuiGasFunding(suiClient, owner, tx, excludeObjectIds);
|
|
470
|
+
if (funding.addressBalance < gasBudget) {
|
|
471
|
+
throw new InsufficientGasFundsError(gasBudget, funding.coinBalance, funding.addressBalance);
|
|
472
|
+
}
|
|
473
|
+
tx.setGasPayment([]);
|
|
474
|
+
return {
|
|
475
|
+
mode: 'addressBalance',
|
|
476
|
+
gasBudget,
|
|
477
|
+
coinBalance: funding.coinBalance,
|
|
478
|
+
addressBalance: funding.addressBalance,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return selectGasFunding({ tx, suiClient, owner, gasBudget, excludeObjectIds });
|
|
374
483
|
}
|
|
375
484
|
|
|
376
485
|
/**
|
package/src/utils/transaction.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Transaction, isTransaction } from '@mysten/sui/transactions';
|
|
2
|
+
import { fromHex } from '@mysten/sui/utils';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Normalize a transaction value to {@link Transaction}.
|
|
@@ -23,3 +24,34 @@ export function toSuiTransaction(txb: Transaction | unknown): Transaction {
|
|
|
23
24
|
|
|
24
25
|
throw new Error('Unsupported transaction value for toSuiTransaction');
|
|
25
26
|
}
|
|
27
|
+
|
|
28
|
+
function isHex(str: string): boolean {
|
|
29
|
+
return /^[0-9a-fA-F]+$/.test(str);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getIntentionContent(intention: unknown): unknown {
|
|
33
|
+
if (intention && typeof intention === 'object' && 'content' in intention) {
|
|
34
|
+
return (intention as { content: unknown }).content;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Restore a Transaction from plain-tx `intention.content`.
|
|
41
|
+
* Accepts V2 JSON (`tx.toJSON()`), hex BCS bytes, or a JSON object.
|
|
42
|
+
*/
|
|
43
|
+
export function transactionFromContent(content: unknown): Transaction {
|
|
44
|
+
if (content instanceof Uint8Array) {
|
|
45
|
+
return Transaction.from(content);
|
|
46
|
+
}
|
|
47
|
+
if (typeof content === 'string') {
|
|
48
|
+
if (isHex(content)) {
|
|
49
|
+
return Transaction.from(fromHex(content));
|
|
50
|
+
}
|
|
51
|
+
return Transaction.from(content);
|
|
52
|
+
}
|
|
53
|
+
if (content && typeof content === 'object') {
|
|
54
|
+
return Transaction.from(JSON.stringify(content));
|
|
55
|
+
}
|
|
56
|
+
throw new Error('Invalid transaction content');
|
|
57
|
+
}
|