1- import algosdk , { Address , ApplicationTransactionFields , TransactionBoxReference , TransactionType , stringifyJSON } from 'algosdk'
1+ import algosdk , { ApplicationTransactionFields , TransactionType } from 'algosdk'
22import { Buffer } from 'buffer'
33import { Config } from '../config'
44import { AlgoAmount } from '../types/amount'
@@ -233,7 +233,6 @@ export const sendTransaction = async function (
233233 const populateAppCallResources = sendParams ?. populateAppCallResources ?? Config . populateAppCallResources
234234
235235 // Populate resources if the transaction is an appcall and populateAppCallResources wasn't explicitly set to false
236- // NOTE: Temporary false by default until this algod bug is fixed: https://github.com/algorand/go-algorand/issues/5914
237236 if ( txnToSend . type === algosdk . TransactionType . appl && populateAppCallResources ) {
238237 const newAtc = new AtomicTransactionComposer ( )
239238 newAtc . addTransaction ( { txn : txnToSend , signer : getSenderTransactionSigner ( from ) } )
@@ -279,6 +278,7 @@ async function getGroupExecutionInfo(
279278 allowUnnamedResources : true ,
280279 allowEmptySignatures : true ,
281280 fixSigners : true ,
281+ populateResources : sendParams . populateAppCallResources ,
282282 } )
283283
284284 const nullSigner = algosdk . makeEmptyTransactionSigner ( )
@@ -325,6 +325,10 @@ async function getGroupExecutionInfo(
325325 }
326326
327327 return {
328+ populatedResourceArrays : sendParams . populateAppCallResources
329+ ? groupResponse . txnResults . map ( ( t ) => t . populatedResourceArrays )
330+ : undefined ,
331+ extraResourceArrays : sendParams . populateAppCallResources ? groupResponse . extraResourceArrays : undefined ,
328332 groupUnnamedResourcesAccessed : sendParams . populateAppCallResources ? groupResponse . unnamedResourcesAccessed : undefined ,
329333 txns : groupResponse . txnResults . map ( ( txn , i ) => {
330334 const originalTxn = atc [ 'transactions' ] [ i ] . txn as algosdk . Transaction
@@ -460,33 +464,7 @@ export async function prepareGroupForSending(
460464 )
461465 : [ 0n , new Map < number , bigint > ( ) ]
462466
463- executionInfo . txns . forEach ( ( { unnamedResourcesAccessed : r } , i ) => {
464- // Populate Transaction App Call Resources
465- if ( sendParams . populateAppCallResources && r !== undefined && group [ i ] . txn . type === TransactionType . appl ) {
466- if ( r . boxes || r . extraBoxRefs ) throw Error ( 'Unexpected boxes at the transaction level' )
467- if ( r . appLocals ) throw Error ( 'Unexpected app local at the transaction level' )
468- if ( r . assetHoldings )
469- throw Error ( 'Unexpected asset holding at the transaction level' )
470- // eslint-disable-next-line @typescript-eslint/no-explicit-any
471- ; ( group [ i ] . txn as any ) [ 'applicationCall' ] = {
472- ...group [ i ] . txn . applicationCall ,
473- accounts : [ ...( group [ i ] . txn ?. applicationCall ?. accounts ?? [ ] ) , ...( r . accounts ?? [ ] ) ] ,
474- foreignApps : [ ...( group [ i ] . txn ?. applicationCall ?. foreignApps ?? [ ] ) , ...( r . apps ?? [ ] ) ] ,
475- foreignAssets : [ ...( group [ i ] . txn ?. applicationCall ?. foreignAssets ?? [ ] ) , ...( r . assets ?? [ ] ) ] ,
476- boxes : [ ...( group [ i ] . txn ?. applicationCall ?. boxes ?? [ ] ) , ...( r . boxes ?? [ ] ) ] ,
477- } satisfies Partial < ApplicationTransactionFields >
478-
479- const accounts = group [ i ] . txn . applicationCall ?. accounts ?. length ?? 0
480- if ( accounts > MAX_APP_CALL_ACCOUNT_REFERENCES )
481- throw Error ( `Account reference limit of ${ MAX_APP_CALL_ACCOUNT_REFERENCES } exceeded in transaction ${ i } ` )
482- const assets = group [ i ] . txn . applicationCall ?. foreignAssets ?. length ?? 0
483- const apps = group [ i ] . txn . applicationCall ?. foreignApps ?. length ?? 0
484- const boxes = group [ i ] . txn . applicationCall ?. boxes ?. length ?? 0
485- if ( accounts + assets + apps + boxes > MAX_APP_CALL_FOREIGN_REFERENCES ) {
486- throw Error ( `Resource reference limit of ${ MAX_APP_CALL_FOREIGN_REFERENCES } exceeded in transaction ${ i } ` )
487- }
488- }
489-
467+ executionInfo . txns . forEach ( ( _ , i ) => {
490468 // Cover App Call Inner Transaction Fees
491469 if ( sendParams . coverAppCallInnerTransactionFees ) {
492470 const additionalTransactionFee = additionalTransactionFees . get ( i )
@@ -508,253 +486,27 @@ export async function prepareGroupForSending(
508486 } )
509487
510488 // Populate Group App Call Resources
511- if ( sendParams . populateAppCallResources ) {
512- const populateGroupResource = (
513- txns : algosdk . TransactionWithSigner [ ] ,
514- reference :
515- | string
516- | algosdk . modelsv2 . BoxReference
517- | algosdk . modelsv2 . ApplicationLocalReference
518- | algosdk . modelsv2 . AssetHoldingReference
519- | bigint
520- | number
521- | Address ,
522- type : 'account' | 'assetHolding' | 'appLocal' | 'app' | 'box' | 'asset' ,
523- ) : void => {
524- const isApplBelowLimit = ( t : algosdk . TransactionWithSigner ) => {
525- if ( t . txn . type !== algosdk . TransactionType . appl ) return false
526-
527- const accounts = t . txn . applicationCall ?. accounts ?. length ?? 0
528- const assets = t . txn . applicationCall ?. foreignAssets ?. length ?? 0
529- const apps = t . txn . applicationCall ?. foreignApps ?. length ?? 0
530- const boxes = t . txn . applicationCall ?. boxes ?. length ?? 0
531-
532- return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES
533- }
534-
535- // If this is a asset holding or app local, first try to find a transaction that already has the account available
536- if ( type === 'assetHolding' || type === 'appLocal' ) {
537- const { account } = reference as algosdk . modelsv2 . ApplicationLocalReference | algosdk . modelsv2 . AssetHoldingReference
538-
539- let txnIndex = txns . findIndex ( ( t ) => {
540- if ( ! isApplBelowLimit ( t ) ) return false
541-
542- return (
543- // account is in the foreign accounts array
544- t . txn . applicationCall ?. accounts ?. map ( ( a ) => a . toString ( ) ) . includes ( account . toString ( ) ) ||
545- // account is available as an app account
546- t . txn . applicationCall ?. foreignApps ?. map ( ( a ) => algosdk . getApplicationAddress ( a ) . toString ( ) ) . includes ( account . toString ( ) ) ||
547- // account is available since it's in one of the fields
548- Object . values ( t . txn ) . some ( ( f ) =>
549- stringifyJSON ( f , ( _ , v ) => ( v instanceof Address ? v . toString ( ) : v ) ) ?. includes ( account . toString ( ) ) ,
550- )
551- )
552- } )
553-
554- if ( txnIndex > - 1 ) {
555- if ( type === 'assetHolding' ) {
556- const { asset } = reference as algosdk . modelsv2 . AssetHoldingReference
557- // eslint-disable-next-line @typescript-eslint/no-explicit-any
558- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
559- ...txns [ txnIndex ] . txn . applicationCall ,
560- foreignAssets : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. foreignAssets ?? [ ] ) , ...[ asset ] ] ,
561- } satisfies Partial < ApplicationTransactionFields >
562- } else {
563- const { app } = reference as algosdk . modelsv2 . ApplicationLocalReference
564- // eslint-disable-next-line @typescript-eslint/no-explicit-any
565- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
566- ...txns [ txnIndex ] . txn . applicationCall ,
567- foreignApps : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. foreignApps ?? [ ] ) , ...[ app ] ] ,
568- } satisfies Partial < ApplicationTransactionFields >
569- }
570- return
571- }
572-
573- // Now try to find a txn that already has that app or asset available
574- txnIndex = txns . findIndex ( ( t ) => {
575- if ( ! isApplBelowLimit ( t ) ) return false
576-
577- // check if there is space in the accounts array
578- if ( ( t . txn . applicationCall ?. accounts ?. length ?? 0 ) >= MAX_APP_CALL_ACCOUNT_REFERENCES ) return false
579-
580- if ( type === 'assetHolding' ) {
581- const { asset } = reference as algosdk . modelsv2 . AssetHoldingReference
582- return t . txn . applicationCall ?. foreignAssets ?. includes ( asset )
583- } else {
584- const { app } = reference as algosdk . modelsv2 . ApplicationLocalReference
585- return t . txn . applicationCall ?. foreignApps ?. includes ( app ) || t . txn . applicationCall ?. appIndex === app
586- }
587- } )
588-
589- if ( txnIndex > - 1 ) {
590- const { account } = reference as algosdk . modelsv2 . AssetHoldingReference | algosdk . modelsv2 . ApplicationLocalReference
591-
592- // eslint-disable-next-line @typescript-eslint/no-explicit-any
593- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
594- ...txns [ txnIndex ] . txn . applicationCall ,
595- accounts : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. accounts ?? [ ] ) , ...[ account ] ] ,
596- } satisfies Partial < ApplicationTransactionFields >
597-
598- return
599- }
600- }
601-
602- // If this is a box, first try to find a transaction that already has the app available
603- if ( type === 'box' ) {
604- const { app, name } = reference as algosdk . modelsv2 . BoxReference
605-
606- const txnIndex = txns . findIndex ( ( t ) => {
607- if ( ! isApplBelowLimit ( t ) ) return false
608-
609- // If the app is in the foreign array OR the app being called, then we know it's available
610- return t . txn . applicationCall ?. foreignApps ?. includes ( app ) || t . txn . applicationCall ?. appIndex === app
489+ if ( executionInfo . populatedResourceArrays ) {
490+ executionInfo . populatedResourceArrays . forEach ( ( r , i ) => {
491+ const txn = group [ i ] . txn . applicationCall
492+ if ( r === undefined || txn === undefined ) return
493+
494+ if ( r . boxes ) {
495+ // @ts -expect-error boxes is readonly
496+ txn . boxes = r . boxes . map ( ( b ) => {
497+ return { appIndex : BigInt ( b . app ) , name : b . name }
611498 } )
612-
613- if ( txnIndex > - 1 ) {
614- // eslint-disable-next-line @typescript-eslint/no-explicit-any
615- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
616- ...txns [ txnIndex ] . txn . applicationCall ,
617- boxes : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. boxes ?? [ ] ) , ...[ { appIndex : app , name } satisfies TransactionBoxReference ] ] ,
618- } satisfies Partial < ApplicationTransactionFields >
619-
620- return
621- }
622499 }
623500
624- // Find the txn index to put the reference(s)
625- const txnIndex = txns . findIndex ( ( t ) => {
626- if ( t . txn . type !== algosdk . TransactionType . appl ) return false
627-
628- const accounts = t . txn . applicationCall ?. accounts ?. length ?? 0
629- if ( type === 'account' ) return accounts < MAX_APP_CALL_ACCOUNT_REFERENCES
501+ // @ts -expect-error accounts is readonly
502+ if ( r . accounts ) txn . accounts = r . accounts
630503
631- const assets = t . txn . applicationCall ?. foreignAssets ?. length ?? 0
632- const apps = t . txn . applicationCall ?. foreignApps ?. length ?? 0
633- const boxes = t . txn . applicationCall ?. boxes ?. length ?? 0
504+ // @ts -expect-error apps is readonly
505+ if ( r . apps ) txn . foreignApps = r . apps
634506
635- // If we're adding local state or asset holding, we need space for the acocunt and the other reference
636- if ( type === 'assetHolding' || type === 'appLocal' ) {
637- return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES - 1 && accounts < MAX_APP_CALL_ACCOUNT_REFERENCES
638- }
639-
640- // If we're adding a box, we need space for both the box ref and the app ref
641- if ( type === 'box' && BigInt ( ( reference as algosdk . modelsv2 . BoxReference ) . app ) !== BigInt ( 0 ) ) {
642- return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES - 1
643- }
644-
645- return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES
646- } )
647-
648- if ( txnIndex === - 1 ) {
649- throw Error ( 'No more transactions below reference limit. Add another app call to the group.' )
650- }
651-
652- if ( type === 'account' ) {
653- // eslint-disable-next-line @typescript-eslint/no-explicit-any
654- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
655- ...txns [ txnIndex ] . txn . applicationCall ,
656- accounts : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. accounts ?? [ ] ) , ...[ reference as Address ] ] ,
657- } satisfies Partial < ApplicationTransactionFields >
658- } else if ( type === 'app' ) {
659- // eslint-disable-next-line @typescript-eslint/no-explicit-any
660- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
661- ...txns [ txnIndex ] . txn . applicationCall ,
662- foreignApps : [
663- ...( txns [ txnIndex ] . txn ?. applicationCall ?. foreignApps ?? [ ] ) ,
664- ...[ typeof reference === 'bigint' ? reference : BigInt ( reference as number ) ] ,
665- ] ,
666- } satisfies Partial < ApplicationTransactionFields >
667- } else if ( type === 'box' ) {
668- const { app, name } = reference as algosdk . modelsv2 . BoxReference
669- // eslint-disable-next-line @typescript-eslint/no-explicit-any
670- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
671- ...txns [ txnIndex ] . txn . applicationCall ,
672- boxes : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. boxes ?? [ ] ) , ...[ { appIndex : app , name } satisfies TransactionBoxReference ] ] ,
673- } satisfies Partial < ApplicationTransactionFields >
674-
675- if ( app . toString ( ) !== '0' ) {
676- // eslint-disable-next-line @typescript-eslint/no-explicit-any
677- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
678- ...txns [ txnIndex ] . txn . applicationCall ,
679- foreignApps : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. foreignApps ?? [ ] ) , ...[ app ] ] ,
680- } satisfies Partial < ApplicationTransactionFields >
681- }
682- } else if ( type === 'assetHolding' ) {
683- const { asset, account } = reference as algosdk . modelsv2 . AssetHoldingReference
684- // eslint-disable-next-line @typescript-eslint/no-explicit-any
685- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
686- ...txns [ txnIndex ] . txn . applicationCall ,
687- foreignAssets : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. foreignAssets ?? [ ] ) , ...[ asset ] ] ,
688- accounts : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. accounts ?? [ ] ) , ...[ account ] ] ,
689- } satisfies Partial < ApplicationTransactionFields >
690- } else if ( type === 'appLocal' ) {
691- const { app, account } = reference as algosdk . modelsv2 . ApplicationLocalReference
692- // eslint-disable-next-line @typescript-eslint/no-explicit-any
693- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
694- ...txns [ txnIndex ] . txn . applicationCall ,
695- foreignApps : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. foreignApps ?? [ ] ) , ...[ app ] ] ,
696- accounts : [ ...( txns [ txnIndex ] . txn ?. applicationCall ?. accounts ?? [ ] ) , ...[ account ] ] ,
697- } satisfies Partial < ApplicationTransactionFields >
698- } else if ( type === 'asset' ) {
699- // eslint-disable-next-line @typescript-eslint/no-explicit-any
700- ; ( txns [ txnIndex ] . txn as any ) [ 'applicationCall' ] = {
701- ...txns [ txnIndex ] . txn . applicationCall ,
702- foreignAssets : [
703- ...( txns [ txnIndex ] . txn ?. applicationCall ?. foreignAssets ?? [ ] ) ,
704- ...[ typeof reference === 'bigint' ? reference : BigInt ( reference as number ) ] ,
705- ] ,
706- } satisfies Partial < ApplicationTransactionFields >
707- }
708- }
709-
710- const g = executionInfo . groupUnnamedResourcesAccessed
711-
712- if ( g ) {
713- // Do cross-reference resources first because they are the most restrictive in terms
714- // of which transactions can be used
715- g . appLocals ?. forEach ( ( a ) => {
716- populateGroupResource ( group , a , 'appLocal' )
717-
718- // Remove resources from the group if we're adding them here
719- g . accounts = g . accounts ?. filter ( ( acc ) => acc !== a . account )
720- g . apps = g . apps ?. filter ( ( app ) => BigInt ( app ) !== BigInt ( a . app ) )
721- } )
722-
723- g . assetHoldings ?. forEach ( ( a ) => {
724- populateGroupResource ( group , a , 'assetHolding' )
725-
726- // Remove resources from the group if we're adding them here
727- g . accounts = g . accounts ?. filter ( ( acc ) => acc !== a . account )
728- g . assets = g . assets ?. filter ( ( asset ) => BigInt ( asset ) !== BigInt ( a . asset ) )
729- } )
730-
731- // Do accounts next because the account limit is 4
732- g . accounts ?. forEach ( ( a ) => {
733- populateGroupResource ( group , a , 'account' )
734- } )
735-
736- g . boxes ?. forEach ( ( b ) => {
737- populateGroupResource ( group , b , 'box' )
738-
739- // Remove apps as resource from the group if we're adding it here
740- g . apps = g . apps ?. filter ( ( app ) => BigInt ( app ) !== BigInt ( b . app ) )
741- } )
742-
743- g . assets ?. forEach ( ( a ) => {
744- populateGroupResource ( group , a , 'asset' )
745- } )
746-
747- g . apps ?. forEach ( ( a ) => {
748- populateGroupResource ( group , a , 'app' )
749- } )
750-
751- if ( g . extraBoxRefs ) {
752- for ( let i = 0 ; i < g . extraBoxRefs ; i += 1 ) {
753- const ref = new algosdk . modelsv2 . BoxReference ( { app : 0 , name : new Uint8Array ( 0 ) } )
754- populateGroupResource ( group , ref , 'box' )
755- }
756- }
757- }
507+ // @ts -expect-error assets is readonly
508+ if ( r . assets ) txn . foreignAssets = r . assets
509+ } )
758510 }
759511
760512 const newAtc = new algosdk . AtomicTransactionComposer ( )
0 commit comments