1
- import algosdk , { Address , ApplicationTransactionFields , TransactionBoxReference , TransactionType , stringifyJSON } from 'algosdk'
1
+ import algosdk , { ApplicationTransactionFields , TransactionType } from 'algosdk'
2
2
import { Buffer } from 'buffer'
3
3
import { Config } from '../config'
4
4
import { AlgoAmount } from '../types/amount'
@@ -233,7 +233,6 @@ export const sendTransaction = async function (
233
233
const populateAppCallResources = sendParams ?. populateAppCallResources ?? Config . populateAppCallResources
234
234
235
235
// 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
237
236
if ( txnToSend . type === algosdk . TransactionType . appl && populateAppCallResources ) {
238
237
const newAtc = new AtomicTransactionComposer ( )
239
238
newAtc . addTransaction ( { txn : txnToSend , signer : getSenderTransactionSigner ( from ) } )
@@ -279,6 +278,7 @@ async function getGroupExecutionInfo(
279
278
allowUnnamedResources : true ,
280
279
allowEmptySignatures : true ,
281
280
fixSigners : true ,
281
+ populateResources : sendParams . populateAppCallResources ,
282
282
} )
283
283
284
284
const nullSigner = algosdk . makeEmptyTransactionSigner ( )
@@ -325,6 +325,10 @@ async function getGroupExecutionInfo(
325
325
}
326
326
327
327
return {
328
+ populatedResourceArrays : sendParams . populateAppCallResources
329
+ ? groupResponse . txnResults . map ( ( t ) => t . populatedResourceArrays )
330
+ : undefined ,
331
+ extraResourceArrays : sendParams . populateAppCallResources ? groupResponse . extraResourceArrays : undefined ,
328
332
groupUnnamedResourcesAccessed : sendParams . populateAppCallResources ? groupResponse . unnamedResourcesAccessed : undefined ,
329
333
txns : groupResponse . txnResults . map ( ( txn , i ) => {
330
334
const originalTxn = atc [ 'transactions' ] [ i ] . txn as algosdk . Transaction
@@ -460,33 +464,7 @@ export async function prepareGroupForSending(
460
464
)
461
465
: [ 0n , new Map < number , bigint > ( ) ]
462
466
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 ) => {
490
468
// Cover App Call Inner Transaction Fees
491
469
if ( sendParams . coverAppCallInnerTransactionFees ) {
492
470
const additionalTransactionFee = additionalTransactionFees . get ( i )
@@ -508,253 +486,27 @@ export async function prepareGroupForSending(
508
486
} )
509
487
510
488
// 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 }
611
498
} )
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
- }
622
499
}
623
500
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
630
503
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
634
506
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
+ } )
758
510
}
759
511
760
512
const newAtc = new algosdk . AtomicTransactionComposer ( )
0 commit comments