@@ -10,7 +10,7 @@ import {
1010import { fromBase64 } from '@iota/iota-sdk/utils' ;
1111import { messageWithIntent as iotaMessageWithIntent } from '@iota/iota-sdk/cryptography' ;
1212import { 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' ;
1414import utils from './utils' ;
1515import 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