├── .npmrc ├── .dockerignore ├── .prettierrc.json ├── examples ├── .eslintrc.json ├── webapp │ ├── test.css │ └── test.html ├── generate_sender_receiver.js ├── asset_destroy_example.js ├── asset_freeze_example.js ├── rekey_example.js ├── typescript_example.ts ├── asset_accept_example.js ├── asset_revoke_example.js ├── asset_send_example.js ├── asset_update_example.js ├── multisig_example.js ├── transaction_group_example.js ├── logic_sig_example.js ├── notefield_example.js ├── asset_create_example.js ├── README.md └── utils.js ├── .huskyrc.js ├── .vscode ├── extensions.json └── settings.json ├── src ├── index.ts ├── types │ ├── index.ts │ ├── address.ts │ ├── account.ts │ ├── transactions │ │ ├── payment.ts │ │ ├── stateproof.ts │ │ ├── keyreg.ts │ │ ├── builder.ts │ │ ├── index.ts │ │ ├── asset.ts │ │ └── application.ts │ ├── multisig.ts │ ├── intDecoding.ts │ ├── blockHeader.ts │ └── utils.ts ├── abi │ ├── index.ts │ ├── reference.ts │ ├── interface.ts │ ├── transaction.ts │ └── contract.ts ├── client │ ├── v2 │ │ ├── algod │ │ │ ├── genesis.ts │ │ │ ├── status.ts │ │ │ ├── supply.ts │ │ │ ├── versions.ts │ │ │ ├── getAssetByID.ts │ │ │ ├── stateproof.ts │ │ │ ├── getApplicationByID.ts │ │ │ ├── lightBlockHeaderProof.ts │ │ │ ├── healthCheck.ts │ │ │ ├── statusAfterBlock.ts │ │ │ ├── getBlockHash.ts │ │ │ ├── accountAssetInformation.ts │ │ │ ├── accountApplicationInformation.ts │ │ │ ├── suggestedParams.ts │ │ │ ├── block.ts │ │ │ ├── pendingTransactions.ts │ │ │ ├── dryrun.ts │ │ │ ├── pendingTransactionInformation.ts │ │ │ ├── pendingTransactionsByAddress.ts │ │ │ ├── accountInformation.ts │ │ │ ├── getTransactionProof.ts │ │ │ ├── compile.ts │ │ │ ├── getApplicationBoxByName.ts │ │ │ ├── sendRawTransaction.ts │ │ │ └── getApplicationBoxes.ts │ │ ├── indexer │ │ │ ├── makeHealthCheck.ts │ │ │ ├── lookupTransactionByID.ts │ │ │ ├── lookupBlock.ts │ │ │ ├── lookupApplicationBoxByIDandName.ts │ │ │ ├── lookupAssetByID.ts │ │ │ ├── lookupApplications.ts │ │ │ ├── searchForApplicationBoxes.ts │ │ │ ├── lookupAccountByID.ts │ │ │ ├── searchForApplications.ts │ │ │ ├── lookupAccountAssets.ts │ │ │ ├── lookupAssetBalances.ts │ │ │ ├── lookupApplicationLogs.ts │ │ │ ├── lookupAccountCreatedAssets.ts │ │ │ ├── lookupAccountAppLocalStates.ts │ │ │ └── lookupAccountCreatedApplications.ts │ │ ├── basemodel.ts │ │ ├── serviceClient.ts │ │ └── jsonrequest.ts │ └── baseHTTPClient.ts ├── account.ts ├── convert.ts ├── encoding │ ├── bigint.ts │ ├── encoding.ts │ └── uint64.ts ├── nacl │ └── naclWrappers.ts ├── boxStorage.ts ├── logic │ └── sourcemap.ts ├── wait.ts ├── bid.ts ├── group.ts ├── signer.ts └── utils │ └── utils.ts ├── .prettierignore ├── tests ├── compile │ ├── tsconfig.json │ └── basic.ts ├── cucumber │ ├── cucumber.js │ ├── integration.tags │ ├── browser │ │ ├── webpack.config.js │ │ ├── index.html │ │ └── test.js │ ├── unit.tags │ └── docker │ │ └── Dockerfile ├── browser │ └── index.html ├── 4.Utils.ts ├── 1.Mnemonics_test.js ├── mocha.js └── 3.Address.js ├── .lintstagedrc.js ├── tsconfig-browser.json ├── tsconfig-cjs.json ├── typedoc.config.json ├── tsconfig-esm.json ├── tsconfig.json ├── .editorconfig ├── .gitignore ├── .github ├── release.yml ├── workflows │ ├── codegen.yml │ └── pr-type-category.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .test-env ├── LICENSE ├── webpack.config.js ├── Makefile ├── .eslintrc.js ├── FAQ.md ├── package.json ├── test-harness.sh └── .circleci └── config.yml /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | docs/ 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /examples/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'pre-commit': 'lint-staged', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as algosdk from './main'; 2 | 3 | export * from './main'; 4 | export default algosdk; 5 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './transactions'; 2 | export * from './multisig'; 3 | export * from './address'; 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | docs 3 | tests/cucumber/features 4 | tests/cucumber/browser/build 5 | tests/browser/bundle.* 6 | .github 7 | -------------------------------------------------------------------------------- /tests/compile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true 4 | }, 5 | "include": ["./basic.ts"] 6 | } 7 | -------------------------------------------------------------------------------- /tests/cucumber/cucumber.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: '--format-options \'{"snippetInterface": "synchronous"}\'', 3 | }; 4 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,ts,md,json,yml}': 'prettier --write', 3 | '*.{js,ts}': 'eslint --cache --fix', 4 | }; 5 | -------------------------------------------------------------------------------- /tsconfig-browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/browser" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.formatOnPaste": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/compile/basic.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /* eslint-disable */ 3 | 4 | import * as algosdk from '../../dist/esm/index'; 5 | -------------------------------------------------------------------------------- /tsconfig-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/cjs", 5 | "module": "CommonJS" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typedoc.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "excludeExternals": true, 3 | "entryPoints": ["./src/main.ts"], 4 | "entryPointStrategy": "expand", 5 | "out": "docs" 6 | } 7 | -------------------------------------------------------------------------------- /src/types/address.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Decoded Algorand address. Includes public key and checksum. 3 | */ 4 | export interface Address { 5 | publicKey: Uint8Array; 6 | checksum: Uint8Array; 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig-esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/esm", 5 | "declaration": true, 6 | "declarationDir": "./dist/types" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/abi/index.ts: -------------------------------------------------------------------------------- 1 | export * from './abi_type'; 2 | export * from './contract'; 3 | export * from './interface'; 4 | export * from './method'; 5 | export * from './transaction'; 6 | export * from './reference'; 7 | -------------------------------------------------------------------------------- /src/client/v2/algod/genesis.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | 3 | export default class Genesis extends JSONRequest { 4 | // eslint-disable-next-line class-methods-use-this 5 | path() { 6 | return '/genesis'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/client/v2/algod/status.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | 3 | export default class Status extends JSONRequest { 4 | // eslint-disable-next-line class-methods-use-this 5 | path() { 6 | return '/v2/status'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/cucumber/integration.tags: -------------------------------------------------------------------------------- 1 | @abi 2 | @algod 3 | @applications.boxes 4 | @applications.verified 5 | @assets 6 | @auction 7 | @c2c 8 | @compile 9 | @compile.sourcemap 10 | @dryrun 11 | @kmd 12 | @rekey_v1 13 | @send 14 | @send.keyregtxn 15 | -------------------------------------------------------------------------------- /src/client/v2/algod/supply.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | 3 | export default class Supply extends JSONRequest { 4 | // eslint-disable-next-line class-methods-use-this 5 | path() { 6 | return '/v2/ledger/supply'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/client/v2/algod/versions.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | 3 | /** 4 | * retrieves the VersionResponse from the running node 5 | */ 6 | export default class Versions extends JSONRequest { 7 | // eslint-disable-next-line class-methods-use-this 8 | path() { 9 | return '/versions'; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/types/account.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An Algorand account object. 3 | * 4 | * Contains an Algorand address and secret key. 5 | */ 6 | export default interface Account { 7 | /** 8 | * Algorand address 9 | */ 10 | addr: string; 11 | 12 | /** 13 | * Secret key belonging to the Algorand address 14 | */ 15 | sk: Uint8Array; 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es5", "es2015.promise", "dom", "es2015"], 4 | "outDir": "./dist", 5 | "allowJs": true, 6 | "target": "es2020", 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "sourceMap": true 11 | }, 12 | "include": ["./src"] 13 | } 14 | -------------------------------------------------------------------------------- /tests/cucumber/browser/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: path.resolve(__dirname, 'test.js'), 6 | output: { 7 | filename: 'test.js', 8 | path: path.resolve(__dirname, 'build'), 9 | }, 10 | devtool: 'source-map', 11 | plugins: [ 12 | new webpack.ProvidePlugin({ 13 | Buffer: ['buffer', 'Buffer'], 14 | }), 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | # Set JavaScript file rules 10 | [*.js] 11 | charset = utf-8 12 | indent_style = space 13 | indent_size = 2 14 | 15 | # Matches the exact files either package.json or .travis.yml 16 | [{package.json,.travis.yml}] 17 | indent_style = space 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .idea/ 4 | .vscode/* 5 | !.vscode/settings.json 6 | !.vscode/extensions.json 7 | 8 | # npm 9 | node_modules/ 10 | 11 | # Testing files 12 | *.feature 13 | temp 14 | 15 | # Environment information 16 | examples/.env 17 | 18 | test-harness/ 19 | tests/cucumber/features/ 20 | tests/cucumber/browser/build 21 | tests/browser/bundle.* 22 | 23 | # Builds 24 | dist/ 25 | docs/ 26 | built/ 27 | 28 | # Caches 29 | .eslintcache 30 | -------------------------------------------------------------------------------- /src/types/transactions/payment.ts: -------------------------------------------------------------------------------- 1 | import { TransactionType, TransactionParams } from './base'; 2 | import { ConstructTransaction } from './builder'; 3 | 4 | type SpecificParameters = Pick< 5 | TransactionParams, 6 | 'to' | 'amount' | 'closeRemainderTo' 7 | >; 8 | 9 | interface Overwrites { 10 | type?: TransactionType.pay; 11 | } 12 | 13 | type PaymentTransaction = ConstructTransaction; 14 | export default PaymentTransaction; 15 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - Skip-Release-Notes 5 | categories: 6 | - title: Bugfixes 7 | labels: 8 | - Bug-Fix 9 | - title: New Features 10 | labels: 11 | - New Feature 12 | - title: Enhancements 13 | labels: 14 | - Enhancement 15 | - title: Not Yet Enabled 16 | labels: 17 | - Not-Yet-Enabled 18 | - title: Other 19 | labels: 20 | - "*" 21 | -------------------------------------------------------------------------------- /src/account.ts: -------------------------------------------------------------------------------- 1 | import * as nacl from './nacl/naclWrappers'; 2 | import * as address from './encoding/address'; 3 | import Account from './types/account'; 4 | 5 | /** 6 | * generateAccount returns a new Algorand address and its corresponding secret key 7 | */ 8 | export default function generateAccount(): Account { 9 | const keys = nacl.keyPair(); 10 | const encodedPk = address.encodeAddress(keys.publicKey); 11 | return { addr: encodedPk, sk: keys.secretKey }; 12 | } 13 | -------------------------------------------------------------------------------- /src/client/v2/algod/getAssetByID.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class GetAssetByID extends JSONRequest { 6 | constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { 7 | super(c, intDecoding); 8 | this.index = index; 9 | } 10 | 11 | path() { 12 | return `/v2/assets/${this.index}`; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/client/v2/algod/stateproof.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class StateProof extends JSONRequest { 6 | constructor(c: HTTPClient, intDecoding: IntDecoding, private round: number) { 7 | super(c, intDecoding); 8 | 9 | this.round = round; 10 | } 11 | 12 | path() { 13 | return `/v2/stateproofs/${this.round}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/client/v2/algod/getApplicationByID.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class GetApplicationByID extends JSONRequest { 6 | constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { 7 | super(c, intDecoding); 8 | this.index = index; 9 | } 10 | 11 | path() { 12 | return `/v2/applications/${this.index}`; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/client/v2/algod/lightBlockHeaderProof.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LightBlockHeaderProof extends JSONRequest { 6 | constructor(c: HTTPClient, intDecoding: IntDecoding, private round: number) { 7 | super(c, intDecoding); 8 | 9 | this.round = round; 10 | } 11 | 12 | path() { 13 | return `/v2/blocks/${this.round}/lightheader/proof`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/types/transactions/stateproof.ts: -------------------------------------------------------------------------------- 1 | import { TransactionType, TransactionParams } from './base'; 2 | import { ConstructTransaction } from './builder'; 3 | 4 | type SpecificParameters = Pick< 5 | TransactionParams, 6 | 'stateProofType' | 'stateProof' | 'stateProofMessage' 7 | >; 8 | 9 | interface Overwrites { 10 | type?: TransactionType.stpf; 11 | } 12 | 13 | type StateProofTransaction = ConstructTransaction< 14 | SpecificParameters, 15 | Overwrites 16 | >; 17 | export default StateProofTransaction; 18 | -------------------------------------------------------------------------------- /tests/cucumber/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Algosdk Browser Testing 5 | 9 | 10 | 11 | 12 | 13 | 14 |

Algosdk Browser Testing

15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/codegen.yml: -------------------------------------------------------------------------------- 1 | name: "SDK Code Generation" 2 | on: 3 | schedule: 4 | - cron: '20 23 * * *' 5 | permissions: 6 | contents: write 7 | pull-requests: write 8 | jobs: 9 | generate_and_pr: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out repository 13 | uses: actions/checkout@v3 14 | - name: Generate and PR 15 | uses: algorand/generator/.github/actions/sdk-codegen/@master 16 | with: 17 | args: "-k JS" 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /src/client/v2/algod/healthCheck.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | 3 | /** 4 | * healthCheck returns an empty object iff the node is running 5 | */ 6 | export default class HealthCheck extends JSONRequest { 7 | // eslint-disable-next-line class-methods-use-this 8 | path() { 9 | return '/health'; 10 | } 11 | 12 | async do(headers = {}) { 13 | const res = await this.c.get(this.path(), {}, headers); 14 | if (!res.ok) { 15 | throw new Error(`Health response: ${res.status}`); 16 | } 17 | return {}; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/abi/reference.ts: -------------------------------------------------------------------------------- 1 | export enum ABIReferenceType { 2 | /** 3 | * Account reference type 4 | */ 5 | account = 'account', 6 | 7 | /** 8 | * Application reference type 9 | */ 10 | application = 'application', 11 | 12 | /** 13 | * Asset reference type 14 | */ 15 | asset = 'asset', 16 | } 17 | 18 | export function abiTypeIsReference(type: any): type is ABIReferenceType { 19 | return ( 20 | type === ABIReferenceType.account || 21 | type === ABIReferenceType.application || 22 | type === ABIReferenceType.asset 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/client/v2/algod/statusAfterBlock.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class StatusAfterBlock extends JSONRequest { 6 | constructor(c: HTTPClient, intDecoding: IntDecoding, private round: number) { 7 | super(c, intDecoding); 8 | if (!Number.isInteger(round)) throw Error('round should be an integer'); 9 | this.round = round; 10 | } 11 | 12 | path() { 13 | return `/v2/status/wait-for-block-after/${this.round}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F514 Feature Request" 3 | about: Suggestions for how we can improve the algorand platform. 4 | title: '' 5 | labels: new-feature-request 6 | assignees: '' 7 | --- 8 | 9 | ## Problem 10 | 11 | 12 | 13 | ## Solution 14 | 15 | 16 | 17 | ## Dependencies 18 | 19 | 20 | 21 | ## Urgency 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/client/v2/algod/getBlockHash.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class GetBlockHash extends JSONRequest { 6 | round: number; 7 | 8 | constructor(c: HTTPClient, intDecoding: IntDecoding, roundNumber: number) { 9 | super(c, intDecoding); 10 | if (!Number.isInteger(roundNumber)) 11 | throw Error('roundNumber should be an integer'); 12 | this.round = roundNumber; 13 | } 14 | 15 | path() { 16 | return `/v2/blocks/${this.round}/hash`; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/types/transactions/keyreg.ts: -------------------------------------------------------------------------------- 1 | import { TransactionType, TransactionParams } from './base'; 2 | import { ConstructTransaction } from './builder'; 3 | 4 | type SpecificParameters = Pick< 5 | TransactionParams, 6 | | 'voteKey' 7 | | 'selectionKey' 8 | | 'stateProofKey' 9 | | 'voteFirst' 10 | | 'voteLast' 11 | | 'voteKeyDilution' 12 | | 'nonParticipation' 13 | >; 14 | 15 | interface Overwrites { 16 | type?: TransactionType.keyreg; 17 | } 18 | 19 | type KeyRegistrationTransaction = ConstructTransaction< 20 | SpecificParameters, 21 | Overwrites 22 | >; 23 | export default KeyRegistrationTransaction; 24 | -------------------------------------------------------------------------------- /.test-env: -------------------------------------------------------------------------------- 1 | # Configs for testing repo download: 2 | SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" 3 | SDK_TESTING_BRANCH="master" 4 | SDK_TESTING_HARNESS="test-harness" 5 | 6 | INSTALL_ONLY=0 7 | 8 | VERBOSE_HARNESS=0 9 | 10 | # WARNING: If set to 1, new features will be LOST when downloading the test harness. 11 | # REGARDLESS: modified features are ALWAYS overwritten. 12 | REMOVE_LOCAL_FEATURES=0 13 | 14 | # WARNING: Be careful when turning on the next variable. 15 | # In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env` 16 | OVERWRITE_TESTING_ENVIRONMENT=0 17 | -------------------------------------------------------------------------------- /src/client/v2/algod/accountAssetInformation.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class AccountAssetInformation extends JSONRequest { 6 | constructor( 7 | c: HTTPClient, 8 | intDecoding: IntDecoding, 9 | private account: string, 10 | private assetID: number 11 | ) { 12 | super(c, intDecoding); 13 | this.account = account; 14 | this.assetID = assetID; 15 | } 16 | 17 | path() { 18 | return `/v2/accounts/${this.account}/assets/${this.assetID}`; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/client/v2/algod/accountApplicationInformation.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class AccountApplicationInformation extends JSONRequest { 6 | constructor( 7 | c: HTTPClient, 8 | intDecoding: IntDecoding, 9 | private account: string, 10 | private applicationID: number 11 | ) { 12 | super(c, intDecoding); 13 | this.account = account; 14 | this.applicationID = applicationID; 15 | } 16 | 17 | path() { 18 | return `/v2/accounts/${this.account}/applications/${this.applicationID}`; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/client/v2/indexer/makeHealthCheck.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | 3 | /** 4 | * Returns the health object for the service. 5 | * Returns 200 if healthy. 6 | * 7 | * #### Example 8 | * ```typescript 9 | * const health = await indexerClient.makeHealthCheck().do(); 10 | * ``` 11 | * 12 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-health) 13 | * @category GET 14 | */ 15 | export default class MakeHealthCheck extends JSONRequest { 16 | /** 17 | * @returns `/health` 18 | */ 19 | // eslint-disable-next-line class-methods-use-this 20 | path() { 21 | return '/health'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/types/multisig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Required options for creating a multisignature 3 | * 4 | * Documentation available at: https://developer.algorand.org/docs/features/transactions/signatures/#multisignatures 5 | */ 6 | export interface MultisigMetadata { 7 | /** 8 | * Multisig version 9 | */ 10 | version: number; 11 | 12 | /** 13 | * Multisig threshold value. Authorization requires a subset of signatures, 14 | * equal to or greater than the threshold value. 15 | */ 16 | threshold: number; 17 | 18 | /** 19 | * A list of Algorand addresses representing possible signers for this multisig. Order is important. 20 | */ 21 | addrs: string[]; 22 | } 23 | -------------------------------------------------------------------------------- /tests/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Algosdk Mocha Browser Testing 5 | 6 | 7 | 8 | 9 |

Algosdk Mocha Browser Testing

10 |
11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/cucumber/unit.tags: -------------------------------------------------------------------------------- 1 | @unit.abijson 2 | @unit.abijson.byname 3 | @unit.algod 4 | @unit.algod.ledger_refactoring 5 | @unit.applications 6 | @unit.applications.boxes 7 | @unit.atomic_transaction_composer 8 | @unit.blocksummary 9 | @unit.dryrun 10 | @unit.dryrun.trace.application 11 | @unit.feetest 12 | @unit.indexer 13 | @unit.indexer.ledger_refactoring 14 | @unit.indexer.logs 15 | @unit.offline 16 | @unit.program_sanity_check 17 | @unit.rekey 18 | @unit.responses 19 | @unit.responses.231 20 | @unit.responses.participationupdates 21 | @unit.responses.unlimited_assets 22 | @unit.responses.blocksummary 23 | @unit.sourcemap 24 | @unit.stateproof.paths 25 | @unit.stateproof.responses 26 | @unit.stateproof.responses.msgp 27 | @unit.tealsign 28 | @unit.transactions 29 | @unit.transactions.keyreg 30 | @unit.transactions.payment 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41C Bug report" 3 | about: Report a reproducible bug. 4 | title: '' 5 | labels: new-bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Subject of the issue 11 | 12 | 13 | 14 | ### Your environment 15 | 16 | 22 | 23 | ### Steps to reproduce 24 | 25 | 1. 26 | 2. 27 | 28 | ### Expected behaviour 29 | 30 | ### Actual behaviour 31 | -------------------------------------------------------------------------------- /src/convert.ts: -------------------------------------------------------------------------------- 1 | const MICROALGOS_TO_ALGOS_RATIO = 1e6; 2 | export const INVALID_MICROALGOS_ERROR_MSG = 3 | 'Microalgos should be positive and less than 2^53 - 1.'; 4 | 5 | /** 6 | * microalgosToAlgos converts microalgos to algos 7 | * @param microalgos - number 8 | * @returns number 9 | */ 10 | export function microalgosToAlgos(microalgos: number) { 11 | if (microalgos < 0 || !Number.isSafeInteger(microalgos)) { 12 | throw new Error(INVALID_MICROALGOS_ERROR_MSG); 13 | } 14 | return microalgos / MICROALGOS_TO_ALGOS_RATIO; 15 | } 16 | 17 | /** 18 | * algosToMicroalgos converts algos to microalgos 19 | * @param algos - number 20 | * @returns number 21 | */ 22 | export function algosToMicroalgos(algos: number) { 23 | const microalgos = algos * MICROALGOS_TO_ALGOS_RATIO; 24 | return Math.round(microalgos); 25 | } 26 | -------------------------------------------------------------------------------- /src/types/intDecoding.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure how integers in JSON response will be decoded. 3 | */ 4 | enum IntDecoding { 5 | /** 6 | * All integers will be decoded as Numbers, meaning any values greater than 7 | * Number.MAX_SAFE_INTEGER will lose precision. 8 | */ 9 | DEFAULT = 'default', 10 | 11 | /** 12 | * All integers will be decoded as Numbers, but if any values are greater than 13 | * Number.MAX_SAFE_INTEGER an error will be thrown. 14 | */ 15 | SAFE = 'safe', 16 | 17 | /** 18 | * Integers will be decoded as Numbers if they are less than or equal to 19 | * Number.MAX_SAFE_INTEGER, otherwise they will be decoded as BigInts. 20 | */ 21 | MIXED = 'mixed', 22 | 23 | /** 24 | * All integers will be decoded as BigInts. 25 | */ 26 | BIGINT = 'bigint', 27 | } 28 | 29 | export default IntDecoding; 30 | -------------------------------------------------------------------------------- /src/client/v2/algod/suggestedParams.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import { SuggestedParams } from '../../../types/transactions/base'; 3 | 4 | /** 5 | * Returns the common needed parameters for a new transaction, in a format the transaction builder expects 6 | */ 7 | export default class SuggestedParamsRequest extends JSONRequest { 8 | /* eslint-disable class-methods-use-this */ 9 | path() { 10 | return '/v2/transactions/params'; 11 | } 12 | 13 | prepare(body: Record): SuggestedParams { 14 | return { 15 | flatFee: false, 16 | fee: body.fee, 17 | firstRound: body['last-round'], 18 | lastRound: body['last-round'] + 1000, 19 | genesisID: body['genesis-id'], 20 | genesisHash: body['genesis-hash'], 21 | }; 22 | } 23 | /* eslint-enable class-methods-use-this */ 24 | } 25 | -------------------------------------------------------------------------------- /src/client/v2/algod/block.ts: -------------------------------------------------------------------------------- 1 | import * as encoding from '../../../encoding/encoding'; 2 | import JSONRequest from '../jsonrequest'; 3 | import HTTPClient from '../../client'; 4 | 5 | /** 6 | * block gets the block info for the given round. this call may block 7 | */ 8 | export default class Block extends JSONRequest { 9 | private round: number; 10 | 11 | constructor(c: HTTPClient, roundNumber: number) { 12 | super(c); 13 | if (!Number.isInteger(roundNumber)) 14 | throw Error('roundNumber should be an integer'); 15 | this.round = roundNumber; 16 | this.query = { format: 'msgpack' }; 17 | } 18 | 19 | path() { 20 | return `/v2/blocks/${this.round}`; 21 | } 22 | 23 | // eslint-disable-next-line class-methods-use-this 24 | prepare(body: Uint8Array) { 25 | if (body && body.byteLength > 0) { 26 | return encoding.decode(body) as Record; 27 | } 28 | return undefined; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/client/v2/algod/pendingTransactions.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import * as encoding from '../../../encoding/encoding'; 4 | 5 | /** 6 | * pendingTransactionsInformation returns transactions that are pending in the pool 7 | */ 8 | export default class PendingTransactions extends JSONRequest { 9 | constructor(c: HTTPClient) { 10 | super(c); 11 | this.query.format = 'msgpack'; 12 | } 13 | 14 | /* eslint-disable class-methods-use-this */ 15 | path() { 16 | return '/v2/transactions/pending'; 17 | } 18 | 19 | prepare(body: Uint8Array) { 20 | if (body && body.byteLength > 0) { 21 | return encoding.decode(body) as Record; 22 | } 23 | return undefined; 24 | } 25 | /* eslint-enable class-methods-use-this */ 26 | 27 | // max sets the maximum number of txs to return 28 | max(max: number) { 29 | this.query.max = max; 30 | return this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/client/v2/algod/dryrun.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import * as modelsv2 from './models/types'; 4 | import * as encoding from '../../../encoding/encoding'; 5 | import { setHeaders } from './compile'; 6 | 7 | export default class Dryrun extends JSONRequest { 8 | private blob: Uint8Array; 9 | 10 | constructor(c: HTTPClient, dr: modelsv2.DryrunRequest) { 11 | super(c); 12 | this.blob = encoding.encode(dr.get_obj_for_encoding(true)); 13 | } 14 | 15 | // eslint-disable-next-line class-methods-use-this 16 | path() { 17 | return '/v2/teal/dryrun'; 18 | } 19 | 20 | /** 21 | * Executes dryrun 22 | * @param headers - A headers object 23 | */ 24 | async do(headers = {}) { 25 | const txHeaders = setHeaders(headers); 26 | const res = await this.c.post( 27 | this.path(), 28 | Buffer.from(this.blob), 29 | txHeaders 30 | ); 31 | return res.body; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/pr-type-category.yml: -------------------------------------------------------------------------------- 1 | name: Check PR category and type 2 | on: 3 | pull_request: 4 | branches: 5 | - develop 6 | types: [opened, synchronize, reopened, labeled, unlabeled, edited] 7 | jobs: 8 | check_label: 9 | runs-on: ubuntu-latest 10 | name: Check PR Category and Type 11 | steps: 12 | - name: Checking for correct number of required github pr labels 13 | uses: mheap/github-action-required-labels@v2 14 | with: 15 | mode: exactly 16 | count: 1 17 | labels: "New Feature, Enhancement, Bug-Fix, Not-Yet-Enabled, Skip-Release-Notes" 18 | 19 | - name: "Checking for PR Category in PR title. Should be like ': '." 20 | run: | 21 | if [[ ! "${{ github.event.pull_request.title }}" =~ ^.{2,}\:.{2,} ]]; then 22 | echo "## PR Category is missing from PR title. Please add it like ': '." >> GITHUB_STEP_SUMMARY 23 | exit 1 24 | fi 25 | -------------------------------------------------------------------------------- /examples/webapp/test.css: -------------------------------------------------------------------------------- 1 | .styled { 2 | border: 0; 3 | line-height: 2.5; 4 | padding: 0 20px; 5 | font-size: 1rem; 6 | text-align: center; 7 | color: #fff; 8 | text-shadow: 1px 1px 1px #000; 9 | border-radius: 10px; 10 | background-color: rgb(126, 134, 227); 11 | background-image: linear-gradient( 12 | to top left, 13 | rgba(0, 0, 0, 0.2), 14 | rgba(0, 0, 0, 0.2) 30%, 15 | rgba(0, 0, 0, 0) 16 | ); 17 | box-shadow: inset 2px 2px 3px rgba(255, 255, 255, 0.6), 18 | inset -2px -2px 3px rgba(0, 0, 0, 0.6); 19 | } 20 | 21 | .styled:hover { 22 | background-color: rgba(126, 134, 227, 1); 23 | } 24 | 25 | .styled:active { 26 | box-shadow: inset -2px -2px 3px rgba(255, 255, 255, 0.6), 27 | inset 2px 2px 3px rgba(0, 0, 0, 0.6); 28 | } 29 | body { 30 | padding: 3rem; 31 | font-size: 16px; 32 | } 33 | 34 | textarea { 35 | width: 100%; 36 | min-height: 30rem; 37 | font-family: 'Lucida Console', Monaco, monospace; 38 | font-size: 0.8rem; 39 | line-height: 1.2; 40 | } 41 | -------------------------------------------------------------------------------- /src/client/v2/algod/pendingTransactionInformation.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import * as encoding from '../../../encoding/encoding'; 4 | 5 | /** 6 | * returns the transaction information for a specific txid of a pending transaction 7 | */ 8 | export default class PendingTransactionInformation extends JSONRequest { 9 | constructor(c: HTTPClient, private txid: string) { 10 | super(c); 11 | this.txid = txid; 12 | this.query.format = 'msgpack'; 13 | } 14 | 15 | // eslint-disable-next-line class-methods-use-this 16 | prepare(body: Uint8Array) { 17 | if (body && body.byteLength > 0) { 18 | return encoding.decode(body) as Record; 19 | } 20 | return undefined; 21 | } 22 | 23 | path() { 24 | return `/v2/transactions/pending/${this.txid}`; 25 | } 26 | 27 | // max sets the maximum number of txs to return 28 | max(max: number) { 29 | this.query.max = max; 30 | return this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/client/v2/algod/pendingTransactionsByAddress.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import * as encoding from '../../../encoding/encoding'; 4 | 5 | /** 6 | * returns all transactions for a PK [addr] in the [first, last] rounds range. 7 | */ 8 | export default class PendingTransactionsByAddress extends JSONRequest { 9 | constructor(c: HTTPClient, private address: string) { 10 | super(c); 11 | this.address = address; 12 | this.query.format = 'msgpack'; 13 | } 14 | 15 | // eslint-disable-next-line class-methods-use-this 16 | prepare(body: Uint8Array): Record { 17 | if (body && body.byteLength > 0) { 18 | return encoding.decode(body) as Record; 19 | } 20 | return undefined; 21 | } 22 | 23 | path() { 24 | return `/v2/accounts/${this.address}/transactions/pending`; 25 | } 26 | 27 | // max sets the maximum number of txs to return 28 | max(max: number) { 29 | this.query.max = max; 30 | return this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/client/v2/algod/accountInformation.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class AccountInformation extends JSONRequest { 6 | constructor( 7 | c: HTTPClient, 8 | intDecoding: IntDecoding, 9 | private account: string 10 | ) { 11 | super(c, intDecoding); 12 | this.account = account; 13 | } 14 | 15 | path() { 16 | return `/v2/accounts/${this.account}`; 17 | } 18 | 19 | /** 20 | * Exclude assets and application data from results 21 | * 22 | * #### Example 23 | * ```typescript 24 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 25 | * const accountInfo = await algodClient.accountInformation(address) 26 | * .exclude('all') 27 | * .do(); 28 | * ``` 29 | * 30 | * @param round 31 | * @category query 32 | */ 33 | exclude(exclude: string) { 34 | this.query.exclude = exclude; 35 | return this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupTransactionByID.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupTransactionByID extends JSONRequest { 6 | /** 7 | * Returns information about the given transaction. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const txnId = "MEUOC4RQJB23CQZRFRKYEI6WBO73VTTPST5A7B3S5OKBUY6LFUDA"; 12 | * const txnInfo = await indexerClient.lookupTransactionByID(txnId).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2transactionstxid) 16 | * @param txID - The ID of the transaction to look up. 17 | * @category GET 18 | */ 19 | constructor(c: HTTPClient, intDecoding: IntDecoding, private txID: string) { 20 | super(c, intDecoding); 21 | this.txID = txID; 22 | } 23 | 24 | /** 25 | * @returns `/v2/transactions/${txID}` 26 | */ 27 | path() { 28 | return `/v2/transactions/${this.txID}`; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/abi/interface.ts: -------------------------------------------------------------------------------- 1 | import { ABIMethod, ABIMethodParams, getMethodByName } from './method'; 2 | 3 | export interface ABIInterfaceParams { 4 | name: string; 5 | desc?: string; 6 | methods: ABIMethodParams[]; 7 | } 8 | 9 | export class ABIInterface { 10 | public readonly name: string; 11 | public readonly description?: string; 12 | public readonly methods: ABIMethod[]; 13 | 14 | constructor(params: ABIInterfaceParams) { 15 | if (typeof params.name !== 'string' || !Array.isArray(params.methods)) { 16 | throw new Error('Invalid ABIInterface parameters'); 17 | } 18 | 19 | this.name = params.name; 20 | this.description = params.desc; 21 | this.methods = params.methods.map((method) => new ABIMethod(method)); 22 | } 23 | 24 | toJSON(): ABIInterfaceParams { 25 | return { 26 | name: this.name, 27 | desc: this.description, 28 | methods: this.methods.map((method) => method.toJSON()), 29 | }; 30 | } 31 | 32 | getMethodByName(name: string): ABIMethod { 33 | return getMethodByName(this.methods, name); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Algorand, llc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/encoding/bigint.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * bigIntToBytes converts a BigInt to a big-endian Uint8Array for encoding. 3 | * @param bi - The bigint to convert. 4 | * @param size - The size of the resulting byte array. 5 | * @returns A byte array containing the big-endian encoding of the input bigint 6 | */ 7 | export function bigIntToBytes(bi: bigint | number, size: number) { 8 | let hex = bi.toString(16); 9 | // Pad the hex with zeros so it matches the size in bytes 10 | if (hex.length !== size * 2) { 11 | hex = hex.padStart(size * 2, '0'); 12 | } 13 | const byteArray = new Uint8Array(hex.length / 2); 14 | for (let i = 0, j = 0; i < hex.length / 2; i++, j += 2) { 15 | byteArray[i] = parseInt(hex.slice(j, j + 2), 16); 16 | } 17 | return byteArray; 18 | } 19 | 20 | /** 21 | * bytesToBigInt produces a bigint from a binary representation. 22 | * 23 | * @param bytes - The Uint8Array to convert. 24 | * @returns The bigint that was encoded in the input data. 25 | */ 26 | export function bytesToBigInt(bytes: Uint8Array) { 27 | let res = BigInt(0); 28 | const buf = Buffer.from(bytes); 29 | for (let i = 0; i < bytes.length; i++) { 30 | res = BigInt(Number(buf.readUIntBE(i, 1))) + res * BigInt(256); 31 | } 32 | return res; 33 | } 34 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupBlock.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupBlock extends JSONRequest { 6 | /** 7 | * Returns the block for the passed round. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const targetBlock = 18309917; 12 | * const blockInfo = await indexerClient.lookupBlock(targetBlock).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2blocksround-number) 16 | * @param round - The number of the round to look up. 17 | * @category GET 18 | */ 19 | constructor(c: HTTPClient, intDecoding: IntDecoding, private round: number) { 20 | super(c, intDecoding); 21 | this.round = round; 22 | } 23 | 24 | /** 25 | * @returns `/v2/blocks/${round}` 26 | */ 27 | path() { 28 | return `/v2/blocks/${this.round}`; 29 | } 30 | 31 | /** 32 | * Header only flag. When this is set to true, returned block does not contain the 33 | * transactions. 34 | */ 35 | headerOnly(headerOnly: boolean) { 36 | this.query['header-only'] = headerOnly; 37 | return this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/client/v2/algod/getTransactionProof.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class GetTransactionProof extends JSONRequest { 6 | constructor( 7 | c: HTTPClient, 8 | intDecoding: IntDecoding, 9 | private round: number, 10 | private txID: string 11 | ) { 12 | super(c, intDecoding); 13 | 14 | this.round = round; 15 | this.txID = txID; 16 | } 17 | 18 | path() { 19 | return `/v2/blocks/${this.round}/transactions/${this.txID}/proof`; 20 | } 21 | 22 | /** 23 | * Exclude assets and application data from results 24 | * The type of hash function used to create the proof, must be one of: "sha512_256", "sha256" 25 | * 26 | * #### Example 27 | * ```typescript 28 | * const hashType = "sha256"; 29 | * const round = 123456; 30 | * const txId = "abc123; 31 | * const txProof = await algodClient.getTransactionProof(round, txId) 32 | * .hashType(hashType) 33 | * .do(); 34 | * ``` 35 | * 36 | * @param hashType 37 | * @category query 38 | */ 39 | hashType(hashType: string) { 40 | this.query.hashtype = hashType; 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/cucumber/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | # install wget, gnupg2, make 4 | RUN apt-get update -qqy \ 5 | && apt-get -qqy install wget gnupg2 make 6 | 7 | # install chrome, firefox 8 | # based on https://github.com/SeleniumHQ/docker-selenium/blob/trunk/NodeChrome/Dockerfile 9 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ 10 | && echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list \ 11 | && apt-get update -qqy \ 12 | && apt-get -qqy --no-install-recommends install google-chrome-stable firefox 13 | 14 | # install node 15 | RUN wget -q -O - https://deb.nodesource.com/setup_14.x | bash \ 16 | && apt-get -qqy --no-install-recommends install nodejs \ 17 | && echo "node version: $(node --version)" \ 18 | && echo "npm version: $(npm --version)" 19 | 20 | # Copy SDK code into the container 21 | RUN mkdir -p $HOME/js-algorand-sdk 22 | COPY . $HOME/js-algorand-sdk 23 | WORKDIR $HOME/js-algorand-sdk 24 | 25 | ARG TEST_BROWSER 26 | ENV TEST_BROWSER=$TEST_BROWSER 27 | 28 | ARG CI 29 | ENV CI=$CI 30 | 31 | RUN npm ci 32 | RUN npm run prepare-browser-tests 33 | 34 | # Run integration tests 35 | CMD ["/bin/bash", "-c", "make unit && make integration"] 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: './src/index.ts', 7 | output: { 8 | filename: 'algosdk.min.js', 9 | path: path.resolve(__dirname, 'dist/browser'), 10 | library: { 11 | type: 'umd', 12 | name: 'algosdk', 13 | }, 14 | }, 15 | devtool: 'source-map', 16 | resolve: { 17 | // Add '.ts' as resolvable extensions 18 | extensions: ['.ts', '.js'], 19 | }, 20 | plugins: [ 21 | new webpack.ProvidePlugin({ 22 | Buffer: ['buffer', 'Buffer'], 23 | }), 24 | ], 25 | module: { 26 | rules: [ 27 | // All files with a '.ts' extension will be handled by 'ts-loader'. 28 | { 29 | test: /\.ts$/, 30 | loader: 'ts-loader', 31 | options: { 32 | configFile: path.resolve(__dirname, 'tsconfig-browser.json'), 33 | }, 34 | }, 35 | 36 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 37 | { test: /\.js$/, loader: 'source-map-loader' }, 38 | ], 39 | // Don't parse tweetnacl module — https://github.com/dchest/tweetnacl-js/wiki/Using-with-Webpack 40 | noParse: [/[\\/]tweetnacl[\\/]/, /[\\/]tweetnacl-auth[\\/]/], 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/client/v2/algod/compile.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | 4 | /** 5 | * Sets the default header (if not previously set) 6 | * @param headers - A headers object 7 | */ 8 | export function setHeaders(headers = {}) { 9 | let hdrs = headers; 10 | if (Object.keys(hdrs).every((key) => key.toLowerCase() !== 'content-type')) { 11 | hdrs = { ...headers }; 12 | hdrs['Content-Type'] = 'text/plain'; 13 | } 14 | return hdrs; 15 | } 16 | 17 | /** 18 | * Executes compile 19 | */ 20 | export default class Compile extends JSONRequest { 21 | constructor(c: HTTPClient, private source: string | Uint8Array) { 22 | super(c); 23 | this.source = source; 24 | } 25 | 26 | // eslint-disable-next-line class-methods-use-this 27 | path() { 28 | return `/v2/teal/compile`; 29 | } 30 | 31 | sourcemap(map: boolean = true) { 32 | this.query.sourcemap = map; 33 | return this; 34 | } 35 | 36 | /** 37 | * Executes compile 38 | * @param headers - A headers object 39 | */ 40 | async do(headers = {}) { 41 | const txHeaders = setHeaders(headers); 42 | const res = await this.c.post( 43 | this.path(), 44 | Buffer.from(this.source), 45 | txHeaders, 46 | this.query 47 | ); 48 | return res.body; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/generate_sender_receiver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example: generating sender and receiver accounts 3 | * This example demonstrates generating multiple accounts. Please 4 | * feel free to use this example to generate test accounts needed 5 | * for some of the other examples. 6 | */ 7 | 8 | const algosdk = require('../src'); 9 | const { fmt } = require('./utils'); 10 | 11 | // generate accounts 12 | const { sk: senderSk, addr: senderAddr } = algosdk.generateAccount(); 13 | const { sk: receiverSk, addr: receiverAddr } = algosdk.generateAccount(); 14 | 15 | // log the mnemonics and addresses 16 | const senderMnemonic = algosdk.secretKeyToMnemonic(senderSk); 17 | const receiverMnemonic = algosdk.secretKeyToMnemonic(receiverSk); 18 | 19 | console.log(` 20 | ${fmt.bold}Sender:${fmt.reset} 21 | ${fmt.dim}Mnemonic:${fmt.reset} ${senderMnemonic} 22 | ${fmt.dim}Address:${fmt.reset} ${senderAddr} 23 | 24 | ${fmt.dim}--------------------${fmt.reset} 25 | 26 | ${fmt.bold}Receiver:${fmt.reset} 27 | ${fmt.dim}Mnemonic:${fmt.reset} ${receiverMnemonic} 28 | ${fmt.dim}Address:${fmt.reset} ${receiverAddr} 29 | 30 | ${fmt.dim}--------------------${fmt.reset} 31 | 32 | ${fmt.bold}TIP:${fmt.reset} You can send funds to your accounts using the testnet and betanet dispensers listed below: 33 | * https://bank.testnet.algorand.network 34 | * https://bank.betanet.algodev.network/ 35 | `); 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UNIT_TAGS := "$(subst :, or ,$(shell awk '{print $2}' tests/cucumber/unit.tags | paste -s -d: -))" 2 | INTEGRATIONS_TAGS := "$(subst :, or ,$(shell awk '{print $2}' tests/cucumber/integration.tags | paste -s -d: -))" 3 | 4 | unit: 5 | node_modules/.bin/cucumber-js --tags $(UNIT_TAGS) tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js 6 | 7 | integration: 8 | node_modules/.bin/cucumber-js --tags $(INTEGRATIONS_TAGS) tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js 9 | 10 | # The following assumes that all cucumber steps are defined in `./tests/cucumber/steps/steps.js` and begin past line 135 of that file. 11 | # Please note any deviations of the above before presuming correctness. 12 | display-all-js-steps: 13 | tail -n +135 tests/cucumber/steps/steps.js | grep -v '^ *//' | awk "/(Given|Then|When)/,/',/" | grep -E "\'.+\'" | sed "s/^[^']*'\([^']*\)'.*/\1/g" 14 | 15 | harness: 16 | ./test-harness.sh up 17 | 18 | harness-down: 19 | ./test-harness.sh down 20 | 21 | docker-build: 22 | docker build -t js-sdk-testing -f tests/cucumber/docker/Dockerfile $(CURDIR) --build-arg TEST_BROWSER --build-arg CI=true 23 | 24 | docker-run: 25 | docker ps -a 26 | docker run -it --network host js-sdk-testing:latest 27 | 28 | docker-test: harness docker-build docker-run 29 | 30 | format: 31 | npm run format 32 | -------------------------------------------------------------------------------- /examples/asset_destroy_example.js: -------------------------------------------------------------------------------- 1 | // Example: destroying an asset 2 | 3 | const algosdk = require('../src'); 4 | 5 | async function main() { 6 | const { 7 | sk: creatorPrivateKey, 8 | addr: creatorAddress, 9 | } = algosdk.generateAccount(); 10 | 11 | const feePerByte = 10; 12 | const firstValidRound = 1000; 13 | const lastValidRound = 2000; 14 | const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; 15 | 16 | const assetIndex = 1234; // identifying index of the asset 17 | 18 | // set suggested parameters 19 | // in most cases, we suggest fetching recommended transaction parameters 20 | // using the `algosdk.Algodv2.getTransactionParams()` method 21 | const suggestedParams = { 22 | fee: feePerByte, 23 | firstRound: firstValidRound, 24 | lastRound: lastValidRound, 25 | genesisHash, 26 | }; 27 | 28 | // create the asset destroy transaction 29 | const transactionOptions = { 30 | from: creatorAddress, 31 | assetIndex, 32 | suggestedParams, 33 | }; 34 | 35 | const txn = algosdk.makeAssetDestroyTxnWithSuggestedParamsFromObject( 36 | transactionOptions 37 | ); 38 | 39 | // sign the transaction 40 | const signedTxn = txn.signTxn(creatorPrivateKey); 41 | 42 | // print transaction data 43 | const decoded = algosdk.decodeSignedTransaction(signedTxn); 44 | console.log(decoded); 45 | } 46 | 47 | main().catch(console.error); 48 | -------------------------------------------------------------------------------- /src/abi/transaction.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '../transaction'; 2 | 3 | export enum ABITransactionType { 4 | /** 5 | * Any transaction type 6 | */ 7 | any = 'txn', 8 | 9 | /** 10 | * Payment transaction type 11 | */ 12 | pay = 'pay', 13 | 14 | /** 15 | * Key registration transaction type 16 | */ 17 | keyreg = 'keyreg', 18 | 19 | /** 20 | * Asset configuration transaction type 21 | */ 22 | acfg = 'acfg', 23 | 24 | /** 25 | * Asset transfer transaction type 26 | */ 27 | axfer = 'axfer', 28 | 29 | /** 30 | * Asset freeze transaction type 31 | */ 32 | afrz = 'afrz', 33 | 34 | /** 35 | * Application transaction type 36 | */ 37 | appl = 'appl', 38 | } 39 | 40 | export function abiTypeIsTransaction(type: any): type is ABITransactionType { 41 | return ( 42 | type === ABITransactionType.any || 43 | type === ABITransactionType.pay || 44 | type === ABITransactionType.keyreg || 45 | type === ABITransactionType.acfg || 46 | type === ABITransactionType.axfer || 47 | type === ABITransactionType.afrz || 48 | type === ABITransactionType.appl 49 | ); 50 | } 51 | 52 | export function abiCheckTransactionType( 53 | type: ABITransactionType, 54 | txn: Transaction 55 | ): boolean { 56 | if (type === ABITransactionType.any) { 57 | return true; 58 | } 59 | 60 | return txn.type && txn.type.toString() === type.toString(); 61 | } 62 | -------------------------------------------------------------------------------- /src/nacl/naclWrappers.ts: -------------------------------------------------------------------------------- 1 | import nacl from 'tweetnacl'; 2 | import sha512 from 'js-sha512'; 3 | 4 | export function genericHash(arr: sha512.Message) { 5 | return sha512.sha512_256.array(arr); 6 | } 7 | 8 | export function randomBytes(length: number) { 9 | return nacl.randomBytes(length); 10 | } 11 | 12 | export function keyPairFromSeed(seed: Uint8Array) { 13 | return nacl.sign.keyPair.fromSeed(seed); 14 | } 15 | 16 | export function keyPair() { 17 | const seed = randomBytes(nacl.box.secretKeyLength); 18 | return keyPairFromSeed(seed); 19 | } 20 | 21 | export function isValidSignatureLength(len: number) { 22 | return len === nacl.sign.signatureLength; 23 | } 24 | 25 | export function keyPairFromSecretKey(sk: Uint8Array) { 26 | return nacl.sign.keyPair.fromSecretKey(sk); 27 | } 28 | 29 | export function sign(msg: Uint8Array, secretKey: Uint8Array) { 30 | return nacl.sign.detached(msg, secretKey); 31 | } 32 | 33 | export function bytesEqual(a: Uint8Array, b: Uint8Array) { 34 | return nacl.verify(a, b); 35 | } 36 | 37 | export function verify( 38 | message: Uint8Array, 39 | signature: Uint8Array, 40 | verifyKey: Uint8Array 41 | ) { 42 | return nacl.sign.detached.verify(message, signature, verifyKey); 43 | } 44 | 45 | // constants 46 | export const PUBLIC_KEY_LENGTH = nacl.sign.publicKeyLength; 47 | export const SECRET_KEY_LENGTH = nacl.sign.secretKeyLength; 48 | export const HASH_BYTES_LENGTH = 32; 49 | export const SEED_BTYES_LENGTH = 32; 50 | -------------------------------------------------------------------------------- /examples/asset_freeze_example.js: -------------------------------------------------------------------------------- 1 | // Example: freezing or unfreezing an account 2 | 3 | const algosdk = require('../src'); 4 | 5 | async function main() { 6 | const { 7 | sk: freezePrivateKey, 8 | addr: freezeAddress, 9 | } = algosdk.generateAccount(); 10 | 11 | const feePerByte = 10; 12 | const firstValidRound = 1000; 13 | const lastValidRound = 2000; 14 | const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; 15 | 16 | const { addr: freezeTarget } = algosdk.generateAccount(); 17 | 18 | const assetIndex = 1234; // identifying index of the asset 19 | 20 | // set suggested parameters 21 | // in most cases, we suggest fetching recommended transaction parameters 22 | // using the `algosdk.Algodv2.getTransactionParams()` method 23 | const suggestedParams = { 24 | fee: feePerByte, 25 | firstRound: firstValidRound, 26 | lastRound: lastValidRound, 27 | genesisHash, 28 | }; 29 | 30 | // Create the asset freeze transaction 31 | const transactionOptions = { 32 | from: freezeAddress, 33 | freezeTarget, 34 | assetIndex, 35 | suggestedParams, 36 | }; 37 | 38 | const txn = algosdk.makeAssetFreezeTxnWithSuggestedParamsFromObject( 39 | transactionOptions 40 | ); 41 | 42 | // sign the transaction 43 | const signedTxn = txn.signTxn(freezePrivateKey); 44 | 45 | // print transaction data 46 | const decoded = algosdk.decodeSignedTransaction(signedTxn); 47 | console.log(decoded); 48 | } 49 | 50 | main().catch(console.error); 51 | -------------------------------------------------------------------------------- /examples/rekey_example.js: -------------------------------------------------------------------------------- 1 | // Example: rekeying 2 | 3 | const algosdk = require('../src'); 4 | const utils = require('./utils'); 5 | 6 | const { ALGOD_INSTANCE, SENDER } = utils.retrieveBaseConfig(); 7 | 8 | async function main() { 9 | // initialize an algod client 10 | const client = new algosdk.Algodv2( 11 | ALGOD_INSTANCE.token, 12 | ALGOD_INSTANCE.server, 13 | ALGOD_INSTANCE.port 14 | ); 15 | 16 | // retrieve sender and generate a new rekey account 17 | const sender = algosdk.mnemonicToSecretKey(SENDER.mnemonic); 18 | const rekeyAccount = algosdk.generateAccount(); 19 | const receiver = sender; 20 | 21 | // get suggested parameters 22 | const suggestedParams = await client.getTransactionParams().do(); 23 | 24 | // To rekey an account to a new address, add the `rekey_to` argument to creation. 25 | // After sending this rekeying transaction, every transaction needs to be signed by the private key of the new address 26 | const amount = 0; 27 | const transactionOptions = { 28 | from: sender.addr, 29 | to: receiver.addr, 30 | rekeyTo: rekeyAccount.addr, 31 | amount, 32 | suggestedParams, 33 | }; 34 | 35 | const rekeyingTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject( 36 | transactionOptions 37 | ); 38 | 39 | // print transaction data 40 | console.log(rekeyingTxn); 41 | console.log( 42 | 'Rekey transaction created successfully. Unsent transaction included above.' 43 | ); 44 | } 45 | 46 | main().catch(console.error); 47 | -------------------------------------------------------------------------------- /examples/typescript_example.ts: -------------------------------------------------------------------------------- 1 | // Example: with TypeScript 2 | 3 | import algosdk from '../src'; 4 | import utils from './utils'; 5 | 6 | const { SENDER, RECEIVER } = utils.retrieveBaseConfig(); 7 | 8 | async function main() { 9 | // retrieve sender and receiver 10 | const { sk, addr } = algosdk.mnemonicToSecretKey(SENDER.mnemonic); 11 | const { addr: receiver } = algosdk.mnemonicToSecretKey(RECEIVER.mnemonic); 12 | 13 | // suggested parameters 14 | const feePerByte = 10; 15 | const firstValidRound = 1000; 16 | const lastValidRound = 2000; 17 | const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; 18 | const genesisID = 'testnet-v1.0'; 19 | 20 | const suggestedParams: algosdk.SuggestedParams = { 21 | flatFee: false, 22 | fee: feePerByte, 23 | firstRound: firstValidRound, 24 | lastRound: lastValidRound, 25 | genesisHash, 26 | genesisID, 27 | }; 28 | 29 | // construct a transaction note 30 | const note = new Uint8Array(Buffer.from('Hello World', 'utf8')); 31 | 32 | // create the transaction 33 | const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ 34 | from: addr, 35 | to: receiver, 36 | amount: 100000, 37 | note, 38 | suggestedParams, 39 | // try adding another option to the list above by using TypeScript autocomplete (ctrl + space in VSCode) 40 | }); 41 | 42 | const signedTxn = txn.signTxn(sk); 43 | console.log(algosdk.decodeSignedTransaction(signedTxn)); 44 | } 45 | 46 | main().catch(console.error); 47 | -------------------------------------------------------------------------------- /examples/asset_accept_example.js: -------------------------------------------------------------------------------- 1 | // Example: accepting assets 2 | 3 | const algosdk = require('../src'); 4 | 5 | async function main() { 6 | const account = algosdk.generateAccount(); 7 | 8 | const feePerByte = 10; 9 | const firstValidRound = 1000; 10 | const lastValidRound = 2000; 11 | const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; 12 | const receiver = account; // to start accepting assets, set receiver to sender 13 | const amount = 0; // to start accepting assets, set amount to 0 14 | 15 | const assetIndex = 1234; // identifying index of the asset 16 | 17 | // set suggested parameters 18 | // in most cases, we suggest fetching recommended transaction parameters 19 | // using the `algosdk.Algodv2.getTransactionParams()` method 20 | const suggestedParams = { 21 | fee: feePerByte, 22 | firstRound: firstValidRound, 23 | lastRound: lastValidRound, 24 | genesisHash, 25 | }; 26 | 27 | // create the asset accept transaction 28 | const transactionOptions = { 29 | from: account.addr, 30 | to: receiver.addr, 31 | assetIndex, 32 | amount, 33 | suggestedParams, 34 | }; 35 | 36 | const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject( 37 | transactionOptions 38 | ); 39 | 40 | // sign the transaction 41 | const signedTxn = txn.signTxn(account.sk); 42 | 43 | // print transaction data 44 | const decoded = algosdk.decodeSignedTransaction(signedTxn); 45 | console.log(decoded); 46 | } 47 | 48 | main().catch(console.error); 49 | -------------------------------------------------------------------------------- /src/types/blockHeader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the metadata and state of a block. 3 | * 4 | * For more information, refer to: https://github.com/algorand/go-algorand/blob/master/data/bookkeeping/block.go 5 | */ 6 | export default interface BlockHeader { 7 | /** 8 | * Transaction fees 9 | */ 10 | fees: string; 11 | 12 | /** 13 | * The number of leftover MicroAlgos after rewards distribution 14 | */ 15 | frac: number; 16 | 17 | /** 18 | * Genesis ID to which this block belongs 19 | */ 20 | gen: string; 21 | 22 | /** 23 | * Genesis hash to which this block belongs. 24 | */ 25 | gh: string; 26 | 27 | /** 28 | * The hash of the previous block 29 | */ 30 | prev: string; 31 | 32 | /** 33 | * Current protocol 34 | */ 35 | proto: string; 36 | 37 | /** 38 | * Rewards rate 39 | */ 40 | rate: number; 41 | 42 | /** 43 | * Round number 44 | */ 45 | rnd: number; 46 | 47 | /** 48 | * Rewards recalculation round 49 | */ 50 | rwcalr: number; 51 | 52 | /** 53 | * Rewards pool 54 | */ 55 | rwd: string; 56 | 57 | /** 58 | * Sortition seed 59 | */ 60 | seed: string; 61 | 62 | /** 63 | * Timestamp in seconds since epoch 64 | */ 65 | ts: number; 66 | 67 | /** 68 | * Transaction root SHA512_256 69 | */ 70 | txn: string; 71 | 72 | /** 73 | * Transaction root SHA256 74 | */ 75 | txn256: string; 76 | 77 | /** 78 | * StateProofTracking map of type to tracking data 79 | */ 80 | spt: Map; 81 | } 82 | -------------------------------------------------------------------------------- /src/abi/contract.ts: -------------------------------------------------------------------------------- 1 | import { ABIMethod, ABIMethodParams, getMethodByName } from './method'; 2 | 3 | export interface ABIContractNetworkInfo { 4 | appID: number; 5 | } 6 | 7 | export interface ABIContractNetworks { 8 | [network: string]: ABIContractNetworkInfo; 9 | } 10 | 11 | export interface ABIContractParams { 12 | name: string; 13 | desc?: string; 14 | networks?: ABIContractNetworks; 15 | methods: ABIMethodParams[]; 16 | } 17 | 18 | export class ABIContract { 19 | public readonly name: string; 20 | public readonly description?: string; 21 | public readonly networks: ABIContractNetworks; 22 | public readonly methods: ABIMethod[]; 23 | 24 | constructor(params: ABIContractParams) { 25 | if ( 26 | typeof params.name !== 'string' || 27 | !Array.isArray(params.methods) || 28 | (params.networks && typeof params.networks !== 'object') 29 | ) { 30 | throw new Error('Invalid ABIContract parameters'); 31 | } 32 | 33 | this.name = params.name; 34 | this.description = params.desc; 35 | this.networks = params.networks ? { ...params.networks } : {}; 36 | this.methods = params.methods.map((method) => new ABIMethod(method)); 37 | } 38 | 39 | toJSON(): ABIContractParams { 40 | return { 41 | name: this.name, 42 | desc: this.description, 43 | networks: this.networks, 44 | methods: this.methods.map((method) => method.toJSON()), 45 | }; 46 | } 47 | 48 | getMethodByName(name: string): ABIMethod { 49 | return getMethodByName(this.methods, name); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/4.Utils.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import * as utils from '../src/utils/utils'; 3 | import * as nacl from '../src/nacl/naclWrappers'; 4 | 5 | describe('utils', () => { 6 | describe('concatArrays', () => { 7 | it('should concat two Uint8Arrays', () => { 8 | const a = new Uint8Array([1, 2, 3]); 9 | const b = new Uint8Array([4, 5, 6]); 10 | 11 | const expected = new Uint8Array([1, 2, 3, 4, 5, 6]); 12 | const actual = utils.concatArrays(a, b); 13 | assert.deepStrictEqual(actual, expected); 14 | }); 15 | 16 | it('should concat two number arrays as a Uint8Array', () => { 17 | const a = [1, 2, 3]; 18 | const b = [4, 5, 6]; 19 | 20 | const expected = new Uint8Array([1, 2, 3, 4, 5, 6]); 21 | const actual = utils.concatArrays(a, b); 22 | assert.deepStrictEqual(actual, expected); 23 | assert(actual instanceof Uint8Array); 24 | }); 25 | 26 | it('should concat three Uint8Arrays', () => { 27 | const a = new Uint8Array([1, 2]); 28 | const b = new Uint8Array([3, 4]); 29 | const c = new Uint8Array([5, 6]); 30 | 31 | const expected = new Uint8Array([1, 2, 3, 4, 5, 6]); 32 | const actual = utils.concatArrays(a, b, c); 33 | assert.deepStrictEqual(expected, actual); 34 | }); 35 | }); 36 | }); 37 | 38 | describe('nacl wrapper', () => { 39 | it('should validate signature length', () => { 40 | assert.strictEqual(nacl.isValidSignatureLength(6), false); 41 | assert.strictEqual(nacl.isValidSignatureLength(64), true); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | mocha: true, 7 | }, 8 | extends: ['airbnb-base', 'prettier', 'plugin:import/typescript'], 9 | parser: '@typescript-eslint/parser', 10 | parserOptions: { 11 | ecmaVersion: 12, 12 | }, 13 | plugins: ['@typescript-eslint', 'eslint-plugin-tsdoc'], 14 | rules: { 15 | 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'], 16 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], 17 | 'max-classes-per-file': 'off', 18 | 'import/extensions': [ 19 | 'error', 20 | 'ignorePackages', 21 | { 22 | js: 'never', 23 | ts: 'never', 24 | }, 25 | ], 26 | 'import/prefer-default-export': 'off', 27 | 'no-continue': 'off', 28 | 'lines-between-class-members': [ 29 | 'error', 30 | 'always', 31 | { exceptAfterSingleLine: true }, 32 | ], 33 | 'no-unused-vars': 'off', 34 | '@typescript-eslint/no-unused-vars': ['error'], 35 | 'no-redeclare': 'off', 36 | '@typescript-eslint/no-redeclare': ['error'], 37 | 'no-shadow': 'off', 38 | '@typescript-eslint/no-shadow': ['error'], 39 | }, 40 | overrides: [ 41 | { 42 | files: ['**/*.ts'], 43 | rules: { 44 | 'import/no-commonjs': 'error', 45 | 'tsdoc/syntax': 'warn', 46 | }, 47 | }, 48 | ], 49 | ignorePatterns: [ 50 | 'dist/', 51 | 'docs/', 52 | 'tests/cucumber/features/', 53 | 'tests/cucumber/browser/build/', 54 | 'tests/browser/bundle.*', 55 | ], 56 | }; 57 | -------------------------------------------------------------------------------- /examples/asset_revoke_example.js: -------------------------------------------------------------------------------- 1 | // Example: revoking assets 2 | 3 | const algosdk = require('../src'); 4 | 5 | async function main() { 6 | const { 7 | sk: clawbackPrivateKey, 8 | addr: clawbackAddress, 9 | } = algosdk.generateAccount(); 10 | 11 | const feePerByte = 10; 12 | const firstValidRound = 1000; 13 | const lastValidRound = 2000; 14 | const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; 15 | const { addr: receiverAddr } = algosdk.generateAccount(); 16 | const { addr: targetAddr } = algosdk.generateAccount(); 17 | const amount = 100; 18 | 19 | const assetIndex = 1234; // identifying index of the asset 20 | 21 | // set suggested parameters 22 | // in most cases, we suggest fetching recommended transaction parameters 23 | // using the `algosdk.Algodv2.getTransactionParams()` method 24 | const suggestedParams = { 25 | fee: feePerByte, 26 | firstRound: firstValidRound, 27 | lastRound: lastValidRound, 28 | genesisHash, 29 | }; 30 | 31 | // create the asset revoke transaction 32 | const transactionOptions = { 33 | from: clawbackAddress, 34 | to: receiverAddr, 35 | revocationTarget: targetAddr, 36 | amount, 37 | assetIndex, 38 | suggestedParams, 39 | }; 40 | 41 | const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject( 42 | transactionOptions 43 | ); 44 | 45 | // sign the transaction 46 | const signedTxn = txn.signTxn(clawbackPrivateKey); 47 | 48 | // print transaction data 49 | const decoded = algosdk.decodeSignedTransaction(signedTxn); 50 | console.log(decoded); 51 | } 52 | 53 | main().catch(console.error); 54 | -------------------------------------------------------------------------------- /src/boxStorage.ts: -------------------------------------------------------------------------------- 1 | import { EncodedBoxReference } from './types'; 2 | import { BoxReference } from './types/transactions/base'; 3 | 4 | function translateBoxReference( 5 | reference: BoxReference, 6 | foreignApps: number[], 7 | appIndex: number 8 | ): EncodedBoxReference { 9 | const referenceId = reference.appIndex; 10 | const referenceName = reference.name; 11 | const isOwnReference = referenceId === 0 || referenceId === appIndex; 12 | let index = 0; 13 | 14 | if (foreignApps != null) { 15 | // Foreign apps start from index 1; index 0 is its own app ID. 16 | index = foreignApps.indexOf(referenceId) + 1; 17 | } 18 | // Check if the app referenced is itself after checking the foreign apps array. 19 | // If index is zero, then the app ID was not found in the foreign apps array 20 | // or the foreign apps array was null. 21 | if (index === 0 && !isOwnReference) { 22 | // Error if the app is trying to reference a foreign app that was not in 23 | // its own foreign apps array. 24 | throw new Error(`Box ref with appId ${referenceId} not in foreign-apps`); 25 | } 26 | return { i: index, n: referenceName }; 27 | } 28 | 29 | /** 30 | * translateBoxReferences translates an array of BoxReferences with app IDs 31 | * into an array of EncodedBoxReferences with foreign indices. 32 | */ 33 | export function translateBoxReferences( 34 | references: BoxReference[] | undefined, 35 | foreignApps: number[], 36 | appIndex: number 37 | ): EncodedBoxReference[] { 38 | if (references == null) return []; 39 | return references.map((bx) => 40 | translateBoxReference(bx, foreignApps, appIndex) 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/client/v2/algod/getApplicationBoxByName.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | import { Box } from './models/types'; 5 | 6 | /** 7 | * Given an application ID and the box name (key), return the value stored in the box. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const index = 60553466; 12 | * const boxName = Buffer.from("foo"); 13 | * const boxResponse = await algodClient.getApplicationBoxByName(index, boxName).do(); 14 | * const boxValue = boxResponse.value; 15 | * ``` 16 | * 17 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox) 18 | * @param index - The application ID to look up. 19 | * @category GET 20 | */ 21 | export default class GetApplicationBoxByName extends JSONRequest< 22 | Box, 23 | Record 24 | > { 25 | constructor( 26 | c: HTTPClient, 27 | intDecoding: IntDecoding, 28 | private index: number, 29 | name: Uint8Array 30 | ) { 31 | super(c, intDecoding); 32 | this.index = index; 33 | // Encode name in base64 format and append the encoding prefix. 34 | const encodedName = Buffer.from(name).toString('base64'); 35 | this.query.name = encodeURI(`b64:${encodedName}`); 36 | } 37 | 38 | /** 39 | * @returns `/v2/applications/${index}/box` 40 | */ 41 | path() { 42 | return `/v2/applications/${this.index}/box`; 43 | } 44 | 45 | // eslint-disable-next-line class-methods-use-this 46 | prepare(body: Record): Box { 47 | return Box.from_obj_for_encoding(body); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/asset_send_example.js: -------------------------------------------------------------------------------- 1 | // Example: sending assets 2 | 3 | const algosdk = require('../src'); 4 | 5 | async function main() { 6 | const { 7 | sk: senderPrivateKey, 8 | addr: senderAddress, 9 | } = algosdk.generateAccount(); 10 | 11 | const feePerByte = 10; 12 | const firstValidRound = 1000; 13 | const lastValidRound = 2000; 14 | const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; 15 | const { addr: closeAssetsToAddr } = algosdk.generateAccount(); 16 | const { addr: receiverAddr } = algosdk.generateAccount(); 17 | const amount = 100; // amount of assets to transfer 18 | 19 | const assetIndex = 1234; // identifying index of the asset 20 | 21 | // set suggested parameters 22 | // in most cases, we suggest fetching recommended transaction parameters 23 | // using the `algosdk.Algodv2.getTransactionParams()` method 24 | const suggestedParams = { 25 | fee: feePerByte, 26 | firstRound: firstValidRound, 27 | lastRound: lastValidRound, 28 | genesisHash, 29 | }; 30 | 31 | // create the asset transfer transaction 32 | const transactionOptions = { 33 | from: senderAddress, 34 | to: receiverAddr, 35 | closeRemainderTo: closeAssetsToAddr, 36 | amount, 37 | assetIndex, 38 | suggestedParams, 39 | }; 40 | 41 | const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject( 42 | transactionOptions 43 | ); 44 | 45 | // sign the transaction 46 | const signedTxn = txn.signTxn(senderPrivateKey); 47 | 48 | // print transaction data 49 | const decoded = algosdk.decodeSignedTransaction(signedTxn); 50 | console.log(decoded); 51 | } 52 | 53 | main().catch(console.error); 54 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupApplicationBoxByIDandName.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | import { Box } from './models/types'; 5 | 6 | export default class LookupApplicationBoxByIDandName extends JSONRequest< 7 | Box, 8 | Record 9 | > { 10 | /** 11 | * Returns information about indexed application boxes. 12 | * 13 | * #### Example 14 | * ```typescript 15 | * const boxName = Buffer.from("foo"); 16 | * const boxResponse = await indexerClient 17 | * .LookupApplicationBoxByIDandName(1234, boxName) 18 | * .do(); 19 | * const boxValue = boxResponse.value; 20 | * ``` 21 | * 22 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idbox) 23 | * @oaram index - application index. 24 | * @category GET 25 | */ 26 | constructor( 27 | c: HTTPClient, 28 | intDecoding: IntDecoding, 29 | private index: number, 30 | boxName: Uint8Array 31 | ) { 32 | super(c, intDecoding); 33 | this.index = index; 34 | // Encode query in base64 format and append the encoding prefix. 35 | const encodedName = Buffer.from(boxName).toString('base64'); 36 | this.query.name = encodeURI(`b64:${encodedName}`); 37 | } 38 | 39 | /** 40 | * @returns `/v2/applications/${index}/box` 41 | */ 42 | path() { 43 | return `/v2/applications/${this.index}/box`; 44 | } 45 | 46 | // eslint-disable-next-line class-methods-use-this 47 | prepare(body: Record): Box { 48 | return Box.from_obj_for_encoding(body); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/asset_update_example.js: -------------------------------------------------------------------------------- 1 | // Example: updating asset configuration 2 | 3 | const algosdk = require('../src'); 4 | 5 | async function main() { 6 | const { 7 | sk: managerPrivateKey, 8 | addr: managerAddress, 9 | } = algosdk.generateAccount(); 10 | const { addr: newFreezeAddr } = algosdk.generateAccount(); 11 | const { addr: newManagerAddr } = algosdk.generateAccount(); 12 | const { addr: newClawbackAddr } = algosdk.generateAccount(); 13 | const { addr: newReserveAddr } = algosdk.generateAccount(); 14 | 15 | const feePerByte = 10; 16 | const firstValidRound = 1000; 17 | const lastValidRound = 2000; 18 | const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; 19 | 20 | const assetIndex = 1234; // identifying index of the asset 21 | 22 | // set suggested parameters 23 | // in most cases, we suggest fetching recommended transaction parameters 24 | // using the `algosdk.Algodv2.getTransactionParams()` method 25 | const suggestedParams = { 26 | fee: feePerByte, 27 | firstRound: firstValidRound, 28 | lastRound: lastValidRound, 29 | genesisHash, 30 | }; 31 | 32 | // create the asset update transaction 33 | const transactionOptions = { 34 | from: managerAddress, 35 | freeze: newFreezeAddr, 36 | manager: newManagerAddr, 37 | clawback: newClawbackAddr, 38 | reserve: newReserveAddr, 39 | assetIndex, 40 | suggestedParams, 41 | }; 42 | 43 | const txn = algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject( 44 | transactionOptions 45 | ); 46 | 47 | // sign the transaction 48 | const signedTxn = txn.signTxn(managerPrivateKey); 49 | 50 | // print transaction data 51 | const decoded = algosdk.decodeSignedTransaction(signedTxn); 52 | console.log(decoded); 53 | } 54 | 55 | main().catch(console.error); 56 | -------------------------------------------------------------------------------- /src/logic/sourcemap.ts: -------------------------------------------------------------------------------- 1 | import * as vlq from 'vlq'; 2 | 3 | export class SourceMap { 4 | version: number; 5 | sources: string[]; 6 | names: string[]; 7 | mappings: string; 8 | 9 | pcToLine: { [key: number]: number }; 10 | lineToPc: { [key: number]: number[] }; 11 | 12 | constructor({ 13 | version, 14 | sources, 15 | names, 16 | mappings, 17 | }: { 18 | version: number; 19 | sources: string[]; 20 | names: string[]; 21 | mappings: string; 22 | }) { 23 | this.version = version; 24 | this.sources = sources; 25 | this.names = names; 26 | this.mappings = mappings; 27 | 28 | if (this.version !== 3) 29 | throw new Error(`Only version 3 is supported, got ${this.version}`); 30 | 31 | if (this.mappings === undefined) 32 | throw new Error( 33 | 'mapping undefined, cannot build source map without `mapping`' 34 | ); 35 | 36 | const pcList = this.mappings.split(';').map((m) => { 37 | const decoded = vlq.decode(m); 38 | if (decoded.length > 2) return decoded[2]; 39 | return undefined; 40 | }); 41 | 42 | this.pcToLine = {}; 43 | this.lineToPc = {}; 44 | 45 | let lastLine = 0; 46 | for (const [pc, lineDelta] of pcList.entries()) { 47 | // If the delta is not undefined, the lastLine should be updated with 48 | // lastLine + the delta 49 | if (lineDelta !== undefined) { 50 | lastLine += lineDelta; 51 | } 52 | 53 | if (!(lastLine in this.lineToPc)) this.lineToPc[lastLine] = []; 54 | 55 | this.lineToPc[lastLine].push(pc); 56 | this.pcToLine[pc] = lastLine; 57 | } 58 | } 59 | 60 | getLineForPc(pc: number): number | undefined { 61 | return this.pcToLine[pc]; 62 | } 63 | 64 | getPcsForLine(line: number): number[] | undefined { 65 | return this.lineToPc[line]; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupAssetByID.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupAssetByID extends JSONRequest { 6 | /** 7 | * Returns asset information of the queried asset. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const assetId = 163650; 12 | * const assetInfo = await indexerClient.lookupAssetByID(assetId).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2assetsasset-id) 16 | * @param index - The asset ID to look up. 17 | */ 18 | constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { 19 | super(c, intDecoding); 20 | this.index = index; 21 | } 22 | 23 | /** 24 | * @returns `/v2/assets/${index}` 25 | */ 26 | path() { 27 | return `/v2/assets/${this.index}`; 28 | } 29 | 30 | /** 31 | * Includes all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates 32 | * 33 | * #### Example 1 34 | * ```typescript 35 | * const assetId = 163650; 36 | * const assetInfo = await indexerClient 37 | * .lookupAssetByID(assetId) 38 | * .includeAll(false) 39 | * .do(); 40 | * ``` 41 | * 42 | * #### Example 2 43 | * ```typescript 44 | * const assetId = 163650; 45 | * const assetInfo = await indexerClient 46 | * .lookupAssetByID(assetId) 47 | * .includeAll() 48 | * .do(); 49 | * ``` 50 | * 51 | * @param value - default true when called without passing a value 52 | * @category query 53 | */ 54 | includeAll(value = true) { 55 | this.query['include-all'] = value; 56 | return this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/types/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Expands types for IntelliSense so they are more human readable 3 | * See https://stackoverflow.com/a/69288824 4 | */ 5 | export type Expand = T extends (...args: infer A) => infer R 6 | ? (...args: Expand) => Expand 7 | : T extends infer O 8 | ? { [K in keyof O]: O[K] } 9 | : never; 10 | 11 | /** 12 | * Same as TypeScript's Pick, but will distribute the Pick over unions 13 | */ 14 | export type DistributivePick = T extends unknown 15 | ? Pick 16 | : never; 17 | 18 | /** 19 | * Overwrite a type with properties from another type 20 | */ 21 | export type Overwrite> = Pick< 22 | T, 23 | Exclude 24 | > & 25 | U; 26 | 27 | /** 28 | * Same as Overwrite, but will distribute the Overwrite over unions 29 | */ 30 | export type DistributiveOverwrite = T extends unknown 31 | ? Overwrite 32 | : never; 33 | 34 | /** 35 | * Mark certain keys as prohibited 36 | */ 37 | export type NeverAllow = { 38 | // eslint-disable-next-line no-unused-vars 39 | [P in K]?: never; 40 | }; 41 | 42 | /** 43 | * Rename a specific property of a type to another name 44 | * 45 | * Usage: RenameProperty\<\{ a: string \}, 'a', 'b'\> 46 | * -\> \{ b: string \} 47 | */ 48 | export type RenameProperty = { 49 | [P in keyof T as P extends K ? R : P]: T[P]; 50 | }; 51 | 52 | /** 53 | * Rename multiple properties of one type to another name 54 | * 55 | * Usage: RenameProperties\<\{ a: string, b: number \}, \{ a: 'c', b: 'd' \}\> 56 | * -\> \{ c: string, d: number \} 57 | */ 58 | export type RenameProperties< 59 | T, 60 | R extends { 61 | [K in keyof R]: K extends keyof T ? PropertyKey : 'Error: key not in T'; 62 | } 63 | > = { [P in keyof T as P extends keyof R ? R[P] : P]: T[P] }; 64 | -------------------------------------------------------------------------------- /examples/multisig_example.js: -------------------------------------------------------------------------------- 1 | // Example: manipulating multisig transactions 2 | 3 | const algosdk = require('../src'); 4 | const utils = require('./utils'); 5 | 6 | const { ALGOD_INSTANCE, SENDER, RECEIVER } = utils.retrieveBaseConfig(); 7 | 8 | async function main() { 9 | // initialize an algod client 10 | const client = new algosdk.Algodv2( 11 | ALGOD_INSTANCE.token, 12 | ALGOD_INSTANCE.server, 13 | ALGOD_INSTANCE.port 14 | ); 15 | 16 | // retrieve a sender and receiver 17 | const signer1 = algosdk.mnemonicToSecretKey(SENDER.mnemonic); 18 | const receiver = algosdk.mnemonicToSecretKey(RECEIVER.mnemonic); 19 | 20 | // generate an additional sign 21 | const signer2 = algosdk.generateAccount(); 22 | 23 | // create a multisig account 24 | const multiSigOptions = { 25 | version: 1, 26 | threshold: 2, 27 | addrs: [signer1.addr, signer2.addr], 28 | }; 29 | const msigAddress = algosdk.multisigAddress(multiSigOptions); 30 | 31 | // get suggested parameters 32 | const suggestedParams = await client.getTransactionParams().do(); 33 | 34 | // create a transaction 35 | const amount = 100000; 36 | const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ 37 | from: msigAddress, 38 | to: receiver.addr, 39 | amount, 40 | suggestedParams, 41 | }); 42 | 43 | // sign transaction 44 | const signature1 = algosdk.signMultisigTransaction( 45 | txn, 46 | multiSigOptions, 47 | signer1.sk 48 | ); 49 | const signature2 = algosdk.signMultisigTransaction( 50 | txn, 51 | multiSigOptions, 52 | signer2.sk 53 | ); 54 | const stxn = algosdk.mergeMultisigTransactions([ 55 | signature1.blob, 56 | signature2.blob, 57 | ]); 58 | 59 | // print transaction data 60 | const decoded = algosdk.decodeSignedTransaction(stxn); 61 | console.log(decoded); 62 | } 63 | 64 | main().catch(console.error); 65 | -------------------------------------------------------------------------------- /src/client/v2/algod/sendRawTransaction.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import { concatArrays } from '../../../utils/utils'; 4 | 5 | /** 6 | * Sets the default header (if not previously set) for sending a raw 7 | * transaction. 8 | * @param headers - A headers object 9 | */ 10 | export function setSendTransactionHeaders(headers = {}) { 11 | let hdrs = headers; 12 | if (Object.keys(hdrs).every((key) => key.toLowerCase() !== 'content-type')) { 13 | hdrs = { ...headers }; 14 | hdrs['Content-Type'] = 'application/x-binary'; 15 | } 16 | return hdrs; 17 | } 18 | 19 | function isByteArray(array: any): array is Uint8Array { 20 | return array && array.byteLength !== undefined; 21 | } 22 | 23 | /** 24 | * broadcasts the passed signed txns to the network 25 | */ 26 | export default class SendRawTransaction extends JSONRequest { 27 | private txnBytesToPost: Uint8Array; 28 | 29 | constructor(c: HTTPClient, stxOrStxs: Uint8Array | Uint8Array[]) { 30 | super(c); 31 | 32 | let forPosting = stxOrStxs; 33 | if (Array.isArray(stxOrStxs)) { 34 | if (!stxOrStxs.every(isByteArray)) { 35 | throw new TypeError('Array elements must be byte arrays'); 36 | } 37 | // Flatten into a single Uint8Array 38 | forPosting = concatArrays(...stxOrStxs); 39 | } else if (!isByteArray(forPosting)) { 40 | throw new TypeError('Argument must be byte array'); 41 | } 42 | this.txnBytesToPost = forPosting; 43 | } 44 | 45 | // eslint-disable-next-line class-methods-use-this 46 | path() { 47 | return '/v2/transactions'; 48 | } 49 | 50 | async do(headers = {}) { 51 | const txHeaders = setSendTransactionHeaders(headers); 52 | const res = await this.c.post( 53 | this.path(), 54 | Buffer.from(this.txnBytesToPost), 55 | txHeaders 56 | ); 57 | return res.body; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupApplications.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupApplications extends JSONRequest { 6 | /** 7 | * Returns information about the passed application. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const appId = 60553466; 12 | * const appInfo = await indexerClient.lookupApplications(appId).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-id) 16 | * @param index - The ID of the application to look up. 17 | * @category GET 18 | */ 19 | constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { 20 | super(c, intDecoding); 21 | this.index = index; 22 | } 23 | 24 | /** 25 | * @returns `/v2/applications/${index}` 26 | */ 27 | path() { 28 | return `/v2/applications/${this.index}`; 29 | } 30 | 31 | /** 32 | * Includes all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates 33 | * 34 | * #### Example 1 35 | * ```typescript 36 | * const appId = 60553466; 37 | * const appInfo = await indexerClient 38 | * .lookupApplications(appId) 39 | * .includeAll(false) 40 | * .do(); 41 | * ``` 42 | * 43 | * #### Example 2 44 | * ```typescript 45 | * const appId = 60553466; 46 | * const appInfo = await indexerClient 47 | * .lookupApplications(appId) 48 | * .includeAll() 49 | * .do(); 50 | * ``` 51 | * 52 | * @param value - default true when called without passing a value 53 | * @category query 54 | */ 55 | includeAll(value = true) { 56 | this.query['include-all'] = value; 57 | return this; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/transaction_group_example.js: -------------------------------------------------------------------------------- 1 | // Example: working with transaction groups 2 | 3 | const algosdk = require('../src'); 4 | const utils = require('./utils'); 5 | 6 | const { ALGOD_INSTANCE, SENDER, RECEIVER } = utils.retrieveBaseConfig(); 7 | 8 | async function main() { 9 | // initialize an algod client 10 | const client = new algosdk.Algodv2( 11 | ALGOD_INSTANCE.token, 12 | ALGOD_INSTANCE.server, 13 | ALGOD_INSTANCE.port 14 | ); 15 | 16 | // retrieve a sender and receiver 17 | const sender = algosdk.mnemonicToSecretKey(SENDER.mnemonic); 18 | const receiver = algosdk.mnemonicToSecretKey(RECEIVER.mnemonic); 19 | 20 | // get suggested parameters 21 | const suggestedParams = await client.getTransactionParams().do(); 22 | 23 | // create the transactions 24 | const amount = 100000; 25 | const txn1 = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ 26 | from: sender.addr, 27 | to: receiver.addr, 28 | amount, 29 | suggestedParams, 30 | }); 31 | const txn2 = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ 32 | from: receiver.addr, 33 | to: sender.addr, 34 | amount, 35 | suggestedParams, 36 | }); 37 | 38 | // assign group id to transactions 39 | algosdk.assignGroupID([txn1, txn2]); 40 | 41 | // sign transactions 42 | const stxn1 = txn1.signTxn(sender.sk); 43 | const stxn2 = txn2.signTxn(receiver.sk); 44 | 45 | // send transactions (note that the accounts need to be funded for this to work) 46 | console.log('Sending transactions...'); 47 | const { txId } = await client.sendRawTransaction([stxn1, stxn2]).do(); 48 | 49 | // wait for confirmation – timeout after 2 rounds 50 | console.log('Awaiting confirmation (this will take several seconds)...'); 51 | const roundTimeout = 2; 52 | await utils.waitForConfirmation(client, txId, roundTimeout); 53 | console.log('Transactions successful.'); 54 | } 55 | 56 | main().catch(console.error); 57 | -------------------------------------------------------------------------------- /examples/logic_sig_example.js: -------------------------------------------------------------------------------- 1 | // Example: creating a LogicSig transaction signed by a program that never approves the transfer. 2 | 3 | const algosdk = require('../src'); 4 | const utils = require('./utils'); 5 | 6 | const { ALGOD_INSTANCE, RECEIVER } = utils.retrieveBaseConfig(); 7 | 8 | async function main() { 9 | // initialize an algod client 10 | const client = new algosdk.Algodv2( 11 | ALGOD_INSTANCE.token, 12 | ALGOD_INSTANCE.server, 13 | ALGOD_INSTANCE.port 14 | ); 15 | 16 | // compile the program 17 | // NOTE: algod must have `EnableDeveloperAPI` set to true in its configuration 18 | // in order to compile TEAL programs. Learn more about the config settings at: 19 | // > https://developer.algorand.org/docs/reference/node/config/ 20 | const program = 'int 0'; 21 | const compiledProgram = await client.compile(program).do(); 22 | const programBytes = new Uint8Array( 23 | Buffer.from(compiledProgram.result, 'base64') 24 | ); 25 | 26 | // create a logic signature 27 | const lsig = new algosdk.LogicSigAccount(programBytes); 28 | const sender = lsig.address(); 29 | 30 | // retrieve a receiver 31 | const receiver = algosdk.mnemonicToSecretKey(RECEIVER.mnemonic); 32 | 33 | // get suggested parameters 34 | const suggestedParams = await client.getTransactionParams().do(); 35 | 36 | // create a transaction 37 | const amount = 100000; 38 | const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ 39 | from: sender, 40 | to: receiver.addr, 41 | amount, 42 | suggestedParams, 43 | }); 44 | 45 | // sign transaction with logic signature 46 | 47 | const lstx = algosdk.signLogicSigTransactionObject(txn, lsig); 48 | 49 | // send transaction (it should fail because of the logic signature, which returns 0) 50 | console.log('Sending transaction...'); 51 | await client.sendRawTransaction(lstx.blob).do(); 52 | } 53 | 54 | main().catch(console.error); 55 | -------------------------------------------------------------------------------- /src/client/v2/algod/getApplicationBoxes.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | import { BoxesResponse } from './models/types'; 5 | 6 | /** 7 | * Given an application ID, return all the box names associated with the app. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const index = 60553466; 12 | * const boxesResponse = await algodClient.getApplicationBoxes(index).max(3).do(); 13 | * const boxNames = boxesResponse.boxes.map(box => box.name); 14 | * ``` 15 | * 16 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes) 17 | * @param index - The application ID to look up. 18 | * @category GET 19 | */ 20 | export default class GetApplicationBoxes extends JSONRequest< 21 | BoxesResponse, 22 | Record 23 | > { 24 | constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { 25 | super(c, intDecoding); 26 | this.index = index; 27 | this.query.max = 0; 28 | } 29 | 30 | /** 31 | * @returns `/v2/applications/${index}/boxes` 32 | */ 33 | path() { 34 | return `/v2/applications/${this.index}/boxes`; 35 | } 36 | 37 | /** 38 | * Limit results for pagination. 39 | * 40 | * #### Example 41 | * ```typescript 42 | * const maxResults = 20; 43 | * const boxesResult = await algodClient 44 | * .GetApplicationBoxes(1234) 45 | * .limit(maxResults) 46 | * .do(); 47 | * ``` 48 | * 49 | * @param limit - maximum number of results to return. 50 | * @category query 51 | */ 52 | max(max: number) { 53 | this.query.max = max; 54 | return this; 55 | } 56 | 57 | // eslint-disable-next-line class-methods-use-this 58 | prepare(body: Record): BoxesResponse { 59 | return BoxesResponse.from_obj_for_encoding(body); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/cucumber/browser/test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const sha512 = require('js-sha512'); 3 | const nacl = require('tweetnacl'); 4 | 5 | window.assert = assert; 6 | window.Buffer = Buffer; 7 | 8 | window.keyPairFromSecretKey = function keyPairFromSecretKey(sk) { 9 | return nacl.sign.keyPair.fromSecretKey(sk); 10 | }; 11 | 12 | window.keyPairFromSeed = function keyPairFromSeed(seed) { 13 | return nacl.sign.keyPair.fromSeed(seed); 14 | }; 15 | 16 | window.genericHash = function genericHash(toHash) { 17 | return sha512.sha512_256.array(toHash); 18 | }; 19 | 20 | window.loadResource = async function loadResource(resource) { 21 | const res = await fetch(`/features/resources/${resource}`); 22 | if (!res.ok) { 23 | throw new Error(`Failed to load resource (${res.status}): ${resource}`); 24 | } 25 | 26 | return Buffer.from(await res.arrayBuffer()); 27 | }; 28 | 29 | window.steps = { 30 | given: {}, 31 | when: {}, 32 | then: {}, 33 | }; 34 | 35 | window.getStep = function getStep(type, name) { 36 | if (window.steps[type] == null || window.steps[type][name] == null) { 37 | throw new Error(`Unrecognized test: ${type} ${name}`); 38 | } 39 | return window.steps[type][name]; 40 | }; 41 | 42 | window.testWorld = {}; 43 | 44 | window.makeUint8Array = function makeUint8Array(arg) { 45 | return new Uint8Array(arg); 46 | }; 47 | 48 | window.makeABIMethod = function makeABIMethod(arg) { 49 | return new window.algosdk.ABIMethod(arg); 50 | }; 51 | 52 | window.makeABIContract = function makeABIContract(arg) { 53 | return new window.algosdk.ABIContract(arg); 54 | }; 55 | 56 | window.makeArray = function makeArray(...args) { 57 | return args; 58 | }; 59 | 60 | window.makeObject = function makeObject(obj) { 61 | return { ...obj }; 62 | }; 63 | 64 | window.parseJSON = function parseJSON(json) { 65 | return JSON.parse(json); 66 | }; 67 | 68 | window.formatIncludeAll = function formatIncludeAll(includeAll) { 69 | if (!['true', 'false'].includes(includeAll)) { 70 | throw new Error(`Unknown value for includeAll: ${includeAll}`); 71 | } 72 | 73 | return includeAll === 'true'; 74 | }; 75 | -------------------------------------------------------------------------------- /src/types/transactions/builder.ts: -------------------------------------------------------------------------------- 1 | import { DistributiveOverwrite } from '../utils'; 2 | import { TransactionParams, SuggestedParams } from './base'; 3 | 4 | /** 5 | * Transaction base with suggested params as object 6 | */ 7 | type TransactionBaseWithSuggestedParams = Pick< 8 | TransactionParams, 9 | 'suggestedParams' | 'from' | 'type' | 'lease' | 'note' | 'reKeyTo' 10 | >; 11 | 12 | /** 13 | * Transaction base with suggested params included as parameters 14 | */ 15 | type TransactionBaseWithoutSuggestedParams = Pick< 16 | TransactionParams, 17 | | 'flatFee' 18 | | 'fee' 19 | | 'firstRound' 20 | | 'lastRound' 21 | | 'genesisHash' 22 | | 'from' 23 | | 'type' 24 | | 'genesisID' 25 | | 'lease' 26 | | 'note' 27 | | 'reKeyTo' 28 | >; 29 | 30 | /** 31 | * Transaction common fields. 32 | * 33 | * Base transaction type that is extended for all other transaction types. 34 | * Suggested params must be included, either as named object or included in the rest 35 | * of the parameters. 36 | */ 37 | export type TransactionBase = 38 | | TransactionBaseWithoutSuggestedParams 39 | | TransactionBaseWithSuggestedParams 40 | | (TransactionBaseWithSuggestedParams & 41 | TransactionBaseWithoutSuggestedParams); 42 | 43 | /** 44 | * Transaction builder type that accepts 2 generics: 45 | * - A: Additional parameters on top of the base transaction parameters 46 | * - O: A set of overwrites for transaction parameters 47 | */ 48 | export type ConstructTransaction = DistributiveOverwrite< 49 | TransactionBase & A, 50 | O 51 | >; 52 | 53 | /** 54 | * Only accept transaction objects that include suggestedParams as an object 55 | */ 56 | export type MustHaveSuggestedParams = Extract< 57 | T, 58 | { suggestedParams: SuggestedParams } 59 | >; 60 | 61 | /** 62 | * Only accept transaction objects that include suggestedParams inline instead of being 63 | * enclosed in its own property 64 | */ 65 | export type MustHaveSuggestedParamsInline< 66 | T extends ConstructTransaction 67 | > = Extract; 68 | 69 | export default ConstructTransaction; 70 | -------------------------------------------------------------------------------- /src/client/baseHTTPClient.ts: -------------------------------------------------------------------------------- 1 | export type Query = { 2 | format?: F; 3 | [key: string]: any; 4 | }; 5 | 6 | export interface BaseHTTPClientResponse { 7 | body: Uint8Array; 8 | status: number; // status must always be 200 except when the response is inside an error 9 | headers: Record; 10 | } 11 | 12 | /** 13 | * BaseHTTPClientError is the interface that errors thrown 14 | * by methods of BaseHTTPClient should be using 15 | */ 16 | export interface BaseHTTPClientError { 17 | response: BaseHTTPClientResponse; 18 | } 19 | 20 | /** 21 | * BaseHTTPClient is an interface abstracting the queries that can be 22 | * made to an algod/indexer endpoint. 23 | * The SDK normally uses the URLTokenBaseHTTPClient implementation. 24 | * But when used via wallets, the wallet may provide a different object 25 | * satisfying the HTTPClient interface. This is useful to allow 26 | * wallets to provide access to paid API services without leaking 27 | * the secret tokens/URLs. 28 | * 29 | * Note that post and delete also have an optional query parameter 30 | * This is to allow future extension where post and delete may have queries 31 | * Currently however HTTPClient does not make use of it 32 | * 33 | * Compared to HTTPClient, BaseHTTPClient does not deal with serialization/deserialization 34 | * Everything is already string/Uint8Array 35 | * and all the headers (including Accept/Content-Type) are assumed to be provided 36 | * 37 | * In case of non-200 status, all methods must throw an error of type 38 | * BaseHTTPClientError 39 | */ 40 | export interface BaseHTTPClient { 41 | get( 42 | relativePath: string, 43 | query?: Query, 44 | requestHeaders?: Record 45 | ): Promise; 46 | post( 47 | relativePath: string, 48 | data: Uint8Array, 49 | query?: Query, 50 | requestHeaders?: Record 51 | ): Promise; 52 | delete( 53 | relativePath: string, 54 | data: Uint8Array, 55 | query?: Query, 56 | requestHeaders?: Record 57 | ): Promise; 58 | } 59 | -------------------------------------------------------------------------------- /examples/notefield_example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example: working with NoteField 3 | * We can put things in the "note" field of a transaction; here's an example 4 | * with a simple payment transaction. Note that you can put any bytes you 5 | * want in the "note" field. 6 | */ 7 | 8 | const algosdk = require('../src'); 9 | const utils = require('./utils'); 10 | 11 | const { ALGOD_INSTANCE, SENDER, RECEIVER } = utils.retrieveBaseConfig(); 12 | 13 | async function main() { 14 | // initialize an algod client 15 | const client = new algosdk.Algodv2( 16 | ALGOD_INSTANCE.token, 17 | ALGOD_INSTANCE.server, 18 | ALGOD_INSTANCE.port 19 | ); 20 | 21 | // retrieve sender and receiver 22 | const { sk, addr } = algosdk.mnemonicToSecretKey(SENDER.mnemonic); 23 | const { addr: receiver } = algosdk.mnemonicToSecretKey(RECEIVER.mnemonic); 24 | 25 | // get suggested parameters 26 | const suggestedParams = await client.getTransactionParams().do(); 27 | 28 | // construct a transaction note 29 | const note = new Uint8Array(Buffer.from('Hello World', 'utf8')); 30 | 31 | // create the transaction 32 | const transactionOptions = { 33 | from: addr, 34 | to: receiver, 35 | amount: 100000, 36 | note, 37 | suggestedParams, 38 | }; 39 | 40 | const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject( 41 | transactionOptions 42 | ); 43 | 44 | // send transaction (note that the sender account needs to be funded for this to work) 45 | console.log('Sending transaction...'); 46 | const signedTxn = txn.signTxn(sk); 47 | const { txId } = await client.sendRawTransaction(signedTxn).do(); 48 | 49 | // wait for confirmation – timeout after 2 rounds 50 | console.log('Awaiting confirmation (this will take several seconds)...'); 51 | const roundTimeout = 2; 52 | const confirmation = await utils.waitForConfirmation( 53 | client, 54 | txId, 55 | roundTimeout 56 | ); 57 | console.log('Transaction successful.'); 58 | 59 | // log the note included on the transaction 60 | const noteArrayFromTxn = confirmation.txn.txn.note; 61 | const receivedNote = Buffer.from(noteArrayFromTxn).toString('utf8'); 62 | console.log(`Note received: ${receivedNote}`); 63 | } 64 | 65 | main().catch(console.error); 66 | -------------------------------------------------------------------------------- /src/wait.ts: -------------------------------------------------------------------------------- 1 | import Algodv2 from './client/v2/algod/algod'; 2 | 3 | /** 4 | * Wait until a transaction has been confirmed or rejected by the network, or 5 | * until 'waitRounds' number of rounds have passed. 6 | * @param client - An Algodv2 client 7 | * @param txid - The ID of the transaction to wait for. 8 | * @param waitRounds - The maximum number of rounds to wait for. 9 | * @returns A promise that, upon success, will resolve to the output of the 10 | * `pendingTransactionInformation` call for the confirmed transaction. 11 | */ 12 | export async function waitForConfirmation( 13 | client: Algodv2, 14 | txid: string, 15 | waitRounds: number 16 | ): Promise> { 17 | // Wait until the transaction is confirmed or rejected, or until 'waitRounds' 18 | // number of rounds have passed. 19 | 20 | const status = await client.status().do(); 21 | if (typeof status === 'undefined') { 22 | throw new Error('Unable to get node status'); 23 | } 24 | const startRound = status['last-round'] + 1; 25 | let currentRound = startRound; 26 | 27 | /* eslint-disable no-await-in-loop */ 28 | while (currentRound < startRound + waitRounds) { 29 | let poolError = false; 30 | try { 31 | const pendingInfo = await client.pendingTransactionInformation(txid).do(); 32 | 33 | if (pendingInfo['confirmed-round']) { 34 | // Got the completed Transaction 35 | return pendingInfo; 36 | } 37 | 38 | if (pendingInfo['pool-error']) { 39 | // If there was a pool error, then the transaction has been rejected 40 | poolError = true; 41 | throw new Error(`Transaction Rejected: ${pendingInfo['pool-error']}`); 42 | } 43 | } catch (err) { 44 | // Ignore errors from PendingTransactionInformation, since it may return 404 if the algod 45 | // instance is behind a load balancer and the request goes to a different algod than the 46 | // one we submitted the transaction to 47 | if (poolError) { 48 | // Rethrow error only if it's because the transaction was rejected 49 | throw err; 50 | } 51 | } 52 | 53 | await client.statusAfterBlock(currentRound).do(); 54 | currentRound += 1; 55 | } 56 | /* eslint-enable no-await-in-loop */ 57 | throw new Error(`Transaction not confirmed after ${waitRounds} rounds`); 58 | } 59 | -------------------------------------------------------------------------------- /src/encoding/encoding.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is a wrapper of msgpack.js. 3 | * The wrapper was written in order to ensure correct encoding of Algorand Transaction and other formats. 4 | * In particular, it matches go-algorand blockchain client, written in go (https://www.github.com/algorand/go-algorand. 5 | * Algorand's msgpack encoding follows to following rules - 6 | * 1. Every integer must be encoded to the smallest type possible (0-255-\>8bit, 256-65535-\>16bit, etx) 7 | * 2. All fields names must be sorted 8 | * 3. All empty and 0 fields should be omitted 9 | * 4. Every positive number must be encoded as uint 10 | * 5. Binary blob should be used for binary data and string for strings 11 | * */ 12 | 13 | import * as msgpack from 'algo-msgpack-with-bigint'; 14 | 15 | // Errors 16 | export const ERROR_CONTAINS_EMPTY_STRING = 17 | 'The object contains empty or 0 values. First empty or 0 value encountered during encoding: '; 18 | 19 | /** 20 | * containsEmpty returns true if any of the object's values are empty, false otherwise. 21 | * Empty arrays considered empty 22 | * @param obj - The object to check 23 | * @returns \{true, empty key\} if contains empty, \{false, undefined\} otherwise 24 | */ 25 | function containsEmpty(obj: Record) { 26 | for (const key in obj) { 27 | if (Object.prototype.hasOwnProperty.call(obj, key)) { 28 | if (!obj[key] || obj[key].length === 0) { 29 | return { containsEmpty: true, firstEmptyKey: key }; 30 | } 31 | } 32 | } 33 | return { containsEmpty: false, firstEmptyKey: undefined }; 34 | } 35 | 36 | /** 37 | * encode encodes objects using msgpack 38 | * @param obj - a dictionary to be encoded. Must not contain empty or 0 values. 39 | * @returns msgpack representation of the object 40 | * @throws Error containing ERROR_CONTAINS_EMPTY_STRING if the object contains empty or zero values 41 | */ 42 | export function encode(obj: Record) { 43 | // Check for empty values 44 | const emptyCheck = containsEmpty(obj); 45 | if (emptyCheck.containsEmpty) { 46 | throw new Error(ERROR_CONTAINS_EMPTY_STRING + emptyCheck.firstEmptyKey); 47 | } 48 | 49 | // enable the canonical option 50 | const options = { sortKeys: true }; 51 | return msgpack.encode(obj, options); 52 | } 53 | 54 | export function decode(buffer: ArrayLike) { 55 | return msgpack.decode(buffer); 56 | } 57 | -------------------------------------------------------------------------------- /src/types/transactions/index.ts: -------------------------------------------------------------------------------- 1 | import PaymentTxn from './payment'; 2 | import KeyRegistrationTxn from './keyreg'; 3 | import { 4 | AssetCreateTransaction as AssetCreateTxn, 5 | AssetConfigurationTransaction as AssetConfigTxn, 6 | AssetDestroyTransaction as AssetDestroyTxn, 7 | AssetFreezeTransaction as AssetFreezeTxn, 8 | AssetTransferTransaction as AssetTransferTxn, 9 | } from './asset'; 10 | import { 11 | ApplicationCreateTransaction as AppCreateTxn, 12 | ApplicationUpdateTransaction as AppUpdateTxn, 13 | ApplicationDeleteTransaction as AppDeleteTxn, 14 | ApplicationOptInTransaction as AppOptInTxn, 15 | ApplicationCloseOutTransaction as AppCloseOutTxn, 16 | ApplicationClearStateTransaction as AppClearStateTxn, 17 | ApplicationNoOpTransaction as AppNoOpTxn, 18 | } from './application'; 19 | import StateProofTxn from './stateproof'; 20 | 21 | // Utilities 22 | export { 23 | TransactionParams, 24 | TransactionType, 25 | SuggestedParams, 26 | BoxReference, 27 | } from './base'; 28 | export { 29 | MustHaveSuggestedParams, 30 | MustHaveSuggestedParamsInline, 31 | } from './builder'; 32 | export * from './encoded'; 33 | 34 | // Transaction types 35 | export { default as PaymentTxn } from './payment'; 36 | export { default as KeyRegistrationTxn } from './keyreg'; 37 | export { 38 | AssetCreateTransaction as AssetCreateTxn, 39 | AssetConfigurationTransaction as AssetConfigTxn, 40 | AssetDestroyTransaction as AssetDestroyTxn, 41 | AssetFreezeTransaction as AssetFreezeTxn, 42 | AssetTransferTransaction as AssetTransferTxn, 43 | } from './asset'; 44 | export { 45 | ApplicationCreateTransaction as AppCreateTxn, 46 | ApplicationUpdateTransaction as AppUpdateTxn, 47 | ApplicationDeleteTransaction as AppDeleteTxn, 48 | ApplicationOptInTransaction as AppOptInTxn, 49 | ApplicationCloseOutTransaction as AppCloseOutTxn, 50 | ApplicationClearStateTransaction as AppClearStateTxn, 51 | ApplicationNoOpTransaction as AppNoOpTxn, 52 | } from './application'; 53 | export { default as StateProofTxn } from './stateproof'; 54 | 55 | // All possible transaction types 56 | type AnyTransaction = 57 | | PaymentTxn 58 | | KeyRegistrationTxn 59 | | AssetCreateTxn 60 | | AssetConfigTxn 61 | | AssetDestroyTxn 62 | | AssetFreezeTxn 63 | | AssetTransferTxn 64 | | AppCreateTxn 65 | | AppUpdateTxn 66 | | AppDeleteTxn 67 | | AppOptInTxn 68 | | AppCloseOutTxn 69 | | AppClearStateTxn 70 | | AppNoOpTxn 71 | | StateProofTxn; 72 | export default AnyTransaction; 73 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## Where did the `dist` folder go? 4 | 5 | Starting with version 1.9.0, a minified browser bundle can be obtained from npm CDNs such as [unpkg](https://unpkg.com/) or [jsDelivr](https://www.jsdelivr.com/). The link can be found in the [README.md](README.md). 6 | 7 | In prior versions, the browser bundles were made available in the `dist` directory. In order to access those, look in the `dist` folder on the version's GitHub tag, e.g. https://github.com/algorand/js-algorand-sdk/tree/v1.8.1/dist. 8 | 9 | ## Can I host the browser bundle myself? 10 | 11 | Yes! If you would instead prefer to host the package yourself, a minified browser bundle can be found in the `dist/browser/` folder of the published npm package starting with version 1.9.0. 12 | 13 | ## It says `Error: Can't resolve....` in the sdk 14 | 15 | This kind of errors is usually seen in Webpack 5 or Vite projects. You will need to install additional polyfill packages. 16 | 17 | #### Webpack 5 projects 18 | 19 | Typically, with Webpack 5 you would see: 20 | 21 | ``` 22 | Module not found: Error: Can't resolve 'crypto' 23 | ERROR in ./node_modules/algosdk/dist/browser/algosdk.min.js 7471:55-72 24 | ``` 25 | 26 | Webpack 5 no longer auto-polyfills some of the node modules used by this sdk. You will have to polyfill them to fix any errors regarding missing modules. 27 | 28 | #### Vite projects 29 | 30 | With Vite, you would see: 31 | 32 | ``` 33 | Uncaught ReferenceError: Buffer is not defined 34 | ``` 35 | 36 | You will have to install `buffer` as dependency. 37 | 38 | In `index.html`, add the following: 39 | 40 | ```html 41 | 45 | ``` 46 | 47 | To utilize the Buffer polyfill in production builds, in `vite.config.js`, add: 48 | 49 | ```js 50 | import inject from '@rollup/plugin-inject'; 51 | 52 | export default defineConfig({ 53 | ..., 54 | build: { 55 | rollupOptions: { 56 | plugins: [inject({ Buffer: ['buffer', 'Buffer'] })], 57 | }, 58 | }, 59 | ... 60 | }); 61 | ``` 62 | 63 | ## How do I generate the SRI hash of `algosdk.min.js`? 64 | 65 | The SRI hash of `algosdk.min.js` is the base64 string after `sha384-` in the `integrity` attribute of the ` 5 | 6 | 7 | 8 | 9 |

10 | 11 | 20 | 21 | 30 | 31 | 40 |

41 |

42 | 43 | 51 |

52 |

53 | 54 | 62 | 63 | 71 |

72 |

73 | 74 | 82 |

83 |

84 | 85 | 88 | 91 | 94 | 97 | 100 |

101 |
Testnet Dispenser 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/client/v2/serviceClient.ts: -------------------------------------------------------------------------------- 1 | import HTTPClient from '../client'; 2 | import IntDecoding from '../../types/intDecoding'; 3 | import { BaseHTTPClient } from '../baseHTTPClient'; 4 | import { TokenHeader } from '../urlTokenBaseHTTPClient'; 5 | 6 | export type TokenHeaderIdentifier = 7 | | 'X-Indexer-API-Token' 8 | | 'X-KMD-API-Token' 9 | | 'X-Algo-API-Token' 10 | | string; 11 | 12 | /** 13 | * Convert a token string to a token header 14 | * @param token - The token string 15 | * @param headerIdentifier - An identifier for the token header 16 | */ 17 | function convertTokenStringToTokenHeader( 18 | token: string = '', 19 | headerIdentifier: TokenHeaderIdentifier 20 | ): TokenHeader { 21 | const tokenHeader = {}; 22 | tokenHeader[headerIdentifier] = token; 23 | return tokenHeader as TokenHeader; 24 | } 25 | 26 | function isBaseHTTPClient( 27 | tbc: string | TokenHeader | BaseHTTPClient 28 | ): tbc is BaseHTTPClient { 29 | return typeof (tbc as BaseHTTPClient).get === 'function'; 30 | } 31 | 32 | /** 33 | * Abstract service client to encapsulate shared AlgodClient and IndexerClient logic 34 | */ 35 | export default abstract class ServiceClient { 36 | /** @ignore */ 37 | c: HTTPClient; 38 | /** @ignore */ 39 | intDecoding: IntDecoding; 40 | 41 | constructor( 42 | tokenHeaderIdentifier: TokenHeaderIdentifier, 43 | tokenHeaderOrStrOrBaseClient: string | TokenHeader | BaseHTTPClient, 44 | baseServer: string, 45 | port?: string | number, 46 | defaultHeaders: Record = {} 47 | ) { 48 | if (isBaseHTTPClient(tokenHeaderOrStrOrBaseClient)) { 49 | // we are using a base client 50 | this.c = new HTTPClient(tokenHeaderOrStrOrBaseClient); 51 | } else { 52 | // Accept token header as string or object 53 | // - workaround to allow backwards compatibility for multiple headers 54 | let tokenHeader: TokenHeader; 55 | if (typeof tokenHeaderOrStrOrBaseClient === 'string') { 56 | tokenHeader = convertTokenStringToTokenHeader( 57 | tokenHeaderOrStrOrBaseClient, 58 | tokenHeaderIdentifier 59 | ); 60 | } else { 61 | tokenHeader = tokenHeaderOrStrOrBaseClient; 62 | } 63 | 64 | this.c = new HTTPClient(tokenHeader, baseServer, port, defaultHeaders); 65 | } 66 | 67 | this.intDecoding = IntDecoding.DEFAULT; 68 | } 69 | 70 | /** 71 | * Set the default int decoding method for all JSON requests this client creates. 72 | * @param method - \{"default" | "safe" | "mixed" | "bigint"\} method The method to use when parsing the 73 | * response for request. Must be one of "default", "safe", "mixed", or "bigint". See 74 | * JSONRequest.setIntDecoding for more details about what each method does. 75 | */ 76 | setIntEncoding(method: IntDecoding) { 77 | this.intDecoding = method; 78 | } 79 | 80 | /** 81 | * Get the default int decoding method for all JSON requests this client creates. 82 | */ 83 | getIntEncoding() { 84 | return this.intDecoding; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algosdk", 3 | "version": "2.0.0", 4 | "description": "The official JavaScript SDK for Algorand", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/esm/index.js", 7 | "browser": { 8 | ".": "dist/browser/algosdk.min.js", 9 | "crypto": false 10 | }, 11 | "types": "dist/types/index.d.ts", 12 | "files": [ 13 | "dist/", 14 | "src/" 15 | ], 16 | "directories": { 17 | "test": "tests" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/algorand/js-algorand-sdk.git" 22 | }, 23 | "dependencies": { 24 | "algo-msgpack-with-bigint": "^2.1.1", 25 | "buffer": "^6.0.2", 26 | "cross-fetch": "^3.1.5", 27 | "hi-base32": "^0.5.1", 28 | "js-sha256": "^0.9.0", 29 | "js-sha3": "^0.8.0", 30 | "js-sha512": "^0.8.0", 31 | "json-bigint": "^1.0.0", 32 | "tweetnacl": "^1.0.3", 33 | "vlq": "^2.0.4" 34 | }, 35 | "devDependencies": { 36 | "@types/json-bigint": "^1.0.0", 37 | "@types/mocha": "^8.2.2", 38 | "@types/url-parse": "^1.4.3", 39 | "@typescript-eslint/eslint-plugin": "^4.26.1", 40 | "@typescript-eslint/parser": "^4.26.1", 41 | "assert": "^2.0.0", 42 | "chromedriver": "^108.0.0", 43 | "concurrently": "^6.2.0", 44 | "coveralls": "^3.1.0", 45 | "cucumber": "^5.1.0", 46 | "es-abstract": "^1.18.3", 47 | "eslint": "^7.21.0", 48 | "eslint-config-airbnb-base": "^14.2.1", 49 | "eslint-config-prettier": "^8.1.0", 50 | "eslint-plugin-import": "^2.22.1", 51 | "eslint-plugin-tsdoc": "^0.2.11", 52 | "express": "^4.17.1", 53 | "geckodriver": "^3.0.1", 54 | "husky": "^4.3.8", 55 | "lint-staged": "^10.5.4", 56 | "mocha": "^9.0.0", 57 | "mocha-lcov-reporter": "^1.3.0", 58 | "mock-http-server": "^1.4.3", 59 | "prettier": "2.2.1", 60 | "selenium-webdriver": "^4.2.0", 61 | "source-map-loader": "^2.0.2", 62 | "ts-loader": "^9.3.1", 63 | "ts-node": "^10.9.1", 64 | "typedoc": "^0.23.8", 65 | "typedoc-plugin-missing-exports": "^0.23.0", 66 | "typedoc-plugin-rename-defaults": "^0.6.4", 67 | "typescript": "^4.7.4", 68 | "webpack": "^5.75.0", 69 | "webpack-cli": "^5.0.1" 70 | }, 71 | "scripts": { 72 | "test": "node -r ts-node/register tests/mocha.js", 73 | "prepare": "npm run build", 74 | "prepare-browser-tests": "npm run build && mkdir -p tests/cucumber/browser/build && cp dist/browser/algosdk.min.* tests/cucumber/browser/build/ && webpack --config tests/cucumber/browser/webpack.config.js", 75 | "build": "concurrently \"webpack --config webpack.config.js\" \"tsc -p tsconfig-esm.json\" \"tsc -p tsconfig-cjs.json\"", 76 | "docs": "typedoc --options typedoc.config.json", 77 | "docs:dev": "typedoc --options typedoc.config.json --watch --preserveWatchOutput", 78 | "lint": "eslint .", 79 | "lint:fix": "eslint --fix .", 80 | "format": "prettier --write .", 81 | "example": "ts-node" 82 | }, 83 | "author": "Algorand, llc", 84 | "license": "MIT", 85 | "engines": { 86 | "node": ">=14.0.0" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/encoding/uint64.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * encodeUint64 converts an integer to its binary representation. 3 | * @param num - The number to convert. This must be an unsigned integer less than 4 | * 2^64. 5 | * @returns An 8-byte typed array containing the big-endian encoding of the input 6 | * integer. 7 | */ 8 | export function encodeUint64(num: number | bigint) { 9 | const isInteger = typeof num === 'bigint' || Number.isInteger(num); 10 | 11 | if (!isInteger || num < 0 || num > BigInt('0xffffffffffffffff')) { 12 | throw new Error('Input is not a 64-bit unsigned integer'); 13 | } 14 | 15 | const buf = Buffer.allocUnsafe(8); 16 | 17 | buf.writeBigUInt64BE(BigInt(num)); 18 | 19 | return new Uint8Array(buf); 20 | } 21 | 22 | /** 23 | * decodeUint64 produces an integer from a binary representation. 24 | * @param data - An typed array containing the big-endian encoding of an unsigned integer 25 | * less than 2^64. This array must be at most 8 bytes long. 26 | * @param decodingMode - Configure how the integer will be 27 | * decoded. 28 | * 29 | * The options are: 30 | * * "safe": The integer will be decoded as a Number, but if it is greater than 31 | * Number.MAX_SAFE_INTEGER an error will be thrown. 32 | * * "mixed": The integer will be decoded as a Number if it is less than or equal to 33 | * Number.MAX_SAFE_INTEGER, otherwise it will be decoded as a BigInt. 34 | * * "bigint": The integer will always be decoded as a BigInt. 35 | * 36 | * Defaults to "safe" if not included. 37 | * @returns The integer that was encoded in the input data. The return type will 38 | * be determined by the parameter decodingMode. 39 | */ 40 | export function decodeUint64(data: Uint8Array, decodingMode: 'safe'): number; 41 | export function decodeUint64( 42 | data: Uint8Array, 43 | decodingMode: 'mixed' 44 | ): number | bigint; 45 | export function decodeUint64(data: Uint8Array, decodingMode: 'bigint'): bigint; 46 | export function decodeUint64(data: any, decodingMode: any = 'safe') { 47 | if ( 48 | decodingMode !== 'safe' && 49 | decodingMode !== 'mixed' && 50 | decodingMode !== 'bigint' 51 | ) { 52 | throw new Error(`Unknown decodingMode option: ${decodingMode}`); 53 | } 54 | 55 | if (data.byteLength === 0 || data.byteLength > 8) { 56 | throw new Error( 57 | `Data has unacceptable length. Expected length is between 1 and 8, got ${data.byteLength}` 58 | ); 59 | } 60 | 61 | // insert 0s at the beginning if data is smaller than 8 bytes 62 | const padding = Buffer.allocUnsafe(8 - data.byteLength); 63 | padding.fill(0); 64 | 65 | const buf = Buffer.concat([padding, Buffer.from(data)]); 66 | 67 | const num = buf.readBigUInt64BE(); 68 | const isBig = num > Number.MAX_SAFE_INTEGER; 69 | 70 | if (decodingMode === 'safe') { 71 | if (isBig) { 72 | throw new Error( 73 | `Integer exceeds maximum safe integer: ${num.toString()}. Try decoding with "mixed" or "safe" decodingMode.` 74 | ); 75 | } 76 | return Number(num); 77 | } 78 | 79 | if (decodingMode === 'mixed' && !isBig) { 80 | return Number(num); 81 | } 82 | 83 | return num; 84 | } 85 | -------------------------------------------------------------------------------- /src/group.ts: -------------------------------------------------------------------------------- 1 | import * as txnBuilder from './transaction'; 2 | import * as nacl from './nacl/naclWrappers'; 3 | import * as encoding from './encoding/encoding'; 4 | import * as address from './encoding/address'; 5 | import * as utils from './utils/utils'; 6 | 7 | const ALGORAND_MAX_TX_GROUP_SIZE = 16; 8 | 9 | interface EncodedTxGroup { 10 | txlist: Buffer[]; 11 | } 12 | 13 | /** 14 | * Aux class for group id calculation of a group of transactions 15 | */ 16 | export class TxGroup { 17 | name = 'Transaction group'; 18 | tag = Buffer.from('TG'); 19 | txGroupHashes: Buffer[]; 20 | 21 | constructor(hashes: Buffer[]) { 22 | if (hashes.length > ALGORAND_MAX_TX_GROUP_SIZE) { 23 | const errorMsg = `${hashes.length.toString()} transactions grouped together but max group size is ${ALGORAND_MAX_TX_GROUP_SIZE.toString()}`; 24 | throw Error(errorMsg); 25 | } 26 | 27 | this.txGroupHashes = hashes; 28 | } 29 | 30 | // eslint-disable-next-line camelcase 31 | get_obj_for_encoding() { 32 | const txgroup: EncodedTxGroup = { 33 | txlist: this.txGroupHashes, 34 | }; 35 | return txgroup; 36 | } 37 | 38 | // eslint-disable-next-line camelcase 39 | static from_obj_for_encoding(txgroupForEnc: EncodedTxGroup) { 40 | const txn = Object.create(this.prototype); 41 | txn.name = 'Transaction group'; 42 | txn.tag = Buffer.from('TG'); 43 | txn.txGroupHashes = []; 44 | for (const hash of txgroupForEnc.txlist) { 45 | txn.txGroupHashes.push(Buffer.from(hash)); 46 | } 47 | return txn; 48 | } 49 | 50 | toByte() { 51 | return encoding.encode(this.get_obj_for_encoding()); 52 | } 53 | } 54 | 55 | /** 56 | * computeGroupID returns group ID for a group of transactions 57 | * @param txns - array of transactions (every element is a dict or Transaction) 58 | * @returns Buffer 59 | */ 60 | export function computeGroupID(txns: txnBuilder.TransactionLike[]) { 61 | const hashes = []; 62 | for (const txn of txns) { 63 | const tx = txnBuilder.instantiateTxnIfNeeded(txn); 64 | hashes.push(tx.rawTxID()); 65 | } 66 | 67 | const txgroup = new TxGroup(hashes); 68 | 69 | const bytes = txgroup.toByte(); 70 | const toBeHashed = Buffer.from(utils.concatArrays(txgroup.tag, bytes)); 71 | const gid = nacl.genericHash(toBeHashed); 72 | return Buffer.from(gid); 73 | } 74 | 75 | /** 76 | * assignGroupID assigns group id to a given list of unsigned transactions 77 | * @param txns - array of transactions (every element is a dict or Transaction) 78 | * @param from - optional sender address specifying which transaction return 79 | * @returns possible list of matching transactions 80 | */ 81 | export function assignGroupID( 82 | txns: txnBuilder.TransactionLike[], 83 | from?: string 84 | ) { 85 | const gid = computeGroupID(txns); 86 | const result: txnBuilder.Transaction[] = []; 87 | for (const txn of txns) { 88 | const tx = txnBuilder.instantiateTxnIfNeeded(txn); 89 | if (!from || address.encodeAddress(tx.from.publicKey) === from) { 90 | tx.group = gid; 91 | result.push(tx); 92 | } 93 | } 94 | return result; 95 | } 96 | 97 | export default TxGroup; 98 | -------------------------------------------------------------------------------- /src/client/v2/indexer/searchForApplicationBoxes.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | import { BoxesResponse } from './models/types'; 5 | 6 | export default class SearchForApplicationBoxes extends JSONRequest< 7 | BoxesResponse, 8 | Record 9 | > { 10 | /** 11 | * Returns information about indexed application boxes. 12 | * 13 | * #### Example 14 | * ```typescript 15 | * const maxResults = 20; 16 | * const appID = 1234; 17 | * 18 | * const responsePage1 = await indexerClient 19 | * .searchForApplicationBoxes(appID) 20 | * .limit(maxResults) 21 | * .do(); 22 | * const boxNamesPage1 = responsePage1.boxes.map(box => box.name); 23 | * 24 | * const responsePage2 = await indexerClient 25 | * .searchForApplicationBoxes(appID) 26 | * .limit(maxResults) 27 | * .nextToken(responsePage1.nextToken) 28 | * .do(); 29 | * const boxNamesPage2 = responsePage2.boxes.map(box => box.name); 30 | * ``` 31 | * 32 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idboxes) 33 | * @oaram index - application index. 34 | * @category GET 35 | */ 36 | constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { 37 | super(c, intDecoding); 38 | this.index = index; 39 | } 40 | 41 | /** 42 | * @returns `/v2/applications/${index}/boxes` 43 | */ 44 | path() { 45 | return `/v2/applications/${this.index}/boxes`; 46 | } 47 | 48 | /** 49 | * Specify the next page of results. 50 | * 51 | * #### Example 52 | * ```typescript 53 | * const maxResults = 20; 54 | * const appID = 1234; 55 | * 56 | * const responsePage1 = await indexerClient 57 | * .searchForApplicationBoxes(appID) 58 | * .limit(maxResults) 59 | * .do(); 60 | * const boxNamesPage1 = responsePage1.boxes.map(box => box.name); 61 | * 62 | * const responsePage2 = await indexerClient 63 | * .searchForApplicationBoxes(appID) 64 | * .limit(maxResults) 65 | * .nextToken(responsePage1.nextToken) 66 | * .do(); 67 | * const boxNamesPage2 = responsePage2.boxes.map(box => box.name); 68 | * ``` 69 | * @param nextToken - provided by the previous results. 70 | * @category query 71 | */ 72 | nextToken(next: string) { 73 | this.query.next = next; 74 | return this; 75 | } 76 | 77 | /** 78 | * Limit results for pagination. 79 | * 80 | * #### Example 81 | * ```typescript 82 | * const maxResults = 20; 83 | * const boxesResponse = await indexerClient 84 | * .searchForApplicationBoxes(1234) 85 | * .limit(maxResults) 86 | * .do(); 87 | * ``` 88 | * 89 | * @param limit - maximum number of results to return. 90 | * @category query 91 | */ 92 | limit(limit: number) { 93 | this.query.limit = limit; 94 | return this; 95 | } 96 | 97 | // eslint-disable-next-line class-methods-use-this 98 | prepare(body: Record): BoxesResponse { 99 | return BoxesResponse.from_obj_for_encoding(body); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/types/transactions/application.ts: -------------------------------------------------------------------------------- 1 | import { TransactionType, TransactionParams } from './base'; 2 | import { ConstructTransaction } from './builder'; 3 | 4 | // ----------------------------------- 5 | // > Application Create Transaction 6 | // ----------------------------------- 7 | 8 | type SpecificParametersForCreate = Pick< 9 | TransactionParams, 10 | | 'appIndex' 11 | | 'appOnComplete' 12 | | 'appApprovalProgram' 13 | | 'appClearProgram' 14 | | 'appLocalInts' 15 | | 'appLocalByteSlices' 16 | | 'appGlobalInts' 17 | | 'appGlobalByteSlices' 18 | | 'appArgs' 19 | | 'appAccounts' 20 | | 'appForeignApps' 21 | | 'appForeignAssets' 22 | | 'boxes' 23 | | 'extraPages' 24 | >; 25 | 26 | interface OverwritesForCreate { 27 | type?: TransactionType.appl; 28 | } 29 | 30 | export type ApplicationCreateTransaction = ConstructTransaction< 31 | SpecificParametersForCreate, 32 | OverwritesForCreate 33 | >; 34 | 35 | // ----------------------------------- 36 | // > Application Update Transaction 37 | // ----------------------------------- 38 | 39 | type SpecificParametersForUpdate = Pick< 40 | TransactionParams, 41 | | 'appIndex' 42 | | 'appOnComplete' 43 | | 'appApprovalProgram' 44 | | 'appClearProgram' 45 | | 'appArgs' 46 | | 'appAccounts' 47 | | 'appForeignApps' 48 | | 'appForeignAssets' 49 | | 'boxes' 50 | >; 51 | 52 | interface OverwritesForUpdate { 53 | type?: TransactionType.appl; 54 | } 55 | 56 | export type ApplicationUpdateTransaction = ConstructTransaction< 57 | SpecificParametersForUpdate, 58 | OverwritesForUpdate 59 | >; 60 | 61 | // ----------------------------------- 62 | // > Application Delete Transaction 63 | // ----------------------------------- 64 | 65 | type SpecificParametersForDelete = Pick< 66 | TransactionParams, 67 | | 'appIndex' 68 | | 'appOnComplete' 69 | | 'appArgs' 70 | | 'appAccounts' 71 | | 'appForeignApps' 72 | | 'appForeignAssets' 73 | | 'boxes' 74 | >; 75 | 76 | interface OverwritesForDelete { 77 | type?: TransactionType.appl; 78 | } 79 | 80 | export type ApplicationDeleteTransaction = ConstructTransaction< 81 | SpecificParametersForDelete, 82 | OverwritesForDelete 83 | >; 84 | 85 | // ----------------------------------- 86 | // > Application Opt-In Transaction 87 | // ----------------------------------- 88 | 89 | // Same structure as the application delete transaction 90 | export type ApplicationOptInTransaction = ApplicationDeleteTransaction; 91 | 92 | // ----------------------------------- 93 | // > Application Close Out Transaction 94 | // ----------------------------------- 95 | 96 | // Same structure as the application delete transaction 97 | export type ApplicationCloseOutTransaction = ApplicationDeleteTransaction; 98 | 99 | // -------------------------------------- 100 | // > Application Clear State Transaction 101 | // -------------------------------------- 102 | 103 | // Same structure as the application delete transaction 104 | export type ApplicationClearStateTransaction = ApplicationDeleteTransaction; 105 | 106 | // -------------------------------------- 107 | // > Application Call (NoOp) Transaction 108 | // -------------------------------------- 109 | 110 | // Same structure as the application delete transaction 111 | export type ApplicationNoOpTransaction = ApplicationDeleteTransaction; 112 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupAccountByID.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupAccountByID extends JSONRequest { 6 | /** 7 | * Returns information about the given account. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 12 | * const accountInfo = await indexerClient.lookupAccountByID(address).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2accountsaccount-id) 16 | * @param account - The address of the account to look up. 17 | * @category GET 18 | */ 19 | constructor( 20 | c: HTTPClient, 21 | intDecoding: IntDecoding, 22 | private account: string 23 | ) { 24 | super(c, intDecoding); 25 | this.account = account; 26 | } 27 | 28 | /** 29 | * @returns `/v2/accounts/${account}` 30 | */ 31 | path() { 32 | return `/v2/accounts/${this.account}`; 33 | } 34 | 35 | /** 36 | * Specify round to filter with. 37 | * 38 | * #### Example 39 | * ```typescript 40 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 41 | * const targetBlock = 18309917; 42 | * const accountInfo = await indexerClient 43 | * .lookupAccountByID(address) 44 | * .round(targetBlock) 45 | * .do(); 46 | * ``` 47 | * @param round 48 | */ 49 | round(round: number) { 50 | this.query.round = round; 51 | return this; 52 | } 53 | 54 | /** 55 | * Include all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates. 56 | * 57 | * #### Example 1 58 | * ```typescript 59 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 60 | * const accountInfo = await indexerClient 61 | * .lookupAccountByID(address) 62 | * .includeAll(false) 63 | * .do(); 64 | * ``` 65 | * 66 | * #### Example 2 67 | * ```typescript 68 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 69 | * const accountInfo = await indexerClient 70 | * .lookupAccountByID(address) 71 | * .includeAll() 72 | * .do(); 73 | * ``` 74 | * @param value 75 | */ 76 | includeAll(value = true) { 77 | this.query['include-all'] = value; 78 | return this; 79 | } 80 | 81 | /** 82 | * Exclude additional items such as asset holdings, application local data stored for this account, asset parameters created by this account, and application parameters created by this account. 83 | * 84 | * #### Example 1 85 | * ```typescript 86 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 87 | * const accountInfo = await indexerClient 88 | * .lookupAccountByID(address) 89 | * .exclude("all") 90 | * .do(); 91 | * ``` 92 | * 93 | * #### Example 2 94 | * ```typescript 95 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 96 | * const accountInfo = await indexerClient 97 | * .lookupAccountByID(address) 98 | * .exclude("assets,created-assets") 99 | * .do(); 100 | * ``` 101 | * @remarks By default, it behaves as exclude=none 102 | * @param exclude - Array of `all`, `assets`, `created-assets`, `apps-local-state`, `created-apps`, `none` 103 | * @category query 104 | */ 105 | exclude(exclude: string) { 106 | this.query.exclude = exclude; 107 | return this; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/client/v2/indexer/searchForApplications.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | 3 | /** 4 | * Returns information about indexed applications. 5 | * 6 | * #### Example 7 | * ```typescript 8 | * const apps = await indexerClient.searchForApplications().do(); 9 | * ``` 10 | * 11 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applications) 12 | * @category GET 13 | */ 14 | export default class SearchForApplications extends JSONRequest { 15 | /** 16 | * @returns `/v2/applications` 17 | */ 18 | // eslint-disable-next-line class-methods-use-this 19 | path() { 20 | return '/v2/applications'; 21 | } 22 | 23 | /** 24 | * Application ID for filter, as int 25 | * 26 | * #### Example 27 | * ```typescript 28 | * const appId = 60553466; 29 | * const apps = await indexerClient 30 | * .searchForApplications() 31 | * .index(appId) 32 | * .do(); 33 | * ``` 34 | * @remarks Alternatively, use `indexerClient.lookupApplications(appId).do()` 35 | * @param index 36 | * @category query 37 | */ 38 | index(index: number) { 39 | this.query['application-id'] = index; 40 | return this; 41 | } 42 | 43 | /** 44 | * Creator for filter, as string 45 | * 46 | * #### Example 47 | * ```typescript 48 | * const creator = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 49 | * const apps = await indexerClient 50 | * .searchForApplications() 51 | * .creator(creator) 52 | * .do(); 53 | * ``` 54 | * @param creator 55 | * @category query 56 | */ 57 | creator(creator: string) { 58 | this.query.creator = creator; 59 | return this; 60 | } 61 | 62 | /** 63 | * Specify the next page of results. 64 | * 65 | * #### Example 66 | * ```typescript 67 | * const maxResults = 20; 68 | * 69 | * const appsPage1 = await indexerClient 70 | * .searchForApplications() 71 | * .limit(maxResults) 72 | * .do(); 73 | * 74 | * const appsPage2 = await indexerClient 75 | * .searchForApplications() 76 | * .limit(maxResults) 77 | * .nextToken(appsPage1["next-token"]) 78 | * .do(); 79 | * ``` 80 | * @param nextToken - provided by the previous results. 81 | * @category query 82 | */ 83 | nextToken(next: string) { 84 | this.query.next = next; 85 | return this; 86 | } 87 | 88 | /** 89 | * Limit results for pagination. 90 | * 91 | * #### Example 92 | * ```typescript 93 | * const maxResults = 20; 94 | * const apps = await indexerClient 95 | * .searchForApplications() 96 | * .limit(maxResults) 97 | * .do(); 98 | * ``` 99 | * 100 | * @param limit - maximum number of results to return. 101 | * @category query 102 | */ 103 | limit(limit: number) { 104 | this.query.limit = limit; 105 | return this; 106 | } 107 | 108 | /** 109 | * Includes all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates 110 | * 111 | * #### Example 1 112 | * ```typescript 113 | * const apps = await indexerClient 114 | * .searchForApplications() 115 | * .includeAll(false) 116 | * .do(); 117 | * ``` 118 | * 119 | * #### Example 2 120 | * ```typescript 121 | * const apps = await indexerClient 122 | * .searchForApplications() 123 | * .includeAll() 124 | * .do(); 125 | * ``` 126 | * 127 | * @param value - default true when called without passing a value 128 | * @category query 129 | */ 130 | includeAll(value = true) { 131 | this.query['include-all'] = value; 132 | return this; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/client/v2/jsonrequest.ts: -------------------------------------------------------------------------------- 1 | import HTTPClient from '../client'; 2 | import IntDecoding from '../../types/intDecoding'; 3 | 4 | /** 5 | * Base abstract class for JSON requests. 6 | * 7 | * Data: The type returned from the `do()` method 8 | * 9 | * Body: The structure of the response's body 10 | */ 11 | export default abstract class JSONRequest< 12 | Data = Record, 13 | Body = Data | Uint8Array 14 | > { 15 | c: HTTPClient; 16 | query: Record; 17 | intDecoding: IntDecoding; 18 | 19 | /** 20 | * @param client - HTTPClient object. 21 | * @param intDecoding - The method to use 22 | * for decoding integers from this request's response. See the setIntDecoding method for more 23 | * details. 24 | */ 25 | constructor(client: HTTPClient, intDecoding?: IntDecoding) { 26 | this.c = client; 27 | this.query = {}; 28 | this.intDecoding = intDecoding || IntDecoding.DEFAULT; 29 | } 30 | 31 | /** 32 | * @returns The path of this request. 33 | * @category JSONRequest 34 | */ 35 | abstract path(): string; 36 | 37 | /** 38 | * Prepare a JSON response before returning it. 39 | * 40 | * Use this method to change and restructure response 41 | * data as needed after receiving it from the `do()` method. 42 | * @param body - Response body received 43 | * @category JSONRequest 44 | */ 45 | // eslint-disable-next-line class-methods-use-this 46 | prepare(body: Body): Data { 47 | return (body as unknown) as Data; 48 | } 49 | 50 | /** 51 | * Execute the request. 52 | * @param headers - Additional headers to send in the request. Optional. 53 | * @returns A promise which resolves to the parsed response data. 54 | * @category JSONRequest 55 | */ 56 | async do(headers: Record = {}): Promise { 57 | const jsonOptions: Record = {}; 58 | if (this.intDecoding !== 'default') { 59 | jsonOptions.intDecoding = this.intDecoding; 60 | } 61 | const res = await this.c.get(this.path(), this.query, headers, jsonOptions); 62 | return this.prepare(res.body); 63 | } 64 | 65 | /** 66 | * Execute the request, but do not process the response data in any way. 67 | * @param headers - Additional headers to send in the request. Optional. 68 | * @returns A promise which resolves to the raw response data, exactly as returned by the server. 69 | * @category JSONRequest 70 | */ 71 | async doRaw(headers: Record = {}): Promise { 72 | const res = await this.c.get(this.path(), this.query, headers, {}, false); 73 | return res.body; 74 | } 75 | 76 | /** 77 | * Configure how integers in this request's JSON response will be decoded. 78 | * 79 | * The options are: 80 | * * "default": Integers will be decoded according to JSON.parse, meaning they will all be 81 | * Numbers and any values greater than Number.MAX_SAFE_INTEGER will lose precision. 82 | * * "safe": All integers will be decoded as Numbers, but if any values are greater than 83 | * Number.MAX_SAFE_INTEGER an error will be thrown. 84 | * * "mixed": Integers will be decoded as Numbers if they are less than or equal to 85 | * Number.MAX_SAFE_INTEGER, otherwise they will be decoded as BigInts. 86 | * * "bigint": All integers will be decoded as BigInts. 87 | * 88 | * @param method - The method to use when parsing the 89 | * response for this request. Must be one of "default", "safe", "mixed", or "bigint". 90 | * @category JSONRequest 91 | */ 92 | setIntDecoding(method: IntDecoding) { 93 | if ( 94 | method !== 'default' && 95 | method !== 'safe' && 96 | method !== 'mixed' && 97 | method !== 'bigint' 98 | ) 99 | throw new Error(`Invalid method for int decoding: ${method}`); 100 | this.intDecoding = method; 101 | return this; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/signer.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from './transaction'; 2 | import Account from './types/account'; 3 | import { LogicSigAccount, signLogicSigTransactionObject } from './logicsig'; 4 | import { MultisigMetadata } from './types/multisig'; 5 | import { signMultisigTransaction, mergeMultisigTransactions } from './multisig'; 6 | 7 | /** 8 | * This type represents a function which can sign transactions from an atomic transaction group. 9 | * @param txnGroup - The atomic group containing transactions to be signed 10 | * @param indexesToSign - An array of indexes in the atomic transaction group that should be signed 11 | * @returns A promise which resolves an array of encoded signed transactions. The length of the 12 | * array will be the same as the length of indexesToSign, and each index i in the array 13 | * corresponds to the signed transaction from txnGroup[indexesToSign[i]] 14 | */ 15 | export type TransactionSigner = ( 16 | txnGroup: Transaction[], 17 | indexesToSign: number[] 18 | ) => Promise; 19 | 20 | /** 21 | * Create a TransactionSigner that can sign transactions for the provided basic Account. 22 | */ 23 | export function makeBasicAccountTransactionSigner( 24 | account: Account 25 | ): TransactionSigner { 26 | return (txnGroup: Transaction[], indexesToSign: number[]) => { 27 | const signed: Uint8Array[] = []; 28 | 29 | for (const index of indexesToSign) { 30 | signed.push(txnGroup[index].signTxn(account.sk)); 31 | } 32 | 33 | return Promise.resolve(signed); 34 | }; 35 | } 36 | 37 | /** 38 | * Create a TransactionSigner that can sign transactions for the provided LogicSigAccount. 39 | */ 40 | export function makeLogicSigAccountTransactionSigner( 41 | account: LogicSigAccount 42 | ): TransactionSigner { 43 | return (txnGroup: Transaction[], indexesToSign: number[]) => { 44 | const signed: Uint8Array[] = []; 45 | 46 | for (const index of indexesToSign) { 47 | const { blob } = signLogicSigTransactionObject(txnGroup[index], account); 48 | signed.push(blob); 49 | } 50 | 51 | return Promise.resolve(signed); 52 | }; 53 | } 54 | 55 | /** 56 | * Create a TransactionSigner that can sign transactions for the provided Multisig account. 57 | * @param msig - The Multisig account metadata 58 | * @param sks - An array of private keys belonging to the msig which should sign the transactions. 59 | */ 60 | export function makeMultiSigAccountTransactionSigner( 61 | msig: MultisigMetadata, 62 | sks: Uint8Array[] 63 | ): TransactionSigner { 64 | return (txnGroup: Transaction[], indexesToSign: number[]) => { 65 | const signed: Uint8Array[] = []; 66 | 67 | for (const index of indexesToSign) { 68 | const txn = txnGroup[index]; 69 | const partialSigs: Uint8Array[] = []; 70 | 71 | for (const sk of sks) { 72 | const { blob } = signMultisigTransaction(txn, msig, sk); 73 | partialSigs.push(blob); 74 | } 75 | 76 | signed.push(mergeMultisigTransactions(partialSigs)); 77 | } 78 | 79 | return Promise.resolve(signed); 80 | }; 81 | } 82 | 83 | /** Represents an unsigned transactions and a signer that can authorize that transaction. */ 84 | export interface TransactionWithSigner { 85 | /** An unsigned transaction */ 86 | txn: Transaction; 87 | /** A transaction signer that can authorize txn */ 88 | signer: TransactionSigner; 89 | } 90 | 91 | /** 92 | * Check if a value conforms to the TransactionWithSigner structure. 93 | * @param value - The value to check. 94 | * @returns True if an only if the value has the structure of a TransactionWithSigner. 95 | */ 96 | export function isTransactionWithSigner( 97 | value: any 98 | ): value is TransactionWithSigner { 99 | return ( 100 | typeof value === 'object' && 101 | Object.keys(value).length === 2 && 102 | typeof value.txn === 'object' && 103 | typeof value.signer === 'function' 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /tests/mocha.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const Mocha = require('mocha'); 3 | const webpack = require('webpack'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | 7 | const webpackConfig = require('../webpack.config'); 8 | 9 | const browser = process.env.TEST_BROWSER; 10 | 11 | async function testRunner() { 12 | console.log('TEST_BROWSER is', browser); 13 | 14 | const testFiles = fs 15 | .readdirSync(__dirname) 16 | .filter( 17 | (file) => 18 | file !== 'mocha.js' && (file.endsWith('.js') || file.endsWith('.ts')) 19 | ) 20 | .map((file) => path.join(__dirname, file)); 21 | 22 | if (browser) { 23 | const browserEntry = path.join(__dirname, 'browser', 'index.html'); 24 | const bundleLocation = path.join(__dirname, 'browser', 'bundle.js'); 25 | 26 | await new Promise((resolve, reject) => { 27 | // Change entry and output for webpack config 28 | const webpackTestConfig = Object.assign(webpackConfig); 29 | 30 | webpackTestConfig.entry = testFiles; 31 | webpackTestConfig.output = { 32 | filename: path.basename(bundleLocation), 33 | path: path.dirname(bundleLocation), 34 | }; 35 | 36 | webpack(webpackTestConfig, (err, stats) => { 37 | if (err || stats.hasErrors()) { 38 | return reject(err || stats.toJson()); 39 | } 40 | return resolve(); 41 | }); 42 | }); 43 | 44 | console.log('Testing in browser'); 45 | 46 | /* eslint-disable global-require */ 47 | if (browser === 'chrome') { 48 | require('chromedriver'); 49 | } else if (browser === 'firefox') { 50 | require('geckodriver'); 51 | } 52 | /* eslint-disable global-require */ 53 | 54 | const webdriver = require('selenium-webdriver'); 55 | const chrome = require('selenium-webdriver/chrome'); 56 | const firefox = require('selenium-webdriver/firefox'); 57 | 58 | let chromeOptions = new chrome.Options(); 59 | let firefoxOptions = new firefox.Options(); 60 | 61 | if (process.env.CI) { 62 | chromeOptions = chromeOptions.addArguments( 63 | 'no-sandbox', 64 | 'headless', 65 | 'disable-gpu' 66 | ); 67 | firefoxOptions = firefoxOptions.headless(); 68 | } 69 | 70 | const driver = await new webdriver.Builder() 71 | .setChromeOptions(chromeOptions) 72 | .setFirefoxOptions(firefoxOptions) 73 | .forBrowser(browser) 74 | .build(); 75 | 76 | await driver.get(`file://${browserEntry}`); 77 | 78 | const title = await driver.getTitle(); 79 | 80 | if (title !== 'Algosdk Mocha Browser Testing') { 81 | throw new Error(`Incorrect title: ${title}`); 82 | } 83 | 84 | const { passed, failures } = await driver.executeAsyncScript( 85 | async (done) => { 86 | const failuresSeen = []; 87 | let testsPassed = 0; 88 | 89 | const runner = mocha.run(() => { 90 | done({ 91 | passed: testsPassed, 92 | failures: failuresSeen, 93 | }); 94 | }); 95 | 96 | runner.on('pass', () => { 97 | testsPassed += 1; 98 | }); 99 | 100 | runner.on('fail', (test, err) => { 101 | failuresSeen.push({ 102 | test: test.fullTitle(), 103 | error: `${err.toString()}\n${err.stack}`, 104 | }); 105 | }); 106 | } 107 | ); 108 | 109 | console.log( 110 | `Failed ${failures.length} of ${failures.length + passed} tests` 111 | ); 112 | 113 | for (const { test, error } of failures) { 114 | console.log(`Test: ${test}\n\t${error}\n`); 115 | } 116 | 117 | await driver.quit(); 118 | 119 | process.exitCode = failures.length > 0 ? 1 : 0; 120 | } else { 121 | console.log('Testing in Node'); 122 | 123 | const mocha = new Mocha(); 124 | testFiles.forEach((file) => mocha.addFile(file)); 125 | 126 | mocha.run((failures) => { 127 | process.exitCode = failures ? 1 : 0; 128 | }); 129 | } 130 | } 131 | 132 | testRunner().catch((err) => { 133 | console.error(err); 134 | process.exitCode = 1; 135 | }); 136 | -------------------------------------------------------------------------------- /examples/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensure that all of the required environment variables are set, throws an error otherwise 3 | * @param list 4 | */ 5 | function ensureEnvVariablesSet(list) { 6 | list.forEach((envVarName) => { 7 | // Throw an error if the variable is not defined 8 | if (typeof process.env[envVarName] !== 'string') { 9 | throw new Error(`"${envVarName}" environment variable not set.`); 10 | } 11 | }); 12 | } 13 | 14 | /** 15 | * Read the base configuration from environment variables. 16 | * Returns: 17 | * - Algod Instance 18 | * - Sender Account 19 | * - Receiver Account 20 | */ 21 | function retrieveBaseConfig() { 22 | // check that environment variables are set 23 | ensureEnvVariablesSet([ 24 | 'ALGOD_TOKEN', 25 | 'ALGOD_SERVER', 26 | 'SENDER_MNEMONIC', 27 | 'RECEIVER_MNEMONIC', 28 | ]); 29 | 30 | // structure into objects 31 | const ALGOD_INSTANCE = { 32 | token: process.env.ALGOD_TOKEN, 33 | server: process.env.ALGOD_SERVER, 34 | port: process.env.ALGOD_PORT && parseInt(process.env.ALGOD_PORT, 10), 35 | }; 36 | 37 | const SENDER = { 38 | mnemonic: process.env.SENDER_MNEMONIC, 39 | }; 40 | 41 | const RECEIVER = { 42 | mnemonic: process.env.RECEIVER_MNEMONIC, 43 | }; 44 | 45 | // test for invalid configuration 46 | if ( 47 | !( 48 | typeof ALGOD_INSTANCE.token === 'string' && 49 | typeof ALGOD_INSTANCE.server === 'string' && 50 | !Number.isNaN(ALGOD_INSTANCE.port) && 51 | typeof SENDER.mnemonic === 'string' && 52 | typeof RECEIVER.mnemonic === 'string' 53 | ) 54 | ) { 55 | throw new Error( 56 | 'Invalid configuration. Perhaps you forgot to source the environment file?' 57 | ); 58 | } 59 | 60 | return { ALGOD_INSTANCE, SENDER, RECEIVER }; 61 | } 62 | 63 | /** 64 | * utility function to wait on a transaction to be confirmed 65 | * the timeout parameter indicates how many rounds do you wish to check pending transactions for 66 | */ 67 | async function waitForConfirmation(algodclient, txId, timeout) { 68 | // Wait until the transaction is confirmed or rejected, or until 'timeout' 69 | // number of rounds have passed. 70 | // Args: 71 | // txId(str): the transaction to wait for 72 | // timeout(int): maximum number of rounds to wait 73 | // Returns: 74 | // pending transaction information, or throws an error if the transaction 75 | // is not confirmed or rejected in the next timeout rounds 76 | if (algodclient == null || txId == null || timeout < 0) { 77 | throw new Error('Bad arguments.'); 78 | } 79 | const status = await algodclient.status().do(); 80 | if (typeof status === 'undefined') 81 | throw new Error('Unable to get node status'); 82 | const startround = status['last-round'] + 1; 83 | let currentround = startround; 84 | 85 | /* eslint-disable no-await-in-loop */ 86 | while (currentround < startround + timeout) { 87 | const pendingInfo = await algodclient 88 | .pendingTransactionInformation(txId) 89 | .do(); 90 | if (pendingInfo !== undefined) { 91 | if ( 92 | pendingInfo['confirmed-round'] !== null && 93 | pendingInfo['confirmed-round'] > 0 94 | ) { 95 | // Got the completed Transaction 96 | return pendingInfo; 97 | } 98 | 99 | if ( 100 | pendingInfo['pool-error'] != null && 101 | pendingInfo['pool-error'].length > 0 102 | ) { 103 | // If there was a pool error, then the transaction has been rejected! 104 | throw new Error( 105 | `Transaction Rejected pool error${pendingInfo['pool-error']}` 106 | ); 107 | } 108 | } 109 | await algodclient.statusAfterBlock(currentround).do(); 110 | currentround += 1; 111 | } 112 | /* eslint-enable no-await-in-loop */ 113 | throw new Error(`Transaction not confirmed after ${timeout} rounds!`); 114 | } 115 | 116 | // Formatting codes to adjust font qualities 117 | const fmt = { 118 | bold: '\x1b[1m', 119 | dim: '\x1b[2m', 120 | reset: '\x1b[0m', 121 | }; 122 | 123 | module.exports = { 124 | ensureEnvVariablesSet, 125 | retrieveBaseConfig, 126 | waitForConfirmation, 127 | fmt, 128 | }; 129 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupAccountAssets.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupAccountAssets extends JSONRequest { 6 | /** 7 | * Returns asset about the given account. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 12 | * const accountAssets = await indexerClient.lookupAccountAssets(address).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2accountsaccount-idassets) 16 | * @param account - The address of the account to look up. 17 | * @category GET 18 | */ 19 | constructor( 20 | c: HTTPClient, 21 | intDecoding: IntDecoding, 22 | private account: string 23 | ) { 24 | super(c, intDecoding); 25 | this.account = account; 26 | } 27 | 28 | /** 29 | * @returns `/v2/accounts/${account}/assets` 30 | */ 31 | path() { 32 | return `/v2/accounts/${this.account}/assets`; 33 | } 34 | 35 | /** 36 | * Add a limit for filter. 37 | * 38 | * #### Example 39 | * ```typescript 40 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 41 | * const maxResults = 20; 42 | * const accountAssets = await indexerClient 43 | * .lookupAccountAssets(address) 44 | * .limit(maxResults) 45 | * .do(); 46 | * ``` 47 | * 48 | * @param limit - maximum number of results to return. 49 | * @category query 50 | */ 51 | limit(limit: number) { 52 | this.query.limit = limit; 53 | return this; 54 | } 55 | 56 | /** 57 | * Specify round to filter with. 58 | * 59 | * #### Example 60 | * ```typescript 61 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 62 | * const targetBlock = 18309917; 63 | * const accountAssets = await indexerClient 64 | * .lookupAccountAssets(address) 65 | * .round(targetBlock) 66 | * .do(); 67 | * ``` 68 | * @param round 69 | * @category query 70 | */ 71 | round(round: number) { 72 | this.query.round = round; 73 | return this; 74 | } 75 | 76 | /** 77 | * Specify the next page of results. 78 | * 79 | * #### Example 80 | * ```typescript 81 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 82 | * const maxResults = 20; 83 | * 84 | * const accountAssetsPage1 = await indexerClient 85 | * .lookupAccountAssets(address) 86 | * .limit(maxResults) 87 | * .do(); 88 | * 89 | * const accountAssetsPage2 = await indexerClient 90 | * .lookupAccountAssets(address) 91 | * .limit(maxResults) 92 | * .next(accountAssetsPage1["next-token"]) 93 | * .do(); 94 | * ``` 95 | * @param nextToken - provided by the previous results. 96 | * @category query 97 | */ 98 | nextToken(nextToken: string) { 99 | this.query.next = nextToken; 100 | return this; 101 | } 102 | 103 | /** 104 | * Include all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates 105 | * 106 | * #### Example 107 | * ```typescript 108 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 109 | * const accountAssets = await indexerClient 110 | * .lookupAccountAssets(address) 111 | * .includeAll(false) 112 | * .do(); 113 | * ``` 114 | * @param value 115 | * @category query 116 | */ 117 | includeAll(value = true) { 118 | this.query['include-all'] = value; 119 | return this; 120 | } 121 | 122 | /** 123 | * Specify an assetID to search for. 124 | * 125 | * #### Example 126 | * ```typescript 127 | * const assetId = 163650; 128 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 129 | * const assetAssets = await indexerClient 130 | * .lookupAccountAssets(address) 131 | * .assetId(assetId) 132 | * .do(); 133 | * ``` 134 | * @param index - the assetID 135 | * @category query 136 | */ 137 | assetId(index: number) { 138 | this.query['asset-id'] = index; 139 | return this; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /test-harness.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # test-harness.sh setup/start cucumber test environment. 5 | # 6 | # Configuration is managed with environment variables, the ones you 7 | # are most likely to reconfigured are stored in '.test-env'. 8 | # 9 | # Variables: 10 | # SDK_TESTING_URL - URL to algorand-sdk-testing, useful for forks. 11 | # SDK_TESTING_BRANCH - branch to checkout, useful for new tests. 12 | # SDK_TESTING_HARNESS - local directory that the algorand-sdk-testing repo is cloned into. 13 | # VERBOSE_HARNESS - more output while the script runs. 14 | # INSTALL_ONLY - installs feature files only, useful for unit tests. 15 | # 16 | # WARNING: If set to 1, new features will be LOST when downloading the test harness. 17 | # REGARDLESS: modified features are ALWAYS overwritten. 18 | # REMOVE_LOCAL_FEATURES - delete all local cucumber feature files before downloading these from github. 19 | # 20 | # WARNING: Be careful when turning on the next variable. 21 | # In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env` 22 | # OVERWRITE_TESTING_ENVIRONMENT=0 23 | 24 | SHUTDOWN=0 25 | if [ $# -ne 0 ]; then 26 | if [ $# -ne 1 ]; then 27 | echo "this script accepts a single argument, which must be 'up' or 'down'." 28 | exit 1 29 | fi 30 | 31 | case $1 in 32 | 'up') 33 | ;; # default. 34 | 'down') 35 | SHUTDOWN=1 36 | ;; 37 | *) 38 | echo "unknown parameter '$1'." 39 | echo "this script accepts a single argument, which must be 'up' or 'down'." 40 | exit 1 41 | ;; 42 | esac 43 | fi 44 | 45 | START=$(date "+%s") 46 | 47 | THIS=$(basename "$0") 48 | ENV_FILE=".test-env" 49 | TEST_DIR="tests/cucumber" 50 | 51 | set -a 52 | source "$ENV_FILE" 53 | set +a 54 | 55 | rootdir=$(dirname "$0") 56 | pushd "$rootdir" 57 | 58 | echo "$THIS: VERBOSE_HARNESS=$VERBOSE_HARNESS" 59 | 60 | ## Reset test harness 61 | if [ -d "$SDK_TESTING_HARNESS" ]; then 62 | pushd "$SDK_TESTING_HARNESS" 63 | ./scripts/down.sh 64 | popd 65 | rm -rf "$SDK_TESTING_HARNESS" 66 | if [[ $SHUTDOWN == 1 ]]; then 67 | echo "$THIS: network shutdown complete." 68 | exit 0 69 | fi 70 | else 71 | echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP" 72 | fi 73 | 74 | if [[ $SHUTDOWN == 1 ]]; then 75 | echo "$THIS: unable to shutdown network." 76 | exit 1 77 | fi 78 | 79 | git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS" 80 | 81 | 82 | echo "$THIS: OVERWRITE_TESTING_ENVIRONMENT=$OVERWRITE_TESTING_ENVIRONMENT" 83 | if [[ $OVERWRITE_TESTING_ENVIRONMENT == 1 ]]; then 84 | echo "$THIS: OVERWRITE downloaded $SDK_TESTING_HARNESS/.env with $ENV_FILE:" 85 | cp "$ENV_FILE" "$SDK_TESTING_HARNESS"/.env 86 | fi 87 | 88 | echo "$THIS: REMOVE_LOCAL_FEATURES=$REMOVE_LOCAL_FEATURES" 89 | ## Copy feature files into the project resources 90 | if [[ $REMOVE_LOCAL_FEATURES == 1 ]]; then 91 | echo "$THIS: OVERWRITE wipes clean $TEST_DIR/features" 92 | if [[ $VERBOSE_HARNESS == 1 ]]; then 93 | ( tree $TEST_DIR/features && echo "$THIS: see the previous for files deleted" ) || true 94 | fi 95 | rm -rf $TEST_DIR/features 96 | fi 97 | mkdir -p $TEST_DIR/features 98 | cp -r "$SDK_TESTING_HARNESS"/features/* $TEST_DIR/features 99 | if [[ $VERBOSE_HARNESS == 1 ]]; then 100 | ( tree $TEST_DIR/features && echo "$THIS: see the previous for files copied over" ) || true 101 | fi 102 | echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s" 103 | 104 | if [[ $INSTALL_ONLY == 1 ]]; then 105 | echo "$THIS: configured to install feature files only. Not starting test harness environment." 106 | exit 0 107 | fi 108 | 109 | ## Start test harness environment 110 | pushd "$SDK_TESTING_HARNESS" 111 | 112 | [[ "$VERBOSE_HARNESS" = 1 ]] && V_FLAG="-v" || V_FLAG="" 113 | echo "$THIS: standing up harnness with command [./up.sh $V_FLAG]" 114 | ./scripts/up.sh "$V_FLAG" 115 | 116 | popd 117 | echo "$THIS: seconds it took to finish testing sdk's up.sh: $(($(date "+%s") - START))s" 118 | echo "" 119 | echo "--------------------------------------------------------------------------------" 120 | echo "|" 121 | echo "| To run sandbox commands, cd into $SDK_TESTING_HARNESS/.sandbox " 122 | echo "|" 123 | echo "--------------------------------------------------------------------------------" 124 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import JSONbigWithoutConfig from 'json-bigint'; 2 | import IntDecoding from '../types/intDecoding'; 3 | 4 | const JSONbig = JSONbigWithoutConfig({ useNativeBigInt: true, strict: true }); 5 | 6 | export interface JSONOptions { 7 | intDecoding?: IntDecoding; 8 | } 9 | 10 | /** 11 | * Parse JSON with additional options. 12 | * @param str - The JSON string to parse. 13 | * @param options - Options object to configure how integers in 14 | * this request's JSON response will be decoded. Use the `intDecoding` 15 | * property with one of the following options: 16 | * 17 | * * "default": All integers will be decoded as Numbers, meaning any values greater than 18 | * Number.MAX_SAFE_INTEGER will lose precision. 19 | * * "safe": All integers will be decoded as Numbers, but if any values are greater than 20 | * Number.MAX_SAFE_INTEGER an error will be thrown. 21 | * * "mixed": Integers will be decoded as Numbers if they are less than or equal to 22 | * Number.MAX_SAFE_INTEGER, otherwise they will be decoded as BigInts. 23 | * * "bigint": All integers will be decoded as BigInts. 24 | * 25 | * Defaults to "default" if not included. 26 | */ 27 | export function parseJSON(str: string, options?: JSONOptions) { 28 | const intDecoding = 29 | options && options.intDecoding ? options.intDecoding : IntDecoding.DEFAULT; 30 | return JSONbig.parse(str, (_, value) => { 31 | if ( 32 | value != null && 33 | typeof value === 'object' && 34 | Object.getPrototypeOf(value) == null 35 | ) { 36 | // JSONbig.parse objects are created with Object.create(null) and thus have a null prototype 37 | // let us remedy that 38 | Object.setPrototypeOf(value, Object.prototype); 39 | } 40 | 41 | if (typeof value === 'bigint') { 42 | if (intDecoding === 'safe' && value > Number.MAX_SAFE_INTEGER) { 43 | throw new Error( 44 | `Integer exceeds maximum safe integer: ${value.toString()}. Try parsing with a different intDecoding option.` 45 | ); 46 | } 47 | if ( 48 | intDecoding === 'bigint' || 49 | (intDecoding === 'mixed' && value > Number.MAX_SAFE_INTEGER) 50 | ) { 51 | return value; 52 | } 53 | // JSONbig.parse converts number to BigInts if they are >= 10**15. This is smaller than 54 | // Number.MAX_SAFE_INTEGER, so we can convert some BigInts back to normal numbers. 55 | return Number(value); 56 | } 57 | 58 | if (typeof value === 'number') { 59 | if (intDecoding === 'bigint' && Number.isInteger(value)) { 60 | return BigInt(value); 61 | } 62 | } 63 | 64 | return value; 65 | }); 66 | } 67 | 68 | /** 69 | * ArrayEqual takes two arrays and return true if equal, false otherwise 70 | */ 71 | export function arrayEqual(a: ArrayLike, b: ArrayLike) { 72 | if (a.length !== b.length) { 73 | return false; 74 | } 75 | return Array.from(a).every((val, i) => val === b[i]); 76 | } 77 | 78 | /** 79 | * ConcatArrays takes n number arrays and returns a joint Uint8Array 80 | * @param arrs - An arbitrary number of n array-like number list arguments 81 | * @returns [a,b] 82 | */ 83 | export function concatArrays(...arrs: ArrayLike[]) { 84 | const size = arrs.reduce((sum, arr) => sum + arr.length, 0); 85 | const c = new Uint8Array(size); 86 | 87 | let offset = 0; 88 | for (let i = 0; i < arrs.length; i++) { 89 | c.set(arrs[i], offset); 90 | offset += arrs[i].length; 91 | } 92 | 93 | return c; 94 | } 95 | 96 | /** 97 | * Remove undefined properties from an object 98 | * @param obj - An object, preferably one with some undefined properties 99 | * @returns A copy of the object with undefined properties removed 100 | */ 101 | export function removeUndefinedProperties( 102 | obj: Record 103 | ) { 104 | const mutableCopy = { ...obj }; 105 | Object.keys(mutableCopy).forEach((key) => { 106 | if (typeof mutableCopy[key] === 'undefined') delete mutableCopy[key]; 107 | }); 108 | return mutableCopy; 109 | } 110 | 111 | /** 112 | * Check whether the environment is Node.js (as opposed to the browser) 113 | * @returns True if Node.js environment, false otherwise 114 | */ 115 | export function isNode() { 116 | return ( 117 | typeof process === 'object' && 118 | typeof process.versions === 'object' && 119 | typeof process.versions.node !== 'undefined' 120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupAssetBalances.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupAssetBalances extends JSONRequest { 6 | /** 7 | * Returns the list of accounts which hold the given asset and their balance. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const assetId = 163650; 12 | * const assetBalances = await indexerClient.lookupAssetBalances(assetId).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2assetsasset-idbalances) 16 | * @param index - The asset ID to look up. 17 | */ 18 | constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) { 19 | super(c, intDecoding); 20 | this.index = index; 21 | } 22 | 23 | /** 24 | * @returns `/v2/assets/${index}/balances` 25 | */ 26 | path() { 27 | return `/v2/assets/${this.index}/balances`; 28 | } 29 | 30 | /** 31 | * Limit results for pagination. 32 | * 33 | * #### Example 34 | * ```typescript 35 | * const assetId = 163650; 36 | * const maxResults = 20; 37 | * const assetBalances = await indexerClient 38 | * .lookupAssetBalances(assetId) 39 | * .limit(maxResults) 40 | * .do(); 41 | * ``` 42 | * 43 | * @param limit - maximum number of results to return. 44 | * @category query 45 | */ 46 | limit(limit: number) { 47 | this.query.limit = limit; 48 | return this; 49 | } 50 | 51 | /** 52 | * Filtered results should have an asset balance greater than this value. 53 | * 54 | * #### Example 55 | * ```typescript 56 | * const assetId = 163650; 57 | * const minBalance = 1000000; 58 | * const assetBalances = await indexerClient 59 | * .lookupAssetBalances(assetId) 60 | * .currencyGreaterThan(minBalance) 61 | * .do(); 62 | * ``` 63 | * @param greater 64 | * @category query 65 | */ 66 | currencyGreaterThan(greater: number) { 67 | this.query['currency-greater-than'] = greater; 68 | return this; 69 | } 70 | 71 | /** 72 | * Filtered results should have an asset balance less than this value. 73 | * 74 | * #### Example 75 | * ```typescript 76 | * const assetId = 163650; 77 | * const maxBalance = 2000000; 78 | * const assetBalances = await indexerClient 79 | * .lookupAssetBalances(assetId) 80 | * .currencyLessThan(maxBalance) 81 | * .do(); 82 | * ``` 83 | * @param lesser 84 | * @category query 85 | */ 86 | currencyLessThan(lesser: number) { 87 | this.query['currency-less-than'] = lesser; 88 | return this; 89 | } 90 | 91 | /** 92 | * Specify the next page of results. 93 | * 94 | * #### Example 95 | * ```typescript 96 | * const assetId = 163650; 97 | * const maxResults = 20; 98 | * 99 | * const assetBalancesPage1 = await indexerClient 100 | * .lookupAssetBalances(assetId) 101 | * .limit(maxResults) 102 | * .do(); 103 | * 104 | * const assetBalancesPage2 = await indexerClient 105 | * .lookupAssetBalances(assetId) 106 | * .limit(maxResults) 107 | * .nextToken(assetBalancesPage1["next-token"]) 108 | * .do(); 109 | * ``` 110 | * @param nextToken - provided by the previous results. 111 | * @category query 112 | */ 113 | nextToken(nextToken: string) { 114 | this.query.next = nextToken; 115 | return this; 116 | } 117 | 118 | /** 119 | * Include all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates. 120 | * 121 | * #### Example 1 122 | * ```typescript 123 | * const assetId = 163650; 124 | * const assetBalances = await indexerClient 125 | * .lookupAssetBalances(assetId) 126 | * .includeAll(false) 127 | * .do(); 128 | * ``` 129 | * 130 | * #### Example 2 131 | * ```typescript 132 | * const assetId = 163650; 133 | * const assetBalances = await indexerClient 134 | * .lookupAssetBalances(assetId) 135 | * .includeAll() 136 | * .do(); 137 | * ``` 138 | * 139 | * @param value 140 | * @category query 141 | */ 142 | includeAll(value = true) { 143 | this.query['include-all'] = value; 144 | return this; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupApplicationLogs.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupApplicationLogs extends JSONRequest { 6 | /** 7 | * Returns log messages generated by the passed in application. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const appId = 60553466; 12 | * const appLogs = await indexerClient.lookupApplicationLogs(appId).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applicationsapplication-idlogs) 16 | * @param appID - The ID of the application which generated the logs. 17 | * @category GET 18 | */ 19 | constructor(c: HTTPClient, intDecoding: IntDecoding, private appID: number) { 20 | super(c, intDecoding); 21 | this.appID = appID; 22 | } 23 | 24 | /** 25 | * @returns `/v2/applications/${appID}/logs` 26 | */ 27 | path() { 28 | return `/v2/applications/${this.appID}/logs`; 29 | } 30 | 31 | /** 32 | * Limit results for pagination. 33 | * 34 | * #### Example 35 | * ```typescript 36 | * const maxResults = 20; 37 | * const appLogs = await indexerClient 38 | * .lookupApplicationLogs(appId) 39 | * .limit(maxResults) 40 | * .do(); 41 | * ``` 42 | * 43 | * @param limit - maximum number of results to return. 44 | */ 45 | limit(limit: number) { 46 | this.query.limit = limit; 47 | return this; 48 | } 49 | 50 | /** 51 | * Include results at or after the specified min-round. 52 | * 53 | * #### Example 54 | * ```typescript 55 | * const minRound = 18309917; 56 | * const appLogs = await indexerClient 57 | * .lookupApplicationLogs(appId) 58 | * .minRound(minRound) 59 | * .do(); 60 | * ``` 61 | * 62 | * @param round 63 | * @category query 64 | */ 65 | minRound(round: number) { 66 | this.query['min-round'] = round; 67 | return this; 68 | } 69 | 70 | /** 71 | * Include results at or before the specified max-round. 72 | * 73 | * #### Example 74 | * ```typescript 75 | * const maxRound = 18309917; 76 | * const appLogs = await indexerClient 77 | * .lookupApplicationLogs(appId) 78 | * .maxRound(maxRound) 79 | * .do(); 80 | * ``` 81 | * 82 | * @param round 83 | * @category query 84 | */ 85 | maxRound(round: number) { 86 | this.query['max-round'] = round; 87 | return this; 88 | } 89 | 90 | /** 91 | * The next page of results. 92 | * 93 | * #### Example 94 | * ```typescript 95 | * const maxResults = 25; 96 | * 97 | * const appLogsPage1 = await indexerClient 98 | * .lookupApplicationLogs(appId) 99 | * .limit(maxResults) 100 | * .do(); 101 | * 102 | * const appLogsPage2 = await indexerClient 103 | * .lookupApplicationLogs(appId) 104 | * .limit(maxResults) 105 | * .nextToken(appLogsPage1["next-token"]) 106 | * .do(); 107 | * ``` 108 | * 109 | * @param nextToken - provided by the previous results. 110 | * @category query 111 | */ 112 | nextToken(nextToken: string) { 113 | this.query.next = nextToken; 114 | return this; 115 | } 116 | 117 | /** 118 | * Only include transactions with this sender address. 119 | * 120 | * #### Example 121 | * ```typescript 122 | * const sender = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 123 | * const appLogs = await indexerClient 124 | * .lookupApplicationLogs(appId) 125 | * .sender(sender) 126 | * .do(); 127 | * ``` 128 | * 129 | * @param senderAddress 130 | * @category query 131 | */ 132 | sender(senderAddress: string) { 133 | this.query['sender-address'] = senderAddress; 134 | return this; 135 | } 136 | 137 | /** 138 | * Lookup the specific transaction by ID. 139 | * 140 | * #### Example 141 | * ```typescript 142 | * const txId = "MEUOC4RQJB23CQZRFRKYEI6WBO73VTTPST5A7B3S5OKBUY6LFUDA"; 143 | * const appLogs = await indexerClient 144 | * .lookupApplicationLogs(appId) 145 | * .txid(txId) 146 | * .do(); 147 | * ``` 148 | * 149 | * @param txid 150 | * @category query 151 | */ 152 | txid(txid: string) { 153 | this.query.txid = txid; 154 | return this; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | node: circleci/node@5.0.0 5 | slack: circleci/slack@4.4.2 6 | browser-tools: circleci/browser-tools@1.2.4 7 | gh-pages: sugarshin/gh-pages@1.0.0 8 | 9 | parameters: 10 | ubuntu_machine: 11 | type: string 12 | default: 'ubuntu-2004:202111-02' 13 | ubuntu_docker: 14 | type: string 15 | default: 'ubuntu:focal' 16 | 17 | workflows: 18 | circleci_build_and_test: 19 | jobs: 20 | - ESLint 21 | - prettier 22 | - compile_test 23 | - build_and_test: 24 | name: build_and_test_<< matrix.browser >> 25 | context: slack-secrets 26 | matrix: 27 | parameters: 28 | browser: ['node', 'chrome', 'firefox'] 29 | - generate_docs: 30 | requires: 31 | - ESLint 32 | - prettier 33 | - compile_test 34 | - build_and_test 35 | filters: 36 | branches: 37 | only: 38 | - master 39 | 40 | jobs: 41 | ESLint: 42 | docker: 43 | - image: << pipeline.parameters.ubuntu_docker >> 44 | steps: 45 | - checkout 46 | - install_dependencies 47 | - run: npm run lint 48 | 49 | prettier: 50 | docker: 51 | - image: << pipeline.parameters.ubuntu_docker >> 52 | steps: 53 | - checkout 54 | - install_dependencies 55 | - run: npx prettier --check . 56 | 57 | compile_test: 58 | docker: 59 | - image: << pipeline.parameters.ubuntu_docker >> 60 | steps: 61 | - checkout 62 | - install_dependencies 63 | - run: 64 | name: Compile test 65 | command: | 66 | npm run build 67 | npx tsc -p tests/compile 68 | 69 | build_and_test: 70 | parameters: 71 | browser: 72 | type: string 73 | machine: 74 | image: << pipeline.parameters.ubuntu_machine >> 75 | resource_class: medium 76 | steps: 77 | - checkout 78 | - install_dependencies: 79 | sudo: 'sudo' 80 | - run: 81 | name: Install docker 82 | command: | 83 | export SUDO=sudo 84 | $SUDO apt update 85 | if [ "<< parameters.browser >>" == "chrome" ]; then $SUDO apt -y install google-chrome-stable; else echo "skipping chrome install" ; fi 86 | $SUDO apt -y install ca-certificates curl gnupg lsb-release 87 | $SUDO curl -fsSL https://download.docker.com/linux/ubuntu/gpg | $SUDO gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 88 | $SUDO echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ 89 | $(lsb_release -cs) stable" | $SUDO tee /etc/apt/sources.list.d/docker.list > /dev/null 90 | $SUDO apt update 91 | $SUDO apt -y install docker-ce docker-ce-cli containerd.io 92 | - browser-tools/install-browser-tools 93 | - run: 94 | name: << parameters.browser >> test 95 | command: | 96 | if [ "<< parameters.browser >>" == "node" ]; then unset TEST_BROWSER; else export TEST_BROWSER=<< parameters.browser >> ; fi 97 | npm test 98 | make docker-test 99 | 100 | generate_docs: 101 | docker: 102 | - image: << pipeline.parameters.ubuntu_docker >> 103 | steps: 104 | - checkout 105 | - install_dependencies 106 | - run: 107 | name: Generate docs for GitHub Pages 108 | command: | 109 | rm -rf docs 110 | npm run docs 111 | touch docs/.nojekyll 112 | - gh-pages/deploy: 113 | ssh-fingerprints: '75:f5:ad:46:65:a5:22:81:f7:20:ca:74:fb:c6:57:d1' 114 | build-dir: docs 115 | commit-message: 'Automated docs update' 116 | 117 | commands: 118 | install_dependencies: 119 | parameters: 120 | sudo: 121 | type: string 122 | default: '' 123 | steps: 124 | - run: 125 | name: Install Dependencies 126 | command: | 127 | << parameters.sudo >> apt -y update 128 | << parameters.sudo >> apt -y install curl make git build-essential jq unzip 129 | - node/install: 130 | node-version: '14' 131 | - run: 132 | name: npm ci 133 | command: | 134 | set -e 135 | npm ci 136 | npm install chromedriver@latest 137 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupAccountCreatedAssets.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupAccountCreatedAssets extends JSONRequest { 6 | /** 7 | * Returns asset information created by the given account. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 12 | * const accountCreatedAssets = await indexerClient.lookupAccountCreatedAssets(address).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2accountsaccount-idcreated-assets) 16 | * @param account - The address of the account to look up. 17 | * @category GET 18 | */ 19 | constructor( 20 | c: HTTPClient, 21 | intDecoding: IntDecoding, 22 | private account: string 23 | ) { 24 | super(c, intDecoding); 25 | this.account = account; 26 | } 27 | 28 | /** 29 | * @returns `/v2/accounts/${account}/created-assets` 30 | */ 31 | path() { 32 | return `/v2/accounts/${this.account}/created-assets`; 33 | } 34 | 35 | /** 36 | * Add a limit for filter. 37 | * 38 | * #### Example 39 | * ```typescript 40 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 41 | * const maxResults = 20; 42 | * const accountAssets = await indexerClient 43 | * .lookupAccountCreatedAssets(address) 44 | * .limit(maxResults) 45 | * .do(); 46 | * ``` 47 | * 48 | * @param limit - maximum number of results to return. 49 | * @category query 50 | */ 51 | limit(limit: number) { 52 | this.query.limit = limit; 53 | return this; 54 | } 55 | 56 | /** 57 | * Specify round to filter with. 58 | * 59 | * #### Example 60 | * ```typescript 61 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 62 | * const targetBlock = 18309917; 63 | * const accountAssets = await indexerClient 64 | * .lookupAccountCreatedAssets(address) 65 | * .round(targetBlock) 66 | * .do(); 67 | * ``` 68 | * @param round 69 | * @category query 70 | */ 71 | round(round: number) { 72 | this.query.round = round; 73 | return this; 74 | } 75 | 76 | /** 77 | * Specify the next page of results. 78 | * 79 | * #### Example 80 | * ```typescript 81 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 82 | * const maxResults = 20; 83 | * 84 | * const accountAssetsPage1 = await indexerClient 85 | * .lookupAccountCreatedAssets(address) 86 | * .limit(maxResults) 87 | * .do(); 88 | * ``` 89 | * 90 | * const accountAssetsPage2 = await indexerClient 91 | * .lookupAccountCreatedAssets(address) 92 | * .limit(maxResults) 93 | * .next(accountAssetsPage1["next-token"]) 94 | * .do(); 95 | * ``` 96 | * @param nextToken - provided by the previous results. 97 | * @category query 98 | */ 99 | nextToken(nextToken: string) { 100 | this.query.next = nextToken; 101 | return this; 102 | } 103 | 104 | /** 105 | * Include all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates 106 | * 107 | * #### Example 108 | * ```typescript 109 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 110 | * const accountAssets = await indexerClient 111 | * .lookupAccountCreatedAssets(address) 112 | * .includeAll(false) 113 | * .do(); 114 | * ``` 115 | * @param value 116 | * @category query 117 | */ 118 | includeAll(value = true) { 119 | this.query['include-all'] = value; 120 | return this; 121 | } 122 | 123 | /** 124 | * Specify an assetID to search for. 125 | * 126 | * #### Example 127 | * ```typescript 128 | * const assetID = 163650; 129 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 130 | * const assetAssets = await indexerClient 131 | * .lookupAccountCreatedAssets(address) 132 | * .assetID(assetID) 133 | * .do(); 134 | * ``` 135 | * @param index - the assetID 136 | * @category query 137 | */ 138 | assetID(index: number) { 139 | this.query['asset-id'] = index; 140 | return this; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupAccountAppLocalStates.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupAccountAppLocalStates extends JSONRequest { 6 | /** 7 | * Returns application local state about the given account. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 12 | * const accountAppLocalStates = await indexerClient.lookupAccountAppLocalStates(address).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2accountsaccount-idapps-local-state) 16 | * @param account - The address of the account to look up. 17 | * @category GET 18 | */ 19 | constructor( 20 | c: HTTPClient, 21 | intDecoding: IntDecoding, 22 | private account: string 23 | ) { 24 | super(c, intDecoding); 25 | this.account = account; 26 | } 27 | 28 | /** 29 | * @returns `/v2/accounts/${account}/apps-local-state` 30 | */ 31 | path() { 32 | return `/v2/accounts/${this.account}/apps-local-state`; 33 | } 34 | 35 | /** 36 | * Add a limit for filter. 37 | * 38 | * #### Example 39 | * ```typescript 40 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 41 | * const maxResults = 20; 42 | * const accountAssets = await indexerClient 43 | * .lookupAccountAppLocalStates(address) 44 | * .limit(maxResults) 45 | * .do(); 46 | * ``` 47 | * 48 | * @param limit - maximum number of results to return. 49 | * @category query 50 | */ 51 | limit(limit: number) { 52 | this.query.limit = limit; 53 | return this; 54 | } 55 | 56 | /** 57 | * Specify round to filter with. 58 | * 59 | * #### Example 60 | * ```typescript 61 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 62 | * const targetBlock = 18309917; 63 | * const accountAssets = await indexerClient 64 | * .lookupAccountAppLocalStates(address) 65 | * .round(targetBlock) 66 | * .do(); 67 | * ``` 68 | * @param round 69 | * @category query 70 | */ 71 | round(round: number) { 72 | this.query.round = round; 73 | return this; 74 | } 75 | 76 | /** 77 | * Specify the next page of results. 78 | * 79 | * #### Example 80 | * ```typescript 81 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 82 | * const maxResults = 20; 83 | * 84 | * const accountAssetsPage1 = await indexerClient 85 | * .lookupAccountAppLocalStates(address) 86 | * .limit(maxResults) 87 | * .do(); 88 | * 89 | * const accountAssetsPage2 = await indexerClient 90 | * .lookupAccountAppLocalStates(address) 91 | * .limit(maxResults) 92 | * .next(accountAssetsPage1["next-token"]) 93 | * .do(); 94 | * ``` 95 | * @param nextToken - provided by the previous results. 96 | */ 97 | nextToken(nextToken: string) { 98 | this.query.next = nextToken; 99 | return this; 100 | } 101 | 102 | /** 103 | * Include all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates 104 | * 105 | * #### Example 106 | * ```typescript 107 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 108 | * const accountAssets = await indexerClient 109 | * .lookupAccountAppLocalStates(address) 110 | * .includeAll(false) 111 | * .do(); 112 | * ``` 113 | * @param value 114 | * @category query 115 | */ 116 | includeAll(value = true) { 117 | this.query['include-all'] = value; 118 | return this; 119 | } 120 | 121 | /** 122 | * Specify an applicationID to search for. 123 | * 124 | * #### Example 125 | * ```typescript 126 | * const applicationID = 163650; 127 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 128 | * const accountApplications = await indexerClient 129 | * .lookupAccountAppLocalStates(address) 130 | * .applicationID(applicationID) 131 | * .do(); 132 | * ``` 133 | * @param index - the applicationID 134 | * @category query 135 | */ 136 | applicationID(index: number) { 137 | this.query['application-id'] = index; 138 | return this; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/3.Address.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const nacl = require('../src/nacl/naclWrappers'); 3 | const algosdk = require('../src/index'); 4 | const address = require('../src/encoding/address'); 5 | 6 | describe('address', () => { 7 | describe('#isValid', () => { 8 | const correctCase = 9 | 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI'; 10 | // prettier-ignore 11 | const correctPublicKey = new Uint8Array([99, 180, 127, 102, 156, 252, 55, 227, 39, 198, 169, 232, 163, 16, 36, 130, 26, 223, 230, 213, 0, 153, 108, 192, 200, 197, 28, 77, 196, 50, 141, 112]); 12 | const correctChecksum = new Uint8Array([122, 240, 2, 74]); 13 | const malformedAddress1 = 14 | 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJ'; 15 | const maldformedAddress2 = 123; 16 | const malformedAddress3 = 17 | 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACererZQGI113RBSRVYHV4ACJI'; 18 | const wrongChecksumAddress = 19 | 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJG'; 20 | 21 | // Check core functions 22 | it('should verify a valid Algorand address', () => { 23 | const decodedAddress = algosdk.decodeAddress(correctCase); 24 | assert.deepStrictEqual(decodedAddress.publicKey, correctPublicKey); 25 | assert.deepStrictEqual(decodedAddress.checksum, correctChecksum); 26 | }); 27 | 28 | it('should fail to verify a malformed Algorand address', () => { 29 | assert.throws( 30 | () => { 31 | algosdk.decodeAddress(malformedAddress1); 32 | }, 33 | (err) => err.message === address.MALFORMED_ADDRESS_ERROR_MSG 34 | ); 35 | assert.throws( 36 | () => { 37 | algosdk.decodeAddress(maldformedAddress2); 38 | }, 39 | (err) => err.message === address.MALFORMED_ADDRESS_ERROR_MSG 40 | ); 41 | // Catch an exception possibly thrown by base32 decoding function 42 | assert.throws( 43 | () => { 44 | algosdk.decodeAddress(malformedAddress3); 45 | }, 46 | (err) => err.message === 'Invalid base32 characters' 47 | ); 48 | }); 49 | 50 | it('should fail to verify a checksum for an invalid Algorand address', () => { 51 | assert.throws( 52 | () => { 53 | algosdk.decodeAddress(wrongChecksumAddress); 54 | }, 55 | (err) => err.message === address.CHECKSUM_ADDRESS_ERROR_MSG 56 | ); 57 | }); 58 | 59 | // Check helper functions 60 | it('should verify a valid Algorand address', () => { 61 | assert.ok(algosdk.isValidAddress(correctCase)); 62 | }); 63 | 64 | it('should fail to verify an invalid Algorand address', () => { 65 | assert.strictEqual(algosdk.isValidAddress(malformedAddress1), false); 66 | }); 67 | }); 68 | 69 | describe('encode, decode', () => { 70 | it('should be able to encode and verify an address', () => { 71 | const pk = nacl.randomBytes(32); 72 | const addr = algosdk.encodeAddress(pk); 73 | assert.ok(algosdk.isValidAddress(addr)); 74 | }); 75 | 76 | it('should be able to encode and decode an address', () => { 77 | const pk = nacl.randomBytes(32); 78 | const addr = algosdk.encodeAddress(pk); 79 | const d = algosdk.decodeAddress(addr); 80 | assert.deepStrictEqual(new Uint8Array(d.publicKey), pk); 81 | }); 82 | }); 83 | 84 | describe('from multisig preimage', () => { 85 | it('should match main repo code', () => { 86 | const addr1 = 87 | 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; 88 | const addr2 = 89 | 'HTNOX33OCQI2JCOLZ2IRM3BC2WZ6JUILSLEORBPFI6W7GU5Q4ZW6LINHLA'; 90 | const addr3 = 91 | 'E6JSNTY4PVCY3IRZ6XEDHEO6VIHCQ5KGXCIQKFQCMB2N6HXRY4IB43VSHI'; 92 | const params = { 93 | version: 1, 94 | threshold: 2, 95 | addrs: [addr1, addr2, addr3], 96 | }; 97 | const expectAddr = 98 | 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; 99 | const actualAddr = algosdk.multisigAddress(params); 100 | assert.ok(algosdk.isValidAddress(actualAddr)); 101 | assert.deepStrictEqual(actualAddr, expectAddr); 102 | }); 103 | }); 104 | 105 | describe('#getApplicationAddress', () => { 106 | it('should produce the correct address', () => { 107 | const appID = 77; 108 | const expected = 109 | 'PCYUFPA2ZTOYWTP43MX2MOX2OWAIAXUDNC2WFCXAGMRUZ3DYD6BWFDL5YM'; 110 | const actual = algosdk.getApplicationAddress(appID); 111 | assert.strictEqual(actual, expected); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/client/v2/indexer/lookupAccountCreatedApplications.ts: -------------------------------------------------------------------------------- 1 | import JSONRequest from '../jsonrequest'; 2 | import HTTPClient from '../../client'; 3 | import IntDecoding from '../../../types/intDecoding'; 4 | 5 | export default class LookupAccountCreatedApplications extends JSONRequest { 6 | /** 7 | * Returns application information created by the given account. 8 | * 9 | * #### Example 10 | * ```typescript 11 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 12 | * const accountCreatedApps = await indexerClient.lookupAccountCreatedApplications(address).do(); 13 | * ``` 14 | * 15 | * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2accountsaccount-idcreated-applications) 16 | * @param account - The address of the account to look up. 17 | * @category GET 18 | */ 19 | constructor( 20 | c: HTTPClient, 21 | intDecoding: IntDecoding, 22 | private account: string 23 | ) { 24 | super(c, intDecoding); 25 | this.account = account; 26 | } 27 | 28 | /** 29 | * @returns `/v2/accounts/${account}/created-applications` 30 | */ 31 | path() { 32 | return `/v2/accounts/${this.account}/created-applications`; 33 | } 34 | 35 | /** 36 | * Add a limit for filter. 37 | * 38 | * #### Example 39 | * ```typescript 40 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 41 | * const maxResults = 20; 42 | * const accountAssets = await indexerClient 43 | * .lookupAccountCreatedApplications(address) 44 | * .limit(maxResults) 45 | * .do(); 46 | * ``` 47 | * 48 | * @param limit - maximum number of results to return. 49 | * @category query 50 | */ 51 | limit(limit: number) { 52 | this.query.limit = limit; 53 | return this; 54 | } 55 | 56 | /** 57 | * Specify round to filter with. 58 | * 59 | * #### Example 60 | * ```typescript 61 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 62 | * const targetBlock = 18309917; 63 | * const accountAssets = await indexerClient 64 | * .lookupAccountCreatedApplications(address) 65 | * .round(targetBlock) 66 | * .do(); 67 | * ``` 68 | * @param round 69 | * @category query 70 | */ 71 | round(round: number) { 72 | this.query.round = round; 73 | return this; 74 | } 75 | 76 | /** 77 | * Specify the next page of results. 78 | * 79 | * #### Example 80 | * ```typescript 81 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 82 | * const maxResults = 20; 83 | * 84 | * const accountAssetsPage1 = await indexerClient 85 | * .lookupAccountCreatedApplications(address) 86 | * .limit(maxResults) 87 | * .do(); 88 | * 89 | * const accountAssetsPage2 = await indexerClient 90 | * .lookupAccountCreatedApplications(address) 91 | * .limit(maxResults) 92 | * .next(accountAssetsPage1["next-token"]) 93 | * .do(); 94 | * ``` 95 | * @param nextToken - provided by the previous results. 96 | * @category query 97 | */ 98 | nextToken(nextToken: string) { 99 | this.query.next = nextToken; 100 | return this; 101 | } 102 | 103 | /** 104 | * Include all items including closed accounts, deleted applications, destroyed assets, opted-out asset holdings, and closed-out application localstates 105 | * 106 | * #### Example 107 | * ```typescript 108 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 109 | * const accountAssets = await indexerClient 110 | * .lookupAccountCreatedApplications(address) 111 | * .includeAll(false) 112 | * .do(); 113 | * ``` 114 | * @param value 115 | * @category query 116 | */ 117 | includeAll(value = true) { 118 | this.query['include-all'] = value; 119 | return this; 120 | } 121 | 122 | /** 123 | * Specify an applicationID to search for. 124 | * 125 | * #### Example 126 | * ```typescript 127 | * const applicationID = 163650; 128 | * const address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"; 129 | * const accountApplications = await indexerClient 130 | * .lookupAccountAppLocalStates(address) 131 | * .applicationID(applicationID) 132 | * .do(); 133 | * ``` 134 | * @param index - the applicationID 135 | * @category query 136 | */ 137 | applicationID(index: number) { 138 | this.query['application-id'] = index; 139 | return this; 140 | } 141 | } 142 | --------------------------------------------------------------------------------