|
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