Skip to content

Commit 0ff8926

Browse files
Merge pull request #7691 from BitGo/WIN-8152
feat(sdk-coin-iota): updating transferTxn to use gas for payment
2 parents 4bbd231 + 73c8f3d commit 0ff8926

File tree

3 files changed

+660
-38
lines changed

3 files changed

+660
-38
lines changed

modules/sdk-coin-iota/src/lib/transaction.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,9 @@ export abstract class Transaction extends BaseTransaction {
302302
await this.populateTxData();
303303
this._iotaTransaction.setGasPrice(this.gasPrice as number);
304304
this._iotaTransaction.setGasBudget(this.gasBudget as number);
305-
this._iotaTransaction.setGasPayment(this.gasPaymentObjects as TransactionObjectInput[]);
305+
this._iotaTransaction.setGasPayment(
306+
this.gasPaymentObjects?.slice(0, MAX_GAS_PAYMENT_OBJECTS - 1) as TransactionObjectInput[]
307+
);
306308
this._txDataBytes = await this._iotaTransaction.build();
307309
this._rebuildRequired = false;
308310
}
@@ -349,12 +351,6 @@ export abstract class Transaction extends BaseTransaction {
349351
throw new InvalidTransactionError('Gas payment objects are required');
350352
}
351353

352-
if (this.gasPaymentObjects.length > MAX_GAS_PAYMENT_OBJECTS) {
353-
throw new InvalidTransactionError(
354-
`Gas payment objects count (${this.gasPaymentObjects.length}) exceeds maximum allowed (${MAX_GAS_PAYMENT_OBJECTS})`
355-
);
356-
}
357-
358354
if (
359355
this._signature &&
360356
!(

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

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { fromBase64 } from '@iota/iota-sdk/utils';
1111
import { messageWithIntent as iotaMessageWithIntent } from '@iota/iota-sdk/cryptography';
1212
import { BcsReader } from '@iota/bcs';
13-
import { MAX_INPUT_OBJECTS, MAX_RECIPIENTS, TRANSFER_TRANSACTION_COMMANDS } from './constants';
13+
import { MAX_GAS_PAYMENT_OBJECTS, MAX_INPUT_OBJECTS, MAX_RECIPIENTS, TRANSFER_TRANSACTION_COMMANDS } from './constants';
1414
import utils from './utils';
1515
import BigNumber from 'bignumber.js';
1616

@@ -106,7 +106,14 @@ export class TransferTransaction extends Transaction {
106106
if (amounts.length !== receivers.length) {
107107
throw new InvalidTransactionError('count of amounts does not match count of receivers');
108108
}
109-
this._paymentObjects = inputObjects;
109+
if (
110+
(!this.gasPaymentObjects || this.gasPaymentObjects.length === 0) &&
111+
(!this.gasSponsor || this.sender === this.gasSponsor)
112+
) {
113+
this.gasPaymentObjects = inputObjects;
114+
} else if (inputObjects.length > 0) {
115+
this._paymentObjects = inputObjects;
116+
}
110117
this.recipients = [];
111118
receivers.forEach((recipient, index) => {
112119
this._recipients.push({
@@ -122,31 +129,70 @@ export class TransferTransaction extends Transaction {
122129
}
123130

124131
protected populateTxInputsAndCommands(): void {
125-
if (this.paymentObjects) {
126-
let firstInputObj: TransactionObjectArgument = this._iotaTransaction.object(
127-
IotaInputs.ObjectRef(this.paymentObjects[0])
128-
);
129-
if (this.paymentObjects.length > 1) {
130-
let idx = 1;
131-
while (idx < this.paymentObjects.length) {
132-
[firstInputObj] = this._iotaTransaction.mergeCoins(
133-
firstInputObj,
134-
this.paymentObjects
135-
.slice(idx, idx + MAX_INPUT_OBJECTS)
136-
.map((input) => this._iotaTransaction.object(IotaInputs.ObjectRef(input)))
137-
);
138-
idx += MAX_INPUT_OBJECTS;
139-
}
140-
}
132+
const consolidatedCoin = this.shouldUseGasObjectsForPayment()
133+
? this.consolidateGasObjects()
134+
: this.consolidatePaymentObjects();
135+
this.splitAndTransferToRecipients(consolidatedCoin);
136+
}
141137

142-
const coins = this._iotaTransaction.splitCoins(
143-
firstInputObj,
144-
this._recipients.map((recipient) => recipient.amount)
145-
);
146-
this._recipients.forEach((recipient, idx) => {
147-
this._iotaTransaction.transferObjects([coins[idx]], recipient.address);
148-
});
138+
private shouldUseGasObjectsForPayment(): boolean {
139+
const paymentObjectExists = this.paymentObjects && this.paymentObjects.length > 0;
140+
const senderPaysOwnGas = !this.gasSponsor || this.gasSponsor === this.sender;
141+
return !paymentObjectExists && senderPaysOwnGas && Boolean(this.gasPaymentObjects);
142+
}
143+
144+
private consolidatePaymentObjects(): TransactionObjectArgument {
145+
if (!this.paymentObjects || this.paymentObjects.length === 0) {
146+
throw new InvalidTransactionError('Payment objects are required');
147+
}
148+
149+
const firstObject = this._iotaTransaction.object(IotaInputs.ObjectRef(this.paymentObjects[0]));
150+
151+
if (this.paymentObjects.length === 1) {
152+
return firstObject;
153+
}
154+
155+
return this.mergeObjectsInBatches(firstObject, this.paymentObjects.slice(1), MAX_INPUT_OBJECTS);
156+
}
157+
158+
private consolidateGasObjects(): TransactionObjectArgument {
159+
if (!this.gasPaymentObjects || this.gasPaymentObjects.length === 0) {
160+
throw new InvalidTransactionError('Gas payment objects are required');
149161
}
162+
163+
const gasObject = this._iotaTransaction.gas;
164+
165+
if (this.gasPaymentObjects.length <= MAX_GAS_PAYMENT_OBJECTS) {
166+
return gasObject;
167+
}
168+
169+
return this.mergeObjectsInBatches(gasObject, this.gasPaymentObjects, MAX_INPUT_OBJECTS);
170+
}
171+
172+
private mergeObjectsInBatches(
173+
targetCoin: TransactionObjectArgument,
174+
objectsToMerge: TransactionObjectInput[],
175+
batchSize: number
176+
): TransactionObjectArgument {
177+
let consolidatedCoin = targetCoin;
178+
179+
for (let startIndex = 0; startIndex < objectsToMerge.length; startIndex += batchSize) {
180+
const batch = objectsToMerge.slice(startIndex, startIndex + batchSize);
181+
const batchAsTransactionObjects = batch.map((obj) => this._iotaTransaction.object(IotaInputs.ObjectRef(obj)));
182+
183+
[consolidatedCoin] = this._iotaTransaction.mergeCoins(consolidatedCoin, batchAsTransactionObjects);
184+
}
185+
186+
return consolidatedCoin;
187+
}
188+
189+
private splitAndTransferToRecipients(sourceCoin: TransactionObjectArgument): void {
190+
const recipientAmounts = this._recipients.map((recipient) => recipient.amount);
191+
const splitCoins = this._iotaTransaction.splitCoins(sourceCoin, recipientAmounts);
192+
193+
this._recipients.forEach((recipient, index) => {
194+
this._iotaTransaction.transferObjects([splitCoins[index]], recipient.address);
195+
});
150196
}
151197

152198
protected validateTxDataImplementation(): void {
@@ -161,11 +207,17 @@ export class TransferTransaction extends Transaction {
161207
}
162208

163209
if (!this.paymentObjects || this.paymentObjects?.length === 0) {
164-
throw new InvalidTransactionError('Payment objects are required');
210+
if (!this.gasSponsor || this.gasSponsor === this.sender) {
211+
if (!this.gasPaymentObjects || this.gasPaymentObjects?.length === 0) {
212+
throw new InvalidTransactionError('Payment or Gas objects are required');
213+
}
214+
} else {
215+
throw new InvalidTransactionError('Payment objects are required');
216+
}
165217
}
166218

167219
// Check for duplicate object IDs in payment objects
168-
const paymentObjectIds = this.paymentObjects.map((obj) => obj.objectId);
220+
const paymentObjectIds = this.paymentObjects?.map((obj) => obj.objectId) ?? [];
169221
const uniquePaymentIds = new Set(paymentObjectIds);
170222
if (uniquePaymentIds.size !== paymentObjectIds.length) {
171223
throw new InvalidTransactionError('Duplicate object IDs found in payment objects');
@@ -180,10 +232,10 @@ export class TransferTransaction extends Transaction {
180232
throw new InvalidTransactionError('Duplicate object IDs found in gas payment objects');
181233
}
182234

183-
const duplicates = this.paymentObjects.filter((payment) => gasObjectIds.includes(payment.objectId));
235+
const duplicates = paymentObjectIds.filter((payment) => gasObjectIds.includes(payment));
184236
if (duplicates.length > 0) {
185237
throw new InvalidTransactionError(
186-
'Payment objects cannot be the same as gas payment objects: ' + duplicates.map((d) => d.objectId).join(', ')
238+
'Payment objects cannot be the same as gas payment objects: ' + duplicates.join(', ')
187239
);
188240
}
189241
}

0 commit comments

Comments
 (0)