|
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 |
@@ -508,253 +512,27 @@ export async function prepareGroupForSending( |
508 | 512 | }) |
509 | 513 |
|
510 | 514 | // 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 | | - } |
| 515 | + if (executionInfo.populatedResourceArrays) { |
| 516 | + executionInfo.populatedResourceArrays.forEach((r, i) => { |
| 517 | + const txn = group[i].txn.applicationCall |
| 518 | + if (r === undefined || txn === undefined) return |
| 519 | + |
| 520 | + if (r.boxes) { |
| 521 | + // @ts-expect-error boxes is readonly |
| 522 | + txn.boxes = r.boxes.map((b) => { |
| 523 | + return { appIndex: BigInt(b.app), name: b.name } |
587 | 524 | }) |
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 |
611 | | - }) |
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 | | - } |
623 | | - |
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 |
630 | | - |
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 |
634 | | - |
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 | 525 | } |
708 | | - } |
709 | | - |
710 | | - const g = executionInfo.groupUnnamedResourcesAccessed |
711 | 526 |
|
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 | | - }) |
| 527 | + // @ts-expect-error accounts is readonly |
| 528 | + if (r.accounts) txn.accounts = r.accounts |
722 | 529 |
|
723 | | - g.assetHoldings?.forEach((a) => { |
724 | | - populateGroupResource(group, a, 'assetHolding') |
| 530 | + // @ts-expect-error apps is readonly |
| 531 | + if (r.apps) txn.foreignApps = r.apps |
725 | 532 |
|
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 | | - } |
| 533 | + // @ts-expect-error assets is readonly |
| 534 | + if (r.assets) txn.foreignAssets = r.assets |
| 535 | + }) |
758 | 536 | } |
759 | 537 |
|
760 | 538 | const newAtc = new algosdk.AtomicTransactionComposer() |
|
0 commit comments