Skip to content

Commit 1c4b78a

Browse files
Merge pull request #7719 from BitGo/WIN-8215
fix(sdk-coin-sui): adding inputObjects in transferBuilder
2 parents 74d5bad + 311a7d8 commit 1c4b78a

File tree

5 files changed

+278
-42
lines changed

5 files changed

+278
-42
lines changed

modules/sdk-coin-sui/src/lib/iface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export interface SuiTransaction<T = SuiProgrammableTransaction> {
103103
sender: string;
104104
tx: T;
105105
gasData: GasData;
106+
inputObjects?: SuiObjectRef[];
106107
}
107108

108109
export interface RequestAddStake {

modules/sdk-coin-sui/src/lib/transactionBuilder.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export abstract class TransactionBuilder<T = SuiProgrammableTransaction> extends
2727
protected _type: SuiTransactionType;
2828
protected _sender: string;
2929
protected _gasData: GasData;
30+
protected _inputObjects: SuiObjectRef[];
3031

3132
protected constructor(_coinConfig: Readonly<CoinConfig>) {
3233
super(_coinConfig);
@@ -93,6 +94,12 @@ export abstract class TransactionBuilder<T = SuiProgrammableTransaction> extends
9394
return this;
9495
}
9596

97+
inputObjects(inputObjects: SuiObjectRef[]): this {
98+
this.validateInputObjectsBase(inputObjects);
99+
this._inputObjects = inputObjects;
100+
return this;
101+
}
102+
96103
/**
97104
* Initialize the transaction builder fields using the decoded transaction data
98105
*
@@ -148,6 +155,14 @@ export abstract class TransactionBuilder<T = SuiProgrammableTransaction> extends
148155
});
149156
}
150157

158+
protected validateInputObjectsBase(inputObjects: SuiObjectRef[]): void {
159+
if (inputObjects && inputObjects.length > 0) {
160+
inputObjects.forEach((inputObject) => {
161+
this.validateSuiObjectRef(inputObject, 'input object');
162+
});
163+
}
164+
}
165+
151166
validateSuiObjectRef(suiObjectRef: SuiObjectRef, field: string): void {
152167
if (!suiObjectRef.hasOwnProperty('objectId')) {
153168
throw new BuildTransactionError(`Invalid ${field}, missing objectId`);

modules/sdk-coin-sui/src/lib/transferBuilder.ts

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Inputs,
1010
Transactions as TransactionsConstructor,
1111
TransactionBlock as ProgrammingTransactionBlockBuilder,
12+
TransactionArgument,
1213
} from './mystenlab/builder';
1314
import utils from './utils';
1415
import { MAX_COMMAND_ARGS, MAX_GAS_OBJECTS } from './constants';
@@ -88,6 +89,10 @@ export class TransferBuilder extends TransactionBuilder<TransferProgrammableTran
8889
this.sender(txData.sender);
8990
this.gasData(txData.gasData);
9091

92+
if (txData.inputObjects) {
93+
this.inputObjects(txData.inputObjects);
94+
}
95+
9196
const recipients = utils.getRecipients(tx.suiTransaction);
9297
this.send(recipients);
9398
}
@@ -104,6 +109,11 @@ export class TransferBuilder extends TransactionBuilder<TransferProgrammableTran
104109
);
105110
assert(this._gasData, new BuildTransactionError('gasData is required before building'));
106111
this.validateGasData(this._gasData);
112+
113+
// If inputObjects are provided, validate them
114+
if (this._inputObjects && this._inputObjects.length > 0) {
115+
this.validateInputObjectsBase(this._inputObjects);
116+
}
107117
}
108118

109119
/**
@@ -115,47 +125,73 @@ export class TransferBuilder extends TransactionBuilder<TransferProgrammableTran
115125
this.validateTransactionFields();
116126
const programmableTxBuilder = new ProgrammingTransactionBlockBuilder();
117127

118-
// number of objects passed as gas payment should be strictly less than `MAX_GAS_OBJECTS`. When the transaction
119-
// requires a larger number of inputs we use the merge command to merge the rest of the objects into the gasCoin
120-
if (this._gasData.payment.length >= MAX_GAS_OBJECTS) {
121-
const gasPaymentObjects = this._gasData.payment
122-
.slice(MAX_GAS_OBJECTS - 1)
123-
.map((object) => Inputs.ObjectRef(object));
124-
125-
// limit for total number of `args: CallArg[]` for a single command is MAX_COMMAND_ARGS so the max length of
126-
// `sources[]` for a `mergeCoins(destination, sources[])` command is MAX_COMMAND_ARGS - 1 (1 used up for
127-
// `destination`). We need to create a total of `gasPaymentObjects/(MAX_COMMAND_ARGS - 1)` merge commands to
128-
// merge all the objects
129-
while (gasPaymentObjects.length > 0) {
130-
programmableTxBuilder.mergeCoins(
131-
programmableTxBuilder.gas,
132-
gasPaymentObjects.splice(0, MAX_COMMAND_ARGS - 1).map((object) => programmableTxBuilder.object(object))
133-
);
128+
if (this._sender !== this._gasData.owner && this._inputObjects && this._inputObjects.length > 0) {
129+
const inputObjects = this._inputObjects.map((object) => programmableTxBuilder.object(Inputs.ObjectRef(object)));
130+
const mergedObject = inputObjects.shift() as TransactionArgument;
131+
if (inputObjects.length > 0) {
132+
programmableTxBuilder.mergeCoins(mergedObject, inputObjects);
134133
}
135-
}
136-
137-
this._recipients.forEach((recipient) => {
138-
const coin = programmableTxBuilder.add(
139-
TransactionsConstructor.SplitCoins(programmableTxBuilder.gas, [
134+
this._recipients.forEach((recipient) => {
135+
const splitObject = programmableTxBuilder.splitCoins(mergedObject, [
140136
programmableTxBuilder.pure(Number(recipient.amount)),
141-
])
142-
);
143-
programmableTxBuilder.add(
144-
TransactionsConstructor.TransferObjects([coin], programmableTxBuilder.object(recipient.address))
145-
);
146-
});
147-
const txData = programmableTxBuilder.blockData;
148-
return {
149-
type: this._type,
150-
sender: this._sender,
151-
tx: {
152-
inputs: [...txData.inputs],
153-
transactions: [...txData.transactions],
154-
},
155-
gasData: {
156-
...this._gasData,
157-
payment: this._gasData.payment.slice(0, MAX_GAS_OBJECTS - 1),
158-
},
159-
};
137+
]);
138+
programmableTxBuilder.transferObjects([splitObject], programmableTxBuilder.object(recipient.address));
139+
});
140+
const txData = programmableTxBuilder.blockData;
141+
return {
142+
type: this._type,
143+
sender: this._sender,
144+
tx: {
145+
inputs: [...txData.inputs],
146+
transactions: [...txData.transactions],
147+
},
148+
gasData: {
149+
...this._gasData,
150+
},
151+
};
152+
} else {
153+
// number of objects passed as gas payment should be strictly less than `MAX_GAS_OBJECTS`. When the transaction
154+
// requires a larger number of inputs we use the merge command to merge the rest of the objects into the gasCoin
155+
if (this._gasData.payment.length >= MAX_GAS_OBJECTS) {
156+
const gasPaymentObjects = this._gasData.payment
157+
.slice(MAX_GAS_OBJECTS - 1)
158+
.map((object) => Inputs.ObjectRef(object));
159+
160+
// limit for total number of `args: CallArg[]` for a single command is MAX_COMMAND_ARGS so the max length of
161+
// `sources[]` for a `mergeCoins(destination, sources[])` command is MAX_COMMAND_ARGS - 1 (1 used up for
162+
// `destination`). We need to create a total of `gasPaymentObjects/(MAX_COMMAND_ARGS - 1)` merge commands to
163+
// merge all the objects
164+
while (gasPaymentObjects.length > 0) {
165+
programmableTxBuilder.mergeCoins(
166+
programmableTxBuilder.gas,
167+
gasPaymentObjects.splice(0, MAX_COMMAND_ARGS - 1).map((object) => programmableTxBuilder.object(object))
168+
);
169+
}
170+
}
171+
172+
this._recipients.forEach((recipient) => {
173+
const coin = programmableTxBuilder.add(
174+
TransactionsConstructor.SplitCoins(programmableTxBuilder.gas, [
175+
programmableTxBuilder.pure(Number(recipient.amount)),
176+
])
177+
);
178+
programmableTxBuilder.add(
179+
TransactionsConstructor.TransferObjects([coin], programmableTxBuilder.object(recipient.address))
180+
);
181+
});
182+
const txData = programmableTxBuilder.blockData;
183+
return {
184+
type: this._type,
185+
sender: this._sender,
186+
tx: {
187+
inputs: [...txData.inputs],
188+
transactions: [...txData.transactions],
189+
},
190+
gasData: {
191+
...this._gasData,
192+
payment: this._gasData.payment.slice(0, MAX_GAS_OBJECTS - 1),
193+
},
194+
};
195+
}
160196
}
161197
}

modules/sdk-coin-sui/src/lib/transferTransaction.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ import { MAX_GAS_OBJECTS, SUI_ADDRESS_LENGTH, UNAVAILABLE_TEXT } from './constan
1313
import { Buffer } from 'buffer';
1414
import { Transaction } from './transaction';
1515
import { CallArg, SuiObjectRef, normalizeSuiAddress } from './mystenlab/types';
16-
import utils from './utils';
17-
import { builder, Inputs, TransactionBlockInput } from './mystenlab/builder';
16+
import utils, { isImmOrOwnedObj } from './utils';
17+
import {
18+
builder,
19+
Inputs,
20+
TransactionArgument,
21+
TransactionBlockInput,
22+
TransactionType as SuiTransactionBlockType,
23+
} from './mystenlab/builder';
1824
import { BCS } from '@mysten/bcs';
1925
import BigNumber from 'bignumber.js';
2026

@@ -70,6 +76,17 @@ export class TransferTransaction extends Transaction<TransferProgrammableTransac
7076
}
7177

7278
const tx = this._suiTransaction;
79+
if (tx.gasData.owner !== tx.sender) {
80+
// Sponsored transaction
81+
return {
82+
id: this._id,
83+
sender: tx.sender,
84+
kind: { ProgrammableTransaction: tx.tx },
85+
gasData: tx.gasData,
86+
expiration: { None: null },
87+
inputObjects: this.getInputObjectsFromTx(tx.tx),
88+
};
89+
}
7390
return {
7491
id: this._id,
7592
sender: tx.sender,
@@ -194,6 +211,16 @@ export class TransferTransaction extends Transaction<TransferProgrammableTransac
194211
transactions: this._suiTransaction.tx.transactions,
195212
} as TransferProgrammableTransaction;
196213

214+
if (this._suiTransaction.gasData.owner !== this._suiTransaction.sender) {
215+
return {
216+
sender: this._suiTransaction.sender,
217+
expiration: { None: null },
218+
gasData: this._suiTransaction.gasData,
219+
kind: {
220+
ProgrammableTransaction: programmableTx,
221+
},
222+
};
223+
}
197224
return {
198225
sender: this._suiTransaction.sender,
199226
expiration: { None: null },
@@ -229,4 +256,37 @@ export class TransferTransaction extends Transaction<TransferProgrammableTransac
229256
outputs,
230257
};
231258
}
259+
260+
/**
261+
* Extracts the objects that were provided as inputs while building the transaction
262+
* @param tx
263+
* @returns {SuiObjectRef[]} Objects that are inputs for the transaction
264+
*/
265+
private getInputObjectsFromTx(tx: TransferProgrammableTransaction): SuiObjectRef[] {
266+
const inputs = tx.inputs;
267+
const transaction = tx.transactions[0] as SuiTransactionBlockType;
268+
269+
let args: TransactionArgument[] = [];
270+
if (transaction.kind === 'MergeCoins') {
271+
const { destination, sources } = transaction;
272+
args = [destination, ...sources];
273+
} else if (transaction.kind === 'SplitCoins') {
274+
args = [transaction.coin];
275+
}
276+
277+
const inputObjects: SuiObjectRef[] = [];
278+
args.forEach((arg) => {
279+
if (arg.kind === 'Input') {
280+
let input = inputs[arg.index];
281+
if ('value' in input) {
282+
input = input.value;
283+
}
284+
if ('Object' in input && isImmOrOwnedObj(input.Object)) {
285+
inputObjects.push(input.Object.ImmOrOwned);
286+
}
287+
}
288+
});
289+
290+
return inputObjects;
291+
}
232292
}

0 commit comments

Comments
 (0)