diff --git a/.github/actions/run-local-testnet/action.yaml b/.github/actions/run-local-testnet/action.yaml new file mode 100644 index 00000000000000..853034fe19c365 --- /dev/null +++ b/.github/actions/run-local-testnet/action.yaml @@ -0,0 +1,37 @@ +name: "Run Local Testnet" +description: | + Runs a local testnet from a Docker image built from a particular release branch +inputs: + IMAGE_TAG: + description: "The image tag to use for running the local testnet, e.g. devnet / testnet / mainnet or some SHA" + required: true + GCP_DOCKER_ARTIFACT_REPO: + description: "The GCP Docker artifact repository" + required: true + +runs: + using: composite + steps: + # Create a directory that we'll bindmount into the container into which it can + # store all its configuration and files. + - name: Create directory for testnet files + run: mkdir -p ${{ runner.temp }}/testnet + shell: bash + + # Run a local testnet. We mount in the testnet directory we just created. + - run: docker run -p 8080:8080 -p 8081:8081 -v ${{ runner.temp }}/testnet:/testnet --name=local-testnet-${{ inputs.IMAGE_TAG }} --detach ${{ inputs.GCP_DOCKER_ARTIFACT_REPO }}/tools:${{ inputs.IMAGE_TAG }} aptos node run-local-testnet --with-faucet --test-dir /testnet + shell: bash + + # Wait for the node API and faucet of the local testnet to start up. + - run: npm install -g wait-on + shell: bash + - run: wait-on -t 60000 --httpTimeout 60000 http-get://127.0.0.1:8080/v1 + shell: bash + - run: wait-on -t 60000 --httpTimeout 60000 http-get://127.0.0.1:8081 + shell: bash + + # Print the logs from the local testnet if the tests failed. + - name: Print local testnet logs if something failed + run: docker logs local-testnet-${{ inputs.IMAGE_TAG }} + shell: bash + if: ${{ failure() }} diff --git a/.github/actions/run-sdk-e2e-tests/action.yaml b/.github/actions/run-sdk-e2e-tests/action.yaml new file mode 100644 index 00000000000000..9a8ebd065a018e --- /dev/null +++ b/.github/actions/run-sdk-e2e-tests/action.yaml @@ -0,0 +1,63 @@ +name: "Run SDK E2E tests" +description: | + Run the SDK E2E tests against a local testnet built from a particular release branch +inputs: + NETWORK: + description: "The network to use for running the local testnet, one of devnet / testnet / mainnet" + required: true + GCP_DOCKER_ARTIFACT_REPO: + description: "The GCP Docker artifact repository" + required: true + +runs: + using: composite + steps: + # Install node and pnpm. + - uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b # pin@v3 + with: + node-version-file: .node-version + registry-url: "https://registry.npmjs.org" + - uses: pnpm/action-setup@537643d491d20c2712d11533497cb47b2d0eb9d5 # pin https://github.com/pnpm/action-setup/releases/tag/v2.2.3 + + # Set up the necessary env vars for the test suite. + - run: echo "APTOS_NODE_URL=http://127.0.0.1:8080/v1" >> .env + shell: bash + working-directory: ./ecosystem/typescript/sdk + - run: echo "APTOS_FAUCET_URL=http://127.0.0.1:8081" >> .env + shell: bash + working-directory: ./ecosystem/typescript/sdk + - run: echo "ANS_TEST_ACCOUNT_PRIVATE_KEY=0x37368b46ce665362562c6d1d4ec01a08c8644c488690df5a17e13ba163e20221" >> .env + shell: bash + working-directory: ./ecosystem/typescript/sdk + - run: echo "ANS_TEST_ACCOUNT_ADDRESS=585fc9f0f0c54183b039ffc770ca282ebd87307916c215a3e692f2f8e4305e82" >> .env + shell: bash + working-directory: ./ecosystem/typescript/sdk + - run: echo "APTOS_INVOCATION='docker run -v ${{ runner.temp }}/ans:/tmp/ans --network host ${{ inputs.GCP_DOCKER_ARTIFACT_REPO }}/tools:${{ inputs.NETWORK }} aptos'" >>.env + shell: bash + working-directory: ./ecosystem/typescript/sdk + - run: echo "ANS_REPO_LOCATION=${{ runner.temp }}/ans" >> .env + shell: bash + working-directory: ./ecosystem/typescript/sdk + - run: echo "NETWORK=${{ inputs.NETWORK }}" >> .env + shell: bash + working-directory: ./ecosystem/typescript/sdk + + # Run package install. If install fails, it probably means the lockfile + # was not included in the commit. + - run: pnpm install --frozen-lockfile + shell: bash + working-directory: ./ecosystem/typescript/sdk + + # Run a local testnet. + - uses: ./.github/actions/run-local-testnet + with: + IMAGE_TAG: ${{ inputs.NETWORK }} + GCP_DOCKER_ARTIFACT_REPO: ${{ inputs.GCP_DOCKER_ARTIFACT_REPO }} + + # Run the TS SDK tests. + - uses: nick-fields/retry@7f8f3d9f0f62fe5925341be21c2e8314fd4f7c7c # pin@v2 + name: sdk-pnpm-test + with: + max_attempts: 3 + timeout_minutes: 25 + command: cd ./ecosystem/typescript/sdk && pnpm run test:ci diff --git a/.github/workflows/docker-build-test.yaml b/.github/workflows/docker-build-test.yaml index 1f97b20f7152aa..673782ba8da786 100644 --- a/.github/workflows/docker-build-test.yaml +++ b/.github/workflows/docker-build-test.yaml @@ -23,7 +23,8 @@ name: "Build+Test Docker Images" on: # build on main branch OR when a PR is labeled with `CICD:build-images` # Allow us to run this specific workflow without a PR workflow_dispatch: - pull_request_target: + # TODO: Undo this once I can see that node-api-compatilibity-tests is working. + pull_request: types: [labeled, opened, synchronize, reopened, auto_merge_enabled] push: branches: @@ -182,7 +183,7 @@ jobs: BUILD_ADDL_TESTING_IMAGES: true TARGET_REGISTRY: ${{ needs.determine-docker-build-metadata.outputs.targetRegistry }} - sdk-release: + node-api-compatibility-tests: needs: [permission-check, rust-images, determine-docker-build-metadata] # runs with the default release docker build variant "rust-images" if: | (github.event_name == 'push' && github.ref_name != 'main') || @@ -190,7 +191,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'CICD:run-e2e-tests') || github.event.pull_request.auto_merge != null || contains(github.event.pull_request.body, '#e2e') - uses: aptos-labs/aptos-core/.github/workflows/sdk-release.yaml@main + uses: ./.github/workflows/node-api-compatibility-tests.yaml secrets: inherit with: GIT_SHA: ${{ needs.determine-docker-build-metadata.outputs.gitSha }} @@ -351,6 +352,6 @@ jobs: FORGE_TEST_SUITE: multiregion_benchmark_test IMAGE_TAG: ${{ needs.determine-docker-build-metadata.outputs.gitSha }} FORGE_RUNNER_DURATION_SECS: 300 - COMMENT_HEADER: forge-multiregion-test + COMMENT_HEADER: forge-multiregion-test FORGE_NAMESPACE: forge-multiregion-test-${{ needs.determine-docker-build-metadata.outputs.targetCacheId }} FORGE_CLUSTER_NAME: forge-multiregion diff --git a/.github/workflows/sdk-release.yaml b/.github/workflows/node-api-compatibility-tests.yaml similarity index 62% rename from .github/workflows/sdk-release.yaml rename to .github/workflows/node-api-compatibility-tests.yaml index 9a2c93bef3c9b8..095975c577234a 100644 --- a/.github/workflows/sdk-release.yaml +++ b/.github/workflows/node-api-compatibility-tests.yaml @@ -12,7 +12,9 @@ ## - Replace env.IMAGE_TAG for a known image tag ## - env.GIT_SHA will resolve to that of your PR branch -name: "API + TS SDK CI" +# These tests ensure that the Node API, the OpenAPI spec that is generated from it, +# and the TS SDK inner client that is generated from that, all match up. +name: "Node API Compatibility Tests" on: # This is called from within the docker-build-test.yaml workflow since we depend # on the images having been built before this workflow runs. @@ -22,9 +24,6 @@ on: required: true type: string description: Use this to override the git SHA1, branch name (e.g. devnet) or tag to release the SDK from - pull_request: - paths: - - .github/workflows/sdk-release.yaml env: # This is the docker image tag that will be used for the SDK release. @@ -36,7 +35,7 @@ jobs: # Confirm that the generated client within the TS SDK has been re-generated # if there are any changes that would affect it within the PR / commit. If # everything is checked in, run tests, build the SDK, and upload it to npmjs. - test-sdk-confirm-client-generated-publish: + node-api-compatibility-tests: runs-on: high-perf-docker permissions: contents: read @@ -84,6 +83,7 @@ jobs: max_attempts: 3 timeout_minutes: 20 command: docker run --rm --mount=type=bind,source=${{ runner.temp }}/specs,target=/specs ${{ secrets.GCP_DOCKER_ARTIFACT_REPO }}/tools:${IMAGE_TAG} aptos-openapi-spec-generator -f yaml -o /specs/spec.yaml + - uses: nick-fields/retry@7f8f3d9f0f62fe5925341be21c2e8314fd4f7c7c # pin@v2 name: generate-json-spec with: @@ -98,21 +98,6 @@ jobs: - run: git diff --no-index --ignore-space-at-eol --ignore-blank-lines ${{ runner.temp }}/specs/spec.yaml api/doc/spec.yaml - run: git diff --no-index --ignore-space-at-eol --ignore-blank-lines ${{ runner.temp }}/specs/spec.json api/doc/spec.json - # Set up dotenv file for tests (jest doesn't read env vars properly). - - run: echo "APTOS_NODE_URL=$APTOS_NODE_URL" >> ./ecosystem/typescript/sdk/.env - - run: echo "APTOS_FAUCET_URL=$APTOS_FAUCET_URL" >> ./ecosystem/typescript/sdk/.env - - run: echo "FAUCET_AUTH_TOKEN=$FAUCET_AUTH_TOKEN" >> ./ecosystem/typescript/sdk/.env - - run: echo "ANS_TEST_ACCOUNT_PRIVATE_KEY=$ANS_TEST_ACCOUNT_PRIVATE_KEY" >> ./ecosystem/typescript/sdk/.env - - run: echo "ANS_TEST_ACCOUNT_ADDRESS=$ANS_TEST_ACCOUNT_ADDRESS" >> ./ecosystem/typescript/sdk/.env - - # These two have to be defined here and not in the env section because the runner - # context is only available here. - - run: echo "APTOS_INVOCATION='docker run -v ${{ runner.temp }}/ans:/tmp/ans --network host ${{ secrets.GCP_DOCKER_ARTIFACT_REPO }}/tools:${IMAGE_TAG} aptos'" >> ./ecosystem/typescript/sdk/.env - - run: echo "ANS_REPO_LOCATION=${{ runner.temp }}/ans" >> ./ecosystem/typescript/sdk/.env - - - run: cp ./ecosystem/typescript/sdk/.env ./ecosystem/typescript/sdk/examples/typescript/.env - - run: cp ./ecosystem/typescript/sdk/.env ./ecosystem/typescript/sdk/examples/javascript/.env - # Run package install. If install fails, it probably means the lockfile # was not included in the commit. - run: cd ./ecosystem/typescript/sdk && pnpm install --frozen-lockfile @@ -123,56 +108,6 @@ jobs: - run: echo "cd ecosystem/typescript/sdk && pnpm generate-client" - run: git diff --no-index --ignore-space-at-eol --ignore-blank-lines ./ecosystem/typescript/sdk/src/generated/ /tmp/generated_client/ - # Run a local testnet built from the same commit. - - run: docker run -p 8080:8080 -p 8081:8081 --name=local-testnet --detach ${{ secrets.GCP_DOCKER_ARTIFACT_REPO }}/tools:${IMAGE_TAG} aptos node run-local-testnet --with-faucet - - # Wait for the API and faucet to startup. - - run: npm install -g wait-on - - run: wait-on -t 60000 --httpTimeout 60000 http-get://127.0.0.1:8080/v1 - - run: wait-on -t 60000 --httpTimeout 60000 http-get://127.0.0.1:8081 - - # Run the TS SDK tests and confirm the build works. - - uses: nick-fields/retry@7f8f3d9f0f62fe5925341be21c2e8314fd4f7c7c # pin@v2 - name: sdk-pnpm-test - with: - max_attempts: 3 - timeout_minutes: 20 - command: cd ./ecosystem/typescript/sdk && pnpm run test:ci - - run: cd ./ecosystem/typescript/sdk && pnpm build - # Confirm the Rust API client examples pass. - uses: aptos-labs/aptos-core/.github/actions/rust-setup@main - - run: cargo run -p aptos-rest-client --example account -- --api-url http://127.0.01:8080 - - - name: Print docker-compose testnet logs on failure - if: ${{ failure() }} - working-directory: docker/compose/validator-testnet - run: docker logs local-testnet - - # Run the TS SDK indexer tests. Note: indexer service can be flaky and we - # dont want those tests to be land blocking for any PR on the aptos repo. - # This is why we run those tests separate from - # test-sdk-confirm-client-generated-publish. - run-indexer-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 - with: - ref: ${{ env.GIT_SHA }} - - uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b # pin@v3 - with: - node-version-file: .node-version - registry-url: "https://registry.npmjs.org" - - uses: pnpm/action-setup@537643d491d20c2712d11533497cb47b2d0eb9d5 # pin https://github.com/pnpm/action-setup/releases/tag/v2.2.3 - - # Run package install. If install fails, it probably means the lockfile - # was not included in the commit. - - run: cd ./ecosystem/typescript/sdk && pnpm install --frozen-lockfile - - # Run indexer tests. - - uses: nick-fields/retry@7f8f3d9f0f62fe5925341be21c2e8314fd4f7c7c # pin@v2 - name: ts-sdk-indexer-test - with: - max_attempts: 3 - timeout_minutes: 20 - command: cd ./ecosystem/typescript/sdk && pnpm run test:indexer + - run: cargo run -p aptos-rest-client --example account -- --api-url $APTOS_NODE_URL diff --git a/.github/workflows/sdk-e2e-tests.yaml b/.github/workflows/sdk-e2e-tests.yaml new file mode 100644 index 00000000000000..76685e15f4d000 --- /dev/null +++ b/.github/workflows/sdk-e2e-tests.yaml @@ -0,0 +1,102 @@ +# Each of these jobs runs the TS SDK E2E tests from this commit against a local testnet +# built from one of the production release branches. In other words, we run the TS SDK +# tests against a local devnet, testnet, and mainnet. We also run the TS SDK tests for +# the indexer, though those run against the production indexer for now. + +name: "SDK E2E Tests" +on: + pull_request: + push: + branches: + - main + +jobs: + run-tests-devnet: + runs-on: high-perf-docker + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 + - uses: aptos-labs/aptos-core/.github/actions/docker-setup@main + with: + GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + GCP_SERVICE_ACCOUNT_EMAIL: ${{ secrets.GCP_SERVICE_ACCOUNT_EMAIL }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DOCKER_ARTIFACT_REPO: ${{ secrets.AWS_DOCKER_ARTIFACT_REPO }} + GIT_CREDENTIALS: ${{ secrets.GIT_CREDENTIALS }} + - uses: ./.github/actions/run-sdk-e2e-tests + with: + NETWORK: devnet + GCP_DOCKER_ARTIFACT_REPO: ${{ secrets.GCP_DOCKER_ARTIFACT_REPO }} + + run-tests-testnet: + runs-on: high-perf-docker + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 + - uses: aptos-labs/aptos-core/.github/actions/docker-setup@main + with: + GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + GCP_SERVICE_ACCOUNT_EMAIL: ${{ secrets.GCP_SERVICE_ACCOUNT_EMAIL }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DOCKER_ARTIFACT_REPO: ${{ secrets.AWS_DOCKER_ARTIFACT_REPO }} + GIT_CREDENTIALS: ${{ secrets.GIT_CREDENTIALS }} + - uses: ./.github/actions/run-sdk-e2e-tests + with: + NETWORK: testnet + GCP_DOCKER_ARTIFACT_REPO: ${{ secrets.GCP_DOCKER_ARTIFACT_REPO }} + + run-tests-mainnet: + runs-on: high-perf-docker + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 + - uses: aptos-labs/aptos-core/.github/actions/docker-setup@main + with: + GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + GCP_SERVICE_ACCOUNT_EMAIL: ${{ secrets.GCP_SERVICE_ACCOUNT_EMAIL }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DOCKER_ARTIFACT_REPO: ${{ secrets.AWS_DOCKER_ARTIFACT_REPO }} + GIT_CREDENTIALS: ${{ secrets.GIT_CREDENTIALS }} + - uses: ./.github/actions/run-sdk-e2e-tests + with: + NETWORK: mainnet + GCP_DOCKER_ARTIFACT_REPO: ${{ secrets.GCP_DOCKER_ARTIFACT_REPO }} + + # Run the TS SDK indexer tests. Note: Unlike the above tests where everything is self + # contained because we run a local testnet, these tests operate against the + # production indexer service. This service can be flaky so we don't want those tests + # to be land blocking for any PR on the aptos repo. This is why we run those tests + # separate from test-sdk-confirm-client-generated-publish. + run-indexer-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 + with: + ref: ${{ env.GIT_SHA }} + - uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b # pin@v3 + with: + node-version-file: .node-version + registry-url: "https://registry.npmjs.org" + - uses: pnpm/action-setup@537643d491d20c2712d11533497cb47b2d0eb9d5 # pin https://github.com/pnpm/action-setup/releases/tag/v2.2.3 + + # Run package install. If install fails, it probably means the lockfile + # was not included in the commit. + - run: cd ./ecosystem/typescript/sdk && pnpm install --frozen-lockfile + + # Run indexer tests. + - uses: nick-fields/retry@7f8f3d9f0f62fe5925341be21c2e8314fd4f7c7c # pin@v2 + name: ts-sdk-indexer-test + with: + max_attempts: 3 + timeout_minutes: 20 + command: cd ./ecosystem/typescript/sdk && pnpm run test:indexer + diff --git a/ecosystem/typescript/sdk/scripts/publish_ans_contract.ts b/ecosystem/typescript/sdk/scripts/publish_ans_contract.ts index f851e13e75bab2..705cf6e77e3abb 100644 --- a/ecosystem/typescript/sdk/scripts/publish_ans_contract.ts +++ b/ecosystem/typescript/sdk/scripts/publish_ans_contract.ts @@ -25,9 +25,9 @@ const ANS_TEST_ACCOUNT_ADDRESS = process.env.ANS_TEST_ACCOUNT_ADDRESS || "585fc9f0f0c54183b039ffc770ca282ebd87307916c215a3e692f2f8e4305e82"; try { - deleteAnsFolder(); + deleteAnsFolder(ANS_REPO_LOCATION); // 1. Clone ANS repository into the current directory - console.log("---clone ANS repository---"); + console.log("---cloning ANS repository---"); execSync(`git clone https://github.com/aptos-labs/aptos-names-contracts.git ${ANS_REPO_LOCATION}`, { stdio: "inherit", }); @@ -48,16 +48,17 @@ try { // 4. Delete aptos-names-contracts folder created by the git clone command console.log("---module published, deleting aptos-names-contracts folder---"); - deleteAnsFolder(); + deleteAnsFolder(ANS_REPO_LOCATION); } catch (error: any) { console.error("An error occurred:"); console.error("Status", error?.status); console.error("parsed stdout", error?.stdout?.toString("utf8")); console.error("parsed stderr", error?.stderr?.toString("utf8")); - deleteAnsFolder(); + deleteAnsFolder(ANS_REPO_LOCATION); process.exit(1); } -function deleteAnsFolder() { - execSync("rm -rf /tmp/ans", { stdio: "inherit" }); +function deleteAnsFolder(ansFolder: string) { + console.log("---deleting local ANS directory if present---"); + execSync(`rm -rf ${ansFolder}`, { stdio: "inherit" }); } diff --git a/ecosystem/typescript/sdk/src/tests/e2e/fungible_asset_client.test.ts b/ecosystem/typescript/sdk/src/tests/e2e/fungible_asset_client.test.ts index 541a23f254ece9..803a6b4c8e468f 100644 --- a/ecosystem/typescript/sdk/src/tests/e2e/fungible_asset_client.test.ts +++ b/ecosystem/typescript/sdk/src/tests/e2e/fungible_asset_client.test.ts @@ -15,13 +15,22 @@ const publisher = new AptosAccount( const alice = new AptosAccount(); const bob = new AptosAccount(); let fungibleAssetMetadataAddress = ""; + +// Do not run these tests if the network is testnet / mainnet right now. +let maybe; +if (process.env.NETWORK?.toLowerCase() == "testnet" || process.env.NETWORK?.toLowerCase() == "mainnet") { + maybe = describe.skip; +} else { + maybe = describe; +} + /** * Since there is no ready-to-use fungible asset contract/module on an aptos framework address - * we pre compiled ../../../aptos-move/move-examples/fungible_token contract and publish - * it here to local testnet so we can interact with it to mint a fungible asset and then - * test FungibleAssetClient class + * we pre compiled the ../../../aptos-move/move-examples/fungible_asset/managed_fungible_token + * contract and publish it here to local testnet so we can interact with it to mint a fungible + * asset and then test FungibleAssetClient class */ -describe("fungible asset", () => { +maybe("fungible asset", () => { /** * Publish the fungible_token module * Mint 5 amount of fungible assets to Alice account @@ -48,7 +57,7 @@ describe("fungible asset", () => { ), ], ); - await provider.waitForTransaction(txnHash); + await provider.waitForTransaction(txnHash, { checkSuccess: true }); // Mint 5 fungible assets to Alice const payload: Gen.EntryFunctionPayload = { @@ -59,7 +68,7 @@ describe("fungible asset", () => { const rawTxn = await provider.generateTransaction(publisher.address(), payload); const bcsTxn = AptosClient.generateBCSTransaction(publisher, rawTxn); const transactionRes = await provider.submitSignedBCSTransaction(bcsTxn); - await provider.waitForTransaction(transactionRes.hash); + await provider.waitForTransaction(transactionRes.hash, { checkSuccess: true }); // Get the asset address const viewPayload: Gen.ViewRequest = { @@ -84,7 +93,7 @@ describe("fungible asset", () => { // Alice transfers 2 amounts of the fungible asset to Bob const transactionHash = await fungibleAsset.transfer(alice, fungibleAssetMetadataAddress, bob.address(), 2); - await provider.waitForTransaction(transactionHash); + await provider.waitForTransaction(transactionHash, { checkSuccess: true }); // Alice has 3 amounts of the fungible asset const aliceCurrentBalance = await fungibleAsset.getPrimaryBalance(alice.address(), fungibleAssetMetadataAddress);