Repository: algorand/js-algorand-sdk
Pull request #725 (2022-12-28 16:49:09)

Enhancement: Add foreign array objects to ATC `addMethodCall`
This PR adds foreign array objects as arguments in `addMethodCall()` for the atomic transaction composer.

Adds a test that mirrors the Go SDK: https://github.com/algorand/go-algorand-sdk/pull/318. Currently written as a unit test, but could also benefit from a cucumber test since all the other SDKs implement this. 

Addresses #653 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71dd3a9..f47b23b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@
 - Remove `makeLogicSig` in favor of either using `LogicSigAccount` (preferred) or directly invoking `LogicSig` constructor.
 - Remove `EncodedMultisigBlob` in favor of `EncodedSignedTransaction.
 
+**Full Changelog**: https://github.com/algorand/js-algorand-sdk/compare/v1.24.1...v2.0.0
+
 # v1.24.1
 
 ## What's Changed
diff --git a/README.md b/README.md
index f03eb23..afbf7c2 100644
--- a/README.md
+++ b/README.md
@@ -22,8 +22,8 @@ Include a minified browser bundle directly in your HTML like so:
 
 ```html
 <script
-  src="https://unpkg.com/algosdk@v1.24.1/dist/browser/algosdk.min.js"
-  integrity="sha384-cXCnQHmkjqS84S5jqAp6YXjU1ynCEJaHROHklSY7eh+cCXQ3aGX0oSgJSfQgDF5y"
+  src="https://unpkg.com/algosdk@v2.0.0/dist/browser/algosdk.min.js"
+  integrity="sha384-NBHc5NXk7w78aVp9uPMo1JmBan6l4MHxzhiKXgYT8wtBoeIMHEuu5OECfGHYTLh7"
   crossorigin="anonymous"
 ></script>
 ```
@@ -32,8 +32,8 @@ or
 
 ```html
 <script
-  src="https://cdn.jsdelivr.net/npm/algosdk@v1.24.1/dist/browser/algosdk.min.js"
-  integrity="sha384-cXCnQHmkjqS84S5jqAp6YXjU1ynCEJaHROHklSY7eh+cCXQ3aGX0oSgJSfQgDF5y"
+  src="https://cdn.jsdelivr.net/npm/algosdk@v2.0.0/dist/browser/algosdk.min.js"
+  integrity="sha384-NBHc5NXk7w78aVp9uPMo1JmBan6l4MHxzhiKXgYT8wtBoeIMHEuu5OECfGHYTLh7"
   crossorigin="anonymous"
 ></script>
 ```
diff --git a/package-lock.json b/package-lock.json
index ee0ac5e..2d937dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "algosdk",
-  "version": "1.24.1",
+  "version": "2.0.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "algosdk",
-      "version": "1.24.1",
+      "version": "2.0.0",
       "license": "MIT",
       "dependencies": {
         "algo-msgpack-with-bigint": "^2.1.1",
diff --git a/package.json b/package.json
index ae52798..2db1054 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "algosdk",
-  "version": "1.24.1",
+  "version": "2.0.0",
   "description": "The official JavaScript SDK for Algorand",
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
diff --git a/src/composer.ts b/src/composer.ts
index b9756b6..1376977 100644
--- a/src/composer.ts
+++ b/src/composer.ts
@@ -202,6 +202,9 @@ export class AtomicTransactionComposer {
     numLocalInts,
     numLocalByteSlices,
     extraPages,
+    appAccounts,
+    appForeignApps,
+    appForeignAssets,
     boxes,
     note,
     lease,
@@ -234,6 +237,12 @@ export class AtomicTransactionComposer {
     numLocalByteSlices?: number;
     /** The number of extra pages to allocate for the application's programs. Only set this if this is an application creation call. If omitted, defaults to 0. */
     extraPages?: number;
+    /** Array of Address strings that represent external accounts supplied to this application. If accounts are provided here, the accounts specified in the method args will appear after these. */
+    appAccounts?: string[];
+    /** Array of App ID numbers that represent external apps supplied to this application. If apps are provided here, the apps specified in the method args will appear after these. */
+    appForeignApps?: number[];
+    /** Array of Asset ID numbers that represent external assets supplied to this application. If assets are provided here, the assets specified in the method args will appear after these. */
+    appForeignAssets?: number[];
     /** The box references for this application call */
     boxes?: BoxReference[];
     /** The note value for this application call */
@@ -370,9 +379,12 @@ export class AtomicTransactionComposer {
     }
 
     const resolvedRefIndexes: number[] = [];
-    const foreignAccounts: string[] = [];
-    const foreignApps: number[] = [];
-    const foreignAssets: number[] = [];
+    const foreignAccounts: string[] =
+      appAccounts == null ? [] : appAccounts.slice();
+    const foreignApps: number[] =
+      appForeignApps == null ? [] : appForeignApps.slice();
+    const foreignAssets: number[] =
+      appForeignAssets == null ? [] : appForeignAssets.slice();
     for (let i = 0; i < refArgTypes.length; i++) {
       const refType = refArgTypes[i];
       const refValue = refArgValues[i];


Test patch:
diff --git a/tests/10.ABI.ts b/tests/10.ABI.ts
index 68447aa..2e778e2 100644
--- a/tests/10.ABI.ts
+++ b/tests/10.ABI.ts
@@ -12,6 +12,13 @@ import {
   ABIType,
   ABIValue,
 } from '../src/abi/abi_type';
+import {
+  AtomicTransactionComposer,
+  ABIMethod,
+  makeBasicAccountTransactionSigner,
+  generateAccount,
+  AtomicTransactionComposerStatus,
+} from '../src';
 import { decodeAddress } from '../src/encoding/address';
 
 describe('ABI type checking', () => {
@@ -474,4 +481,56 @@ describe('ABI encoding', () => {
       ])
     );
   });
+
+  it('should properly accept foreign array objects in the ATC addMethodCall', () => {
+    const composer = new AtomicTransactionComposer();
+    const method = ABIMethod.fromSignature('add(application)uint8');
+    const account = generateAccount();
+    const sender = 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA';
+    const sp = {
+      fee: 1000,
+      firstRound: 1,
+      lastRound: 1001,
+      genesisID: 'gi',
+      genesisHash: 'gh',
+    };
+    const foreignAcct =
+      'E4VCHISDQPLIZWMALIGNPK2B2TERPDMR64MZJXE3UL75MUDXZMADX5OWXM';
+
+    // Create method call using ATC.
+    // The foreign apps array argument should be packed before the method argument.
+    composer.addMethodCall({
+      appID: 7,
+      method,
+      sender,
+      suggestedParams: sp,
+      methodArgs: [2],
+      appAccounts: [foreignAcct],
+      appForeignApps: [1],
+      appForeignAssets: [124],
+      signer: makeBasicAccountTransactionSigner(account),
+    });
+
+    assert.deepStrictEqual(
+      composer.getStatus(),
+      AtomicTransactionComposerStatus.BUILDING
+    );
+    assert.deepStrictEqual(composer.count(), 1);
+
+    // The built group should have one txn.
+    const txns = composer.buildGroup();
+    // eslint-disable-next-line prefer-destructuring
+    const txn = txns[0].txn;
+
+    // Assert that foreign objects were passed in and ordering was correct.
+    assert.deepStrictEqual(txn.appForeignApps?.length, 2);
+    assert.deepStrictEqual(txn.appForeignApps[0], 1);
+    assert.deepStrictEqual(txn.appForeignApps[1], 2);
+
+    assert.deepStrictEqual(txn.appForeignAssets?.length, 1);
+    assert.deepStrictEqual(txn.appForeignAssets[0], 124);
+
+    assert.deepStrictEqual(txn.appAccounts?.length, 1);
+    assert.deepStrictEqual(txn.appAccounts[0], decodeAddress(foreignAcct));
+  });
 });