diff --git a/.circleci/config.yml b/.circleci/config.yml
index 08b8647c9..5801ad9cb 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -127,7 +127,7 @@ commands:
<< parameters.sudo >> apt -y update
<< parameters.sudo >> apt -y install curl make git build-essential jq unzip
- node/install:
- node-version: '16'
+ node-version: '18'
- run:
name: npm ci
command: |
diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml
index 8b71fb66d..1330c8f42 100644
--- a/.github/workflows/create-release-pr.yml
+++ b/.github/workflows/create-release-pr.yml
@@ -9,7 +9,7 @@ on:
required: true
type: string
pre_release_version:
- description: "Pre-Release version, e.g. 'beta.1'"
+ description: "(Optional) Pre-Release version, e.g. 'beta.1'. Used mainly to support consensus release on betanet."
required: false
type: string
@@ -109,7 +109,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: 18
- name: Update Version References in Source
env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 362957712..f284b0b98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+# v2.6.0
+
+
+
+## What's Changed
+
+### Bugfixes
+
+- signer: Only merge multisigs if there are more than 1 by @acfunk in https://github.com/algorand/js-algorand-sdk/pull/822
+
+### New Features
+
+- Simulate: Application State Change and Hash of Executed Bytecode by @ahangsu in https://github.com/algorand/js-algorand-sdk/pull/818
+
+### Enhancements
+
+- node: Update to node 18, drop support for node 16 by @jasonpaulos in https://github.com/algorand/js-algorand-sdk/pull/827
+- api: Regenerate client. by @winder in https://github.com/algorand/js-algorand-sdk/pull/826
+
+## New Contributors
+
+- @acfunk made their first contribution in https://github.com/algorand/js-algorand-sdk/pull/822
+
+**Full Changelog**: https://github.com/algorand/js-algorand-sdk/compare/v2.5.0...v2.6.0
+
# v2.5.0
diff --git a/README.md b/README.md
index 2fd379caf..3d65a6965 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,8 @@ Include a minified browser bundle directly in your HTML like so:
```html
```
@@ -30,8 +30,8 @@ or
```html
```
diff --git a/package-lock.json b/package-lock.json
index e9a9f4cf1..d1f3e002d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "algosdk",
- "version": "2.5.0",
+ "version": "2.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "algosdk",
- "version": "2.5.0",
+ "version": "2.6.0",
"license": "MIT",
"dependencies": {
"algo-msgpack-with-bigint": "^2.1.1",
@@ -54,7 +54,7 @@
"webpack-cli": "^5.0.1"
},
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
diff --git a/package.json b/package.json
index d65e8b405..f915406c1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "algosdk",
- "version": "2.5.0",
+ "version": "2.6.0",
"description": "The official JavaScript SDK for Algorand",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
@@ -80,6 +80,6 @@
"author": "Algorand, llc",
"license": "MIT",
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
}
}
diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts
index 8da85a997..33ecfdc10 100644
--- a/src/client/v2/algod/algod.ts
+++ b/src/client/v2/algod/algod.ts
@@ -10,6 +10,7 @@ import Genesis from './genesis';
import GetAssetByID from './getAssetByID';
import GetApplicationByID from './getApplicationByID';
import GetBlockHash from './getBlockHash';
+import GetBlockTxids from './getBlockTxids';
import GetApplicationBoxByName from './getApplicationBoxByName';
import GetApplicationBoxes from './getApplicationBoxes';
import HealthCheck from './healthCheck';
@@ -238,6 +239,23 @@ export default class AlgodClient extends ServiceClient {
return new GetBlockHash(this.c, this.intDecoding, roundNumber);
}
+ /**
+ * Get the top level transaction IDs for the block on the given round.
+ *
+ * #### Example
+ * ```typescript
+ * const roundNumber = 18038133;
+ * const block = await algodClient.getBlockTxids(roundNumber).do();
+ * ```
+ *
+ * [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/#get-v2blocksroundtxids)
+ * @param roundNumber - The round number of the block to get.
+ * @category GET
+ */
+ getBlockTxids(roundNumber: number) {
+ return new GetBlockTxids(this.c, this.intDecoding, roundNumber);
+ }
+
/**
* Returns the transaction information for a specific pending transaction.
*
diff --git a/src/client/v2/algod/getBlockTxids.ts b/src/client/v2/algod/getBlockTxids.ts
new file mode 100644
index 000000000..fe52ae8d6
--- /dev/null
+++ b/src/client/v2/algod/getBlockTxids.ts
@@ -0,0 +1,18 @@
+import JSONRequest from '../jsonrequest';
+import HTTPClient from '../../client';
+import IntDecoding from '../../../types/intDecoding';
+
+export default class GetBlockTxids extends JSONRequest {
+ round: number;
+
+ constructor(c: HTTPClient, intDecoding: IntDecoding, roundNumber: number) {
+ super(c, intDecoding);
+ if (!Number.isInteger(roundNumber))
+ throw Error('roundNumber should be an integer');
+ this.round = roundNumber;
+ }
+
+ path() {
+ return `/v2/blocks/${this.round}/txids`;
+ }
+}
diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts
index e2c8e50dc..3392591e0 100644
--- a/src/client/v2/algod/models/types.ts
+++ b/src/client/v2/algod/models/types.ts
@@ -784,6 +784,183 @@ export class Application extends BaseModel {
}
}
+/**
+ * An application's initial global/local/box states that were accessed during
+ * simulation.
+ */
+export class ApplicationInitialStates extends BaseModel {
+ /**
+ * Application index.
+ */
+ public id: number | bigint;
+
+ /**
+ * An application's global/local/box state.
+ */
+ public appBoxes?: ApplicationKVStorage;
+
+ /**
+ * An application's global/local/box state.
+ */
+ public appGlobals?: ApplicationKVStorage;
+
+ /**
+ * An application's initial local states tied to different accounts.
+ */
+ public appLocals?: ApplicationKVStorage[];
+
+ /**
+ * Creates a new `ApplicationInitialStates` object.
+ * @param id - Application index.
+ * @param appBoxes - An application's global/local/box state.
+ * @param appGlobals - An application's global/local/box state.
+ * @param appLocals - An application's initial local states tied to different accounts.
+ */
+ constructor({
+ id,
+ appBoxes,
+ appGlobals,
+ appLocals,
+ }: {
+ id: number | bigint;
+ appBoxes?: ApplicationKVStorage;
+ appGlobals?: ApplicationKVStorage;
+ appLocals?: ApplicationKVStorage[];
+ }) {
+ super();
+ this.id = id;
+ this.appBoxes = appBoxes;
+ this.appGlobals = appGlobals;
+ this.appLocals = appLocals;
+
+ this.attribute_map = {
+ id: 'id',
+ appBoxes: 'app-boxes',
+ appGlobals: 'app-globals',
+ appLocals: 'app-locals',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(
+ data: Record
+ ): ApplicationInitialStates {
+ /* eslint-disable dot-notation */
+ if (typeof data['id'] === 'undefined')
+ throw new Error(`Response is missing required field 'id': ${data}`);
+ return new ApplicationInitialStates({
+ id: data['id'],
+ appBoxes:
+ typeof data['app-boxes'] !== 'undefined'
+ ? ApplicationKVStorage.from_obj_for_encoding(data['app-boxes'])
+ : undefined,
+ appGlobals:
+ typeof data['app-globals'] !== 'undefined'
+ ? ApplicationKVStorage.from_obj_for_encoding(data['app-globals'])
+ : undefined,
+ appLocals:
+ typeof data['app-locals'] !== 'undefined'
+ ? data['app-locals'].map(ApplicationKVStorage.from_obj_for_encoding)
+ : undefined,
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
+/**
+ * An application's global/local/box state.
+ */
+export class ApplicationKVStorage extends BaseModel {
+ /**
+ * Key-Value pairs representing application states.
+ */
+ public kvs: AvmKeyValue[];
+
+ /**
+ * The address of the account associated with the local state.
+ */
+ public account?: string;
+
+ /**
+ * Creates a new `ApplicationKVStorage` object.
+ * @param kvs - Key-Value pairs representing application states.
+ * @param account - The address of the account associated with the local state.
+ */
+ constructor({ kvs, account }: { kvs: AvmKeyValue[]; account?: string }) {
+ super();
+ this.kvs = kvs;
+ this.account = account;
+
+ this.attribute_map = {
+ kvs: 'kvs',
+ account: 'account',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(
+ data: Record
+ ): ApplicationKVStorage {
+ /* eslint-disable dot-notation */
+ if (!Array.isArray(data['kvs']))
+ throw new Error(
+ `Response is missing required array field 'kvs': ${data}`
+ );
+ return new ApplicationKVStorage({
+ kvs: data['kvs'].map(AvmKeyValue.from_obj_for_encoding),
+ account: data['account'],
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
+/**
+ * References an account's local state for an application.
+ */
+export class ApplicationLocalReference extends BaseModel {
+ /**
+ * Address of the account with the local state.
+ */
+ public account: string;
+
+ /**
+ * Application ID of the local state application.
+ */
+ public app: number | bigint;
+
+ /**
+ * Creates a new `ApplicationLocalReference` object.
+ * @param account - Address of the account with the local state.
+ * @param app - Application ID of the local state application.
+ */
+ constructor({ account, app }: { account: string; app: number | bigint }) {
+ super();
+ this.account = account;
+ this.app = app;
+
+ this.attribute_map = {
+ account: 'account',
+ app: 'app',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(
+ data: Record
+ ): ApplicationLocalReference {
+ /* eslint-disable dot-notation */
+ if (typeof data['account'] === 'undefined')
+ throw new Error(`Response is missing required field 'account': ${data}`);
+ if (typeof data['app'] === 'undefined')
+ throw new Error(`Response is missing required field 'app': ${data}`);
+ return new ApplicationLocalReference({
+ account: data['account'],
+ app: data['app'],
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
/**
* Stores local state associated with an application.
*/
@@ -984,6 +1161,108 @@ export class ApplicationParams extends BaseModel {
}
}
+/**
+ * An operation against an application's global/local/box state.
+ */
+export class ApplicationStateOperation extends BaseModel {
+ /**
+ * Type of application state. Value `g` is **global state**, `l` is **local
+ * state**, `b` is **boxes**.
+ */
+ public appStateType: string;
+
+ /**
+ * The key (name) of the global/local/box state.
+ */
+ public key: Uint8Array;
+
+ /**
+ * Operation type. Value `w` is **write**, `d` is **delete**.
+ */
+ public operation: string;
+
+ /**
+ * For local state changes, the address of the account associated with the local
+ * state.
+ */
+ public account?: string;
+
+ /**
+ * Represents an AVM value.
+ */
+ public newValue?: AvmValue;
+
+ /**
+ * Creates a new `ApplicationStateOperation` object.
+ * @param appStateType - Type of application state. Value `g` is **global state**, `l` is **local
+ * state**, `b` is **boxes**.
+ * @param key - The key (name) of the global/local/box state.
+ * @param operation - Operation type. Value `w` is **write**, `d` is **delete**.
+ * @param account - For local state changes, the address of the account associated with the local
+ * state.
+ * @param newValue - Represents an AVM value.
+ */
+ constructor({
+ appStateType,
+ key,
+ operation,
+ account,
+ newValue,
+ }: {
+ appStateType: string;
+ key: string | Uint8Array;
+ operation: string;
+ account?: string;
+ newValue?: AvmValue;
+ }) {
+ super();
+ this.appStateType = appStateType;
+ this.key =
+ typeof key === 'string'
+ ? new Uint8Array(Buffer.from(key, 'base64'))
+ : key;
+ this.operation = operation;
+ this.account = account;
+ this.newValue = newValue;
+
+ this.attribute_map = {
+ appStateType: 'app-state-type',
+ key: 'key',
+ operation: 'operation',
+ account: 'account',
+ newValue: 'new-value',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(
+ data: Record
+ ): ApplicationStateOperation {
+ /* eslint-disable dot-notation */
+ if (typeof data['app-state-type'] === 'undefined')
+ throw new Error(
+ `Response is missing required field 'app-state-type': ${data}`
+ );
+ if (typeof data['key'] === 'undefined')
+ throw new Error(`Response is missing required field 'key': ${data}`);
+ if (typeof data['operation'] === 'undefined')
+ throw new Error(
+ `Response is missing required field 'operation': ${data}`
+ );
+ return new ApplicationStateOperation({
+ appStateType: data['app-state-type'],
+ key: data['key'],
+ operation: data['operation'],
+ account: data['account'],
+ newValue:
+ typeof data['new-value'] !== 'undefined'
+ ? AvmValue.from_obj_for_encoding(data['new-value'])
+ : undefined,
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
/**
* Specifies maximums on the number of each type that may be stored.
*/
@@ -1164,6 +1443,53 @@ export class AssetHolding extends BaseModel {
}
}
+/**
+ * References an asset held by an account.
+ */
+export class AssetHoldingReference extends BaseModel {
+ /**
+ * Address of the account holding the asset.
+ */
+ public account: string;
+
+ /**
+ * Asset ID of the holding.
+ */
+ public asset: number | bigint;
+
+ /**
+ * Creates a new `AssetHoldingReference` object.
+ * @param account - Address of the account holding the asset.
+ * @param asset - Asset ID of the holding.
+ */
+ constructor({ account, asset }: { account: string; asset: number | bigint }) {
+ super();
+ this.account = account;
+ this.asset = asset;
+
+ this.attribute_map = {
+ account: 'account',
+ asset: 'asset',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(
+ data: Record
+ ): AssetHoldingReference {
+ /* eslint-disable dot-notation */
+ if (typeof data['account'] === 'undefined')
+ throw new Error(`Response is missing required field 'account': ${data}`);
+ if (typeof data['asset'] === 'undefined')
+ throw new Error(`Response is missing required field 'asset': ${data}`);
+ return new AssetHoldingReference({
+ account: data['account'],
+ asset: data['asset'],
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
/**
* AssetParams specifies the parameters for an asset.
* (apar) when part of an AssetConfig transaction.
@@ -1397,6 +1723,51 @@ export class AssetParams extends BaseModel {
}
}
+/**
+ * Represents an AVM key-value pair in an application store.
+ */
+export class AvmKeyValue extends BaseModel {
+ public key: Uint8Array;
+
+ /**
+ * Represents an AVM value.
+ */
+ public value: AvmValue;
+
+ /**
+ * Creates a new `AvmKeyValue` object.
+ * @param key -
+ * @param value - Represents an AVM value.
+ */
+ constructor({ key, value }: { key: string | Uint8Array; value: AvmValue }) {
+ super();
+ this.key =
+ typeof key === 'string'
+ ? new Uint8Array(Buffer.from(key, 'base64'))
+ : key;
+ this.value = value;
+
+ this.attribute_map = {
+ key: 'key',
+ value: 'value',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(data: Record): AvmKeyValue {
+ /* eslint-disable dot-notation */
+ if (typeof data['key'] === 'undefined')
+ throw new Error(`Response is missing required field 'key': ${data}`);
+ if (typeof data['value'] === 'undefined')
+ throw new Error(`Response is missing required field 'value': ${data}`);
+ return new AvmKeyValue({
+ key: data['key'],
+ value: AvmValue.from_obj_for_encoding(data['value']),
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
/**
* Represents an AVM value.
*/
@@ -1547,6 +1918,42 @@ export class BlockResponse extends BaseModel {
}
}
+/**
+ * Top level transaction IDs in a block.
+ */
+export class BlockTxidsResponse extends BaseModel {
+ /**
+ * Block transaction IDs.
+ */
+ public blocktxids: string[];
+
+ /**
+ * Creates a new `BlockTxidsResponse` object.
+ * @param blocktxids - Block transaction IDs.
+ */
+ constructor({ blocktxids }: { blocktxids: string[] }) {
+ super();
+ this.blocktxids = blocktxids;
+
+ this.attribute_map = {
+ blocktxids: 'blockTxids',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(data: Record): BlockTxidsResponse {
+ /* eslint-disable dot-notation */
+ if (!Array.isArray(data['blockTxids']))
+ throw new Error(
+ `Response is missing required array field 'blockTxids': ${data}`
+ );
+ return new BlockTxidsResponse({
+ blocktxids: data['blockTxids'],
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
/**
* Box name and its content.
*/
@@ -1654,6 +2061,60 @@ export class BoxDescriptor extends BaseModel {
}
}
+/**
+ * References a box of an application.
+ */
+export class BoxReference extends BaseModel {
+ /**
+ * Application ID which this box belongs to
+ */
+ public app: number | bigint;
+
+ /**
+ * Base64 encoded box name
+ */
+ public name: Uint8Array;
+
+ /**
+ * Creates a new `BoxReference` object.
+ * @param app - Application ID which this box belongs to
+ * @param name - Base64 encoded box name
+ */
+ constructor({
+ app,
+ name,
+ }: {
+ app: number | bigint;
+ name: string | Uint8Array;
+ }) {
+ super();
+ this.app = app;
+ this.name =
+ typeof name === 'string'
+ ? new Uint8Array(Buffer.from(name, 'base64'))
+ : name;
+
+ this.attribute_map = {
+ app: 'app',
+ name: 'name',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(data: Record): BoxReference {
+ /* eslint-disable dot-notation */
+ if (typeof data['app'] === 'undefined')
+ throw new Error(`Response is missing required field 'app': ${data}`);
+ if (typeof data['name'] === 'undefined')
+ throw new Error(`Response is missing required field 'name': ${data}`);
+ return new BoxReference({
+ app: data['app'],
+ name: data['name'],
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
/**
* Box names of an application
*/
@@ -3473,6 +3934,51 @@ export class ScratchChange extends BaseModel {
}
}
+/**
+ * Initial states of resources that were accessed during simulation.
+ */
+export class SimulateInitialStates extends BaseModel {
+ /**
+ * The initial states of accessed application before simulation. The order of this
+ * array is arbitrary.
+ */
+ public appInitialStates?: ApplicationInitialStates[];
+
+ /**
+ * Creates a new `SimulateInitialStates` object.
+ * @param appInitialStates - The initial states of accessed application before simulation. The order of this
+ * array is arbitrary.
+ */
+ constructor({
+ appInitialStates,
+ }: {
+ appInitialStates?: ApplicationInitialStates[];
+ }) {
+ super();
+ this.appInitialStates = appInitialStates;
+
+ this.attribute_map = {
+ appInitialStates: 'app-initial-states',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(
+ data: Record
+ ): SimulateInitialStates {
+ /* eslint-disable dot-notation */
+ return new SimulateInitialStates({
+ appInitialStates:
+ typeof data['app-initial-states'] !== 'undefined'
+ ? data['app-initial-states'].map(
+ ApplicationInitialStates.from_obj_for_encoding
+ )
+ : undefined,
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
/**
* Request type for simulation endpoint.
*/
@@ -3483,7 +3989,7 @@ export class SimulateRequest extends BaseModel {
public txnGroups: SimulateRequestTransactionGroup[];
/**
- * Allow transactions without signatures to be simulated as if they had correct
+ * Allows transactions without signatures to be simulated as if they had correct
* signatures.
*/
public allowEmptySignatures?: boolean;
@@ -3493,6 +3999,11 @@ export class SimulateRequest extends BaseModel {
*/
public allowMoreLogging?: boolean;
+ /**
+ * Allows access to unnamed resources during simulation.
+ */
+ public allowUnnamedResources?: boolean;
+
/**
* An object that configures simulation execution trace.
*/
@@ -3503,41 +4014,62 @@ export class SimulateRequest extends BaseModel {
*/
public extraOpcodeBudget?: number | bigint;
+ /**
+ * If provided, specifies the round preceding the simulation. State changes through
+ * this round will be used to run this simulation. Usually only the 4 most recent
+ * rounds will be available (controlled by the node config value MaxAcctLookback).
+ * If not specified, defaults to the latest available round.
+ */
+ public round?: number | bigint;
+
/**
* Creates a new `SimulateRequest` object.
* @param txnGroups - The transaction groups to simulate.
- * @param allowEmptySignatures - Allow transactions without signatures to be simulated as if they had correct
+ * @param allowEmptySignatures - Allows transactions without signatures to be simulated as if they had correct
* signatures.
* @param allowMoreLogging - Lifts limits on log opcode usage during simulation.
+ * @param allowUnnamedResources - Allows access to unnamed resources during simulation.
* @param execTraceConfig - An object that configures simulation execution trace.
* @param extraOpcodeBudget - Applies extra opcode budget during simulation for each transaction group.
+ * @param round - If provided, specifies the round preceding the simulation. State changes through
+ * this round will be used to run this simulation. Usually only the 4 most recent
+ * rounds will be available (controlled by the node config value MaxAcctLookback).
+ * If not specified, defaults to the latest available round.
*/
constructor({
txnGroups,
allowEmptySignatures,
allowMoreLogging,
+ allowUnnamedResources,
execTraceConfig,
extraOpcodeBudget,
+ round,
}: {
txnGroups: SimulateRequestTransactionGroup[];
allowEmptySignatures?: boolean;
allowMoreLogging?: boolean;
+ allowUnnamedResources?: boolean;
execTraceConfig?: SimulateTraceConfig;
extraOpcodeBudget?: number | bigint;
+ round?: number | bigint;
}) {
super();
this.txnGroups = txnGroups;
this.allowEmptySignatures = allowEmptySignatures;
this.allowMoreLogging = allowMoreLogging;
+ this.allowUnnamedResources = allowUnnamedResources;
this.execTraceConfig = execTraceConfig;
this.extraOpcodeBudget = extraOpcodeBudget;
+ this.round = round;
this.attribute_map = {
txnGroups: 'txn-groups',
allowEmptySignatures: 'allow-empty-signatures',
allowMoreLogging: 'allow-more-logging',
+ allowUnnamedResources: 'allow-unnamed-resources',
execTraceConfig: 'exec-trace-config',
extraOpcodeBudget: 'extra-opcode-budget',
+ round: 'round',
};
}
@@ -3554,11 +4086,13 @@ export class SimulateRequest extends BaseModel {
),
allowEmptySignatures: data['allow-empty-signatures'],
allowMoreLogging: data['allow-more-logging'],
+ allowUnnamedResources: data['allow-unnamed-resources'],
execTraceConfig:
typeof data['exec-trace-config'] !== 'undefined'
? SimulateTraceConfig.from_obj_for_encoding(data['exec-trace-config'])
: undefined,
extraOpcodeBudget: data['extra-opcode-budget'],
+ round: data['round'],
});
/* eslint-enable dot-notation */
}
@@ -3634,6 +4168,11 @@ export class SimulateResponse extends BaseModel {
*/
public execTraceConfig?: SimulateTraceConfig;
+ /**
+ * Initial states of resources that were accessed during simulation.
+ */
+ public initialStates?: SimulateInitialStates;
+
/**
* Creates a new `SimulateResponse` object.
* @param lastRound - The round immediately preceding this simulation. State changes through this
@@ -3644,6 +4183,7 @@ export class SimulateResponse extends BaseModel {
* parameters is present, then evaluation parameters may differ from standard
* evaluation in certain ways.
* @param execTraceConfig - An object that configures simulation execution trace.
+ * @param initialStates - Initial states of resources that were accessed during simulation.
*/
constructor({
lastRound,
@@ -3651,12 +4191,14 @@ export class SimulateResponse extends BaseModel {
version,
evalOverrides,
execTraceConfig,
+ initialStates,
}: {
lastRound: number | bigint;
txnGroups: SimulateTransactionGroupResult[];
version: number | bigint;
evalOverrides?: SimulationEvalOverrides;
execTraceConfig?: SimulateTraceConfig;
+ initialStates?: SimulateInitialStates;
}) {
super();
this.lastRound = lastRound;
@@ -3664,6 +4206,7 @@ export class SimulateResponse extends BaseModel {
this.version = version;
this.evalOverrides = evalOverrides;
this.execTraceConfig = execTraceConfig;
+ this.initialStates = initialStates;
this.attribute_map = {
lastRound: 'last-round',
@@ -3671,6 +4214,7 @@ export class SimulateResponse extends BaseModel {
version: 'version',
evalOverrides: 'eval-overrides',
execTraceConfig: 'exec-trace-config',
+ initialStates: 'initial-states',
};
}
@@ -3703,6 +4247,10 @@ export class SimulateResponse extends BaseModel {
typeof data['exec-trace-config'] !== 'undefined'
? SimulateTraceConfig.from_obj_for_encoding(data['exec-trace-config'])
: undefined,
+ initialStates:
+ typeof data['initial-states'] !== 'undefined'
+ ? SimulateInitialStates.from_obj_for_encoding(data['initial-states'])
+ : undefined,
});
/* eslint-enable dot-notation */
}
@@ -3729,6 +4277,12 @@ export class SimulateTraceConfig extends BaseModel {
*/
public stackChange?: boolean;
+ /**
+ * A boolean option enabling returning application state changes (global, local,
+ * and box changes) with the execution trace during simulation.
+ */
+ public stateChange?: boolean;
+
/**
* Creates a new `SimulateTraceConfig` object.
* @param enable - A boolean option for opting in execution trace features simulation endpoint.
@@ -3736,25 +4290,31 @@ export class SimulateTraceConfig extends BaseModel {
* trace during simulation.
* @param stackChange - A boolean option enabling returning stack changes together with execution trace
* during simulation.
+ * @param stateChange - A boolean option enabling returning application state changes (global, local,
+ * and box changes) with the execution trace during simulation.
*/
constructor({
enable,
scratchChange,
stackChange,
+ stateChange,
}: {
enable?: boolean;
scratchChange?: boolean;
stackChange?: boolean;
+ stateChange?: boolean;
}) {
super();
this.enable = enable;
this.scratchChange = scratchChange;
this.stackChange = stackChange;
+ this.stateChange = stateChange;
this.attribute_map = {
enable: 'enable',
scratchChange: 'scratch-change',
stackChange: 'stack-change',
+ stateChange: 'state-change',
};
}
@@ -3765,6 +4325,7 @@ export class SimulateTraceConfig extends BaseModel {
enable: data['enable'],
scratchChange: data['scratch-change'],
stackChange: data['stack-change'],
+ stateChange: data['state-change'],
});
/* eslint-enable dot-notation */
}
@@ -3803,6 +4364,19 @@ export class SimulateTransactionGroupResult extends BaseModel {
*/
public failureMessage?: string;
+ /**
+ * These are resources that were accessed by this group that would normally have
+ * caused failure, but were allowed in simulation. Depending on where this object
+ * is in the response, the unnamed resources it contains may or may not qualify for
+ * group resource sharing. If this is a field in SimulateTransactionGroupResult,
+ * the resources do qualify, but if this is a field in SimulateTransactionResult,
+ * they do not qualify. In order to make this group valid for actual submission,
+ * resources that qualify for group sharing can be made available by any
+ * transaction of the group; otherwise, resources must be placed in the same
+ * transaction which accessed them.
+ */
+ public unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed;
+
/**
* Creates a new `SimulateTransactionGroupResult` object.
* @param txnResults - Simulation result for individual transactions
@@ -3814,6 +4388,15 @@ export class SimulateTransactionGroupResult extends BaseModel {
* indicate deeper inner transactions.
* @param failureMessage - If present, indicates that the transaction group failed and specifies why that
* happened
+ * @param unnamedResourcesAccessed - These are resources that were accessed by this group that would normally have
+ * caused failure, but were allowed in simulation. Depending on where this object
+ * is in the response, the unnamed resources it contains may or may not qualify for
+ * group resource sharing. If this is a field in SimulateTransactionGroupResult,
+ * the resources do qualify, but if this is a field in SimulateTransactionResult,
+ * they do not qualify. In order to make this group valid for actual submission,
+ * resources that qualify for group sharing can be made available by any
+ * transaction of the group; otherwise, resources must be placed in the same
+ * transaction which accessed them.
*/
constructor({
txnResults,
@@ -3821,12 +4404,14 @@ export class SimulateTransactionGroupResult extends BaseModel {
appBudgetConsumed,
failedAt,
failureMessage,
+ unnamedResourcesAccessed,
}: {
txnResults: SimulateTransactionResult[];
appBudgetAdded?: number | bigint;
appBudgetConsumed?: number | bigint;
failedAt?: (number | bigint)[];
failureMessage?: string;
+ unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed;
}) {
super();
this.txnResults = txnResults;
@@ -3834,6 +4419,7 @@ export class SimulateTransactionGroupResult extends BaseModel {
this.appBudgetConsumed = appBudgetConsumed;
this.failedAt = failedAt;
this.failureMessage = failureMessage;
+ this.unnamedResourcesAccessed = unnamedResourcesAccessed;
this.attribute_map = {
txnResults: 'txn-results',
@@ -3841,6 +4427,7 @@ export class SimulateTransactionGroupResult extends BaseModel {
appBudgetConsumed: 'app-budget-consumed',
failedAt: 'failed-at',
failureMessage: 'failure-message',
+ unnamedResourcesAccessed: 'unnamed-resources-accessed',
};
}
@@ -3861,6 +4448,12 @@ export class SimulateTransactionGroupResult extends BaseModel {
appBudgetConsumed: data['app-budget-consumed'],
failedAt: data['failed-at'],
failureMessage: data['failure-message'],
+ unnamedResourcesAccessed:
+ typeof data['unnamed-resources-accessed'] !== 'undefined'
+ ? SimulateUnnamedResourcesAccessed.from_obj_for_encoding(
+ data['unnamed-resources-accessed']
+ )
+ : undefined,
});
/* eslint-enable dot-notation */
}
@@ -3893,6 +4486,19 @@ export class SimulateTransactionResult extends BaseModel {
*/
public logicSigBudgetConsumed?: number | bigint;
+ /**
+ * These are resources that were accessed by this group that would normally have
+ * caused failure, but were allowed in simulation. Depending on where this object
+ * is in the response, the unnamed resources it contains may or may not qualify for
+ * group resource sharing. If this is a field in SimulateTransactionGroupResult,
+ * the resources do qualify, but if this is a field in SimulateTransactionResult,
+ * they do not qualify. In order to make this group valid for actual submission,
+ * resources that qualify for group sharing can be made available by any
+ * transaction of the group; otherwise, resources must be placed in the same
+ * transaction which accessed them.
+ */
+ public unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed;
+
/**
* Creates a new `SimulateTransactionResult` object.
* @param txnResult - Details about a pending transaction. If the transaction was recently confirmed,
@@ -3902,29 +4508,42 @@ export class SimulateTransactionResult extends BaseModel {
* @param execTrace - The execution trace of calling an app or a logic sig, containing the inner app
* call trace in a recursive way.
* @param logicSigBudgetConsumed - Budget used during execution of a logic sig transaction.
+ * @param unnamedResourcesAccessed - These are resources that were accessed by this group that would normally have
+ * caused failure, but were allowed in simulation. Depending on where this object
+ * is in the response, the unnamed resources it contains may or may not qualify for
+ * group resource sharing. If this is a field in SimulateTransactionGroupResult,
+ * the resources do qualify, but if this is a field in SimulateTransactionResult,
+ * they do not qualify. In order to make this group valid for actual submission,
+ * resources that qualify for group sharing can be made available by any
+ * transaction of the group; otherwise, resources must be placed in the same
+ * transaction which accessed them.
*/
constructor({
txnResult,
appBudgetConsumed,
execTrace,
logicSigBudgetConsumed,
+ unnamedResourcesAccessed,
}: {
txnResult: PendingTransactionResponse;
appBudgetConsumed?: number | bigint;
execTrace?: SimulationTransactionExecTrace;
logicSigBudgetConsumed?: number | bigint;
+ unnamedResourcesAccessed?: SimulateUnnamedResourcesAccessed;
}) {
super();
this.txnResult = txnResult;
this.appBudgetConsumed = appBudgetConsumed;
this.execTrace = execTrace;
this.logicSigBudgetConsumed = logicSigBudgetConsumed;
+ this.unnamedResourcesAccessed = unnamedResourcesAccessed;
this.attribute_map = {
txnResult: 'txn-result',
appBudgetConsumed: 'app-budget-consumed',
execTrace: 'exec-trace',
logicSigBudgetConsumed: 'logic-sig-budget-consumed',
+ unnamedResourcesAccessed: 'unnamed-resources-accessed',
};
}
@@ -3949,6 +4568,147 @@ export class SimulateTransactionResult extends BaseModel {
)
: undefined,
logicSigBudgetConsumed: data['logic-sig-budget-consumed'],
+ unnamedResourcesAccessed:
+ typeof data['unnamed-resources-accessed'] !== 'undefined'
+ ? SimulateUnnamedResourcesAccessed.from_obj_for_encoding(
+ data['unnamed-resources-accessed']
+ )
+ : undefined,
+ });
+ /* eslint-enable dot-notation */
+ }
+}
+
+/**
+ * These are resources that were accessed by this group that would normally have
+ * caused failure, but were allowed in simulation. Depending on where this object
+ * is in the response, the unnamed resources it contains may or may not qualify for
+ * group resource sharing. If this is a field in SimulateTransactionGroupResult,
+ * the resources do qualify, but if this is a field in SimulateTransactionResult,
+ * they do not qualify. In order to make this group valid for actual submission,
+ * resources that qualify for group sharing can be made available by any
+ * transaction of the group; otherwise, resources must be placed in the same
+ * transaction which accessed them.
+ */
+export class SimulateUnnamedResourcesAccessed extends BaseModel {
+ /**
+ * The unnamed accounts that were referenced. The order of this array is arbitrary.
+ */
+ public accounts?: string[];
+
+ /**
+ * The unnamed application local states that were referenced. The order of this
+ * array is arbitrary.
+ */
+ public appLocals?: ApplicationLocalReference[];
+
+ /**
+ * The unnamed applications that were referenced. The order of this array is
+ * arbitrary.
+ */
+ public apps?: (number | bigint)[];
+
+ /**
+ * The unnamed asset holdings that were referenced. The order of this array is
+ * arbitrary.
+ */
+ public assetHoldings?: AssetHoldingReference[];
+
+ /**
+ * The unnamed assets that were referenced. The order of this array is arbitrary.
+ */
+ public assets?: (number | bigint)[];
+
+ /**
+ * The unnamed boxes that were referenced. The order of this array is arbitrary.
+ */
+ public boxes?: BoxReference[];
+
+ /**
+ * The number of extra box references used to increase the IO budget. This is in
+ * addition to the references defined in the input transaction group and any
+ * referenced to unnamed boxes.
+ */
+ public extraBoxRefs?: number | bigint;
+
+ /**
+ * Creates a new `SimulateUnnamedResourcesAccessed` object.
+ * @param accounts - The unnamed accounts that were referenced. The order of this array is arbitrary.
+ * @param appLocals - The unnamed application local states that were referenced. The order of this
+ * array is arbitrary.
+ * @param apps - The unnamed applications that were referenced. The order of this array is
+ * arbitrary.
+ * @param assetHoldings - The unnamed asset holdings that were referenced. The order of this array is
+ * arbitrary.
+ * @param assets - The unnamed assets that were referenced. The order of this array is arbitrary.
+ * @param boxes - The unnamed boxes that were referenced. The order of this array is arbitrary.
+ * @param extraBoxRefs - The number of extra box references used to increase the IO budget. This is in
+ * addition to the references defined in the input transaction group and any
+ * referenced to unnamed boxes.
+ */
+ constructor({
+ accounts,
+ appLocals,
+ apps,
+ assetHoldings,
+ assets,
+ boxes,
+ extraBoxRefs,
+ }: {
+ accounts?: string[];
+ appLocals?: ApplicationLocalReference[];
+ apps?: (number | bigint)[];
+ assetHoldings?: AssetHoldingReference[];
+ assets?: (number | bigint)[];
+ boxes?: BoxReference[];
+ extraBoxRefs?: number | bigint;
+ }) {
+ super();
+ this.accounts = accounts;
+ this.appLocals = appLocals;
+ this.apps = apps;
+ this.assetHoldings = assetHoldings;
+ this.assets = assets;
+ this.boxes = boxes;
+ this.extraBoxRefs = extraBoxRefs;
+
+ this.attribute_map = {
+ accounts: 'accounts',
+ appLocals: 'app-locals',
+ apps: 'apps',
+ assetHoldings: 'asset-holdings',
+ assets: 'assets',
+ boxes: 'boxes',
+ extraBoxRefs: 'extra-box-refs',
+ };
+ }
+
+ // eslint-disable-next-line camelcase
+ static from_obj_for_encoding(
+ data: Record
+ ): SimulateUnnamedResourcesAccessed {
+ /* eslint-disable dot-notation */
+ return new SimulateUnnamedResourcesAccessed({
+ accounts: data['accounts'],
+ appLocals:
+ typeof data['app-locals'] !== 'undefined'
+ ? data['app-locals'].map(
+ ApplicationLocalReference.from_obj_for_encoding
+ )
+ : undefined,
+ apps: data['apps'],
+ assetHoldings:
+ typeof data['asset-holdings'] !== 'undefined'
+ ? data['asset-holdings'].map(
+ AssetHoldingReference.from_obj_for_encoding
+ )
+ : undefined,
+ assets: data['assets'],
+ boxes:
+ typeof data['boxes'] !== 'undefined'
+ ? data['boxes'].map(BoxReference.from_obj_for_encoding)
+ : undefined,
+ extraBoxRefs: data['extra-box-refs'],
});
/* eslint-enable dot-notation */
}
@@ -3966,6 +4726,11 @@ export class SimulationEvalOverrides extends BaseModel {
*/
public allowEmptySignatures?: boolean;
+ /**
+ * If true, allows access to unnamed resources during simulation.
+ */
+ public allowUnnamedResources?: boolean;
+
/**
* The extra opcode budget added to each transaction group during simulation
*/
@@ -3985,29 +4750,34 @@ export class SimulationEvalOverrides extends BaseModel {
* Creates a new `SimulationEvalOverrides` object.
* @param allowEmptySignatures - If true, transactions without signatures are allowed and simulated as if they
* were properly signed.
+ * @param allowUnnamedResources - If true, allows access to unnamed resources during simulation.
* @param extraOpcodeBudget - The extra opcode budget added to each transaction group during simulation
* @param maxLogCalls - The maximum log calls one can make during simulation
* @param maxLogSize - The maximum byte number to log during simulation
*/
constructor({
allowEmptySignatures,
+ allowUnnamedResources,
extraOpcodeBudget,
maxLogCalls,
maxLogSize,
}: {
allowEmptySignatures?: boolean;
+ allowUnnamedResources?: boolean;
extraOpcodeBudget?: number | bigint;
maxLogCalls?: number | bigint;
maxLogSize?: number | bigint;
}) {
super();
this.allowEmptySignatures = allowEmptySignatures;
+ this.allowUnnamedResources = allowUnnamedResources;
this.extraOpcodeBudget = extraOpcodeBudget;
this.maxLogCalls = maxLogCalls;
this.maxLogSize = maxLogSize;
this.attribute_map = {
allowEmptySignatures: 'allow-empty-signatures',
+ allowUnnamedResources: 'allow-unnamed-resources',
extraOpcodeBudget: 'extra-opcode-budget',
maxLogCalls: 'max-log-calls',
maxLogSize: 'max-log-size',
@@ -4021,6 +4791,7 @@ export class SimulationEvalOverrides extends BaseModel {
/* eslint-disable dot-notation */
return new SimulationEvalOverrides({
allowEmptySignatures: data['allow-empty-signatures'],
+ allowUnnamedResources: data['allow-unnamed-resources'],
extraOpcodeBudget: data['extra-opcode-budget'],
maxLogCalls: data['max-log-calls'],
maxLogSize: data['max-log-size'],
@@ -4058,6 +4829,11 @@ export class SimulationOpcodeTraceUnit extends BaseModel {
*/
public stackPopCount?: number | bigint;
+ /**
+ * The operations against the current application's states.
+ */
+ public stateChanges?: ApplicationStateOperation[];
+
/**
* Creates a new `SimulationOpcodeTraceUnit` object.
* @param pc - The program counter of the current opcode being evaluated.
@@ -4065,6 +4841,7 @@ export class SimulationOpcodeTraceUnit extends BaseModel {
* @param spawnedInners - The indexes of the traces for inner transactions spawned by this opcode, if any.
* @param stackAdditions - The values added by this opcode to the stack.
* @param stackPopCount - The number of deleted stack values by this opcode.
+ * @param stateChanges - The operations against the current application's states.
*/
constructor({
pc,
@@ -4072,12 +4849,14 @@ export class SimulationOpcodeTraceUnit extends BaseModel {
spawnedInners,
stackAdditions,
stackPopCount,
+ stateChanges,
}: {
pc: number | bigint;
scratchChanges?: ScratchChange[];
spawnedInners?: (number | bigint)[];
stackAdditions?: AvmValue[];
stackPopCount?: number | bigint;
+ stateChanges?: ApplicationStateOperation[];
}) {
super();
this.pc = pc;
@@ -4085,6 +4864,7 @@ export class SimulationOpcodeTraceUnit extends BaseModel {
this.spawnedInners = spawnedInners;
this.stackAdditions = stackAdditions;
this.stackPopCount = stackPopCount;
+ this.stateChanges = stateChanges;
this.attribute_map = {
pc: 'pc',
@@ -4092,6 +4872,7 @@ export class SimulationOpcodeTraceUnit extends BaseModel {
spawnedInners: 'spawned-inners',
stackAdditions: 'stack-additions',
stackPopCount: 'stack-pop-count',
+ stateChanges: 'state-changes',
};
}
@@ -4114,6 +4895,12 @@ export class SimulationOpcodeTraceUnit extends BaseModel {
? data['stack-additions'].map(AvmValue.from_obj_for_encoding)
: undefined,
stackPopCount: data['stack-pop-count'],
+ stateChanges:
+ typeof data['state-changes'] !== 'undefined'
+ ? data['state-changes'].map(
+ ApplicationStateOperation.from_obj_for_encoding
+ )
+ : undefined,
});
/* eslint-enable dot-notation */
}
@@ -4124,11 +4911,21 @@ export class SimulationOpcodeTraceUnit extends BaseModel {
* call trace in a recursive way.
*/
export class SimulationTransactionExecTrace extends BaseModel {
+ /**
+ * SHA512_256 hash digest of the approval program executed in transaction.
+ */
+ public approvalProgramHash?: Uint8Array;
+
/**
* Program trace that contains a trace of opcode effects in an approval program.
*/
public approvalProgramTrace?: SimulationOpcodeTraceUnit[];
+ /**
+ * SHA512_256 hash digest of the clear state program executed in transaction.
+ */
+ public clearStateProgramHash?: Uint8Array;
+
/**
* Program trace that contains a trace of opcode effects in a clear state program.
*/
@@ -4140,6 +4937,11 @@ export class SimulationTransactionExecTrace extends BaseModel {
*/
public innerTrace?: SimulationTransactionExecTrace[];
+ /**
+ * SHA512_256 hash digest of the logic sig executed in transaction.
+ */
+ public logicSigHash?: Uint8Array;
+
/**
* Program trace that contains a trace of opcode effects in a logic sig.
*/
@@ -4147,33 +4949,57 @@ export class SimulationTransactionExecTrace extends BaseModel {
/**
* Creates a new `SimulationTransactionExecTrace` object.
+ * @param approvalProgramHash - SHA512_256 hash digest of the approval program executed in transaction.
* @param approvalProgramTrace - Program trace that contains a trace of opcode effects in an approval program.
+ * @param clearStateProgramHash - SHA512_256 hash digest of the clear state program executed in transaction.
* @param clearStateProgramTrace - Program trace that contains a trace of opcode effects in a clear state program.
* @param innerTrace - An array of SimulationTransactionExecTrace representing the execution trace of
* any inner transactions executed.
+ * @param logicSigHash - SHA512_256 hash digest of the logic sig executed in transaction.
* @param logicSigTrace - Program trace that contains a trace of opcode effects in a logic sig.
*/
constructor({
+ approvalProgramHash,
approvalProgramTrace,
+ clearStateProgramHash,
clearStateProgramTrace,
innerTrace,
+ logicSigHash,
logicSigTrace,
}: {
+ approvalProgramHash?: string | Uint8Array;
approvalProgramTrace?: SimulationOpcodeTraceUnit[];
+ clearStateProgramHash?: string | Uint8Array;
clearStateProgramTrace?: SimulationOpcodeTraceUnit[];
innerTrace?: SimulationTransactionExecTrace[];
+ logicSigHash?: string | Uint8Array;
logicSigTrace?: SimulationOpcodeTraceUnit[];
}) {
super();
+ this.approvalProgramHash =
+ typeof approvalProgramHash === 'string'
+ ? new Uint8Array(Buffer.from(approvalProgramHash, 'base64'))
+ : approvalProgramHash;
this.approvalProgramTrace = approvalProgramTrace;
+ this.clearStateProgramHash =
+ typeof clearStateProgramHash === 'string'
+ ? new Uint8Array(Buffer.from(clearStateProgramHash, 'base64'))
+ : clearStateProgramHash;
this.clearStateProgramTrace = clearStateProgramTrace;
this.innerTrace = innerTrace;
+ this.logicSigHash =
+ typeof logicSigHash === 'string'
+ ? new Uint8Array(Buffer.from(logicSigHash, 'base64'))
+ : logicSigHash;
this.logicSigTrace = logicSigTrace;
this.attribute_map = {
+ approvalProgramHash: 'approval-program-hash',
approvalProgramTrace: 'approval-program-trace',
+ clearStateProgramHash: 'clear-state-program-hash',
clearStateProgramTrace: 'clear-state-program-trace',
innerTrace: 'inner-trace',
+ logicSigHash: 'logic-sig-hash',
logicSigTrace: 'logic-sig-trace',
};
}
@@ -4184,12 +5010,14 @@ export class SimulationTransactionExecTrace extends BaseModel {
): SimulationTransactionExecTrace {
/* eslint-disable dot-notation */
return new SimulationTransactionExecTrace({
+ approvalProgramHash: data['approval-program-hash'],
approvalProgramTrace:
typeof data['approval-program-trace'] !== 'undefined'
? data['approval-program-trace'].map(
SimulationOpcodeTraceUnit.from_obj_for_encoding
)
: undefined,
+ clearStateProgramHash: data['clear-state-program-hash'],
clearStateProgramTrace:
typeof data['clear-state-program-trace'] !== 'undefined'
? data['clear-state-program-trace'].map(
@@ -4202,6 +5030,7 @@ export class SimulationTransactionExecTrace extends BaseModel {
SimulationTransactionExecTrace.from_obj_for_encoding
)
: undefined,
+ logicSigHash: data['logic-sig-hash'],
logicSigTrace:
typeof data['logic-sig-trace'] !== 'undefined'
? data['logic-sig-trace'].map(
diff --git a/src/signer.ts b/src/signer.ts
index 4e9ef16aa..80e711234 100644
--- a/src/signer.ts
+++ b/src/signer.ts
@@ -73,7 +73,11 @@ export function makeMultiSigAccountTransactionSigner(
partialSigs.push(blob);
}
- signed.push(mergeMultisigTransactions(partialSigs));
+ if (partialSigs.length > 1) {
+ signed.push(mergeMultisigTransactions(partialSigs));
+ } else {
+ signed.push(partialSigs[0]);
+ }
}
return Promise.resolve(signed);
diff --git a/tests/10.ABI.ts b/tests/10.ABI.ts
index d8a8600af..fff072f10 100644
--- a/tests/10.ABI.ts
+++ b/tests/10.ABI.ts
@@ -1,5 +1,15 @@
/* eslint-env mocha */
import assert from 'assert';
+import {
+ ABIMethod,
+ AtomicTransactionComposer,
+ AtomicTransactionComposerStatus,
+ MultisigMetadata,
+ generateAccount,
+ makeBasicAccountTransactionSigner,
+ makeMultiSigAccountTransactionSigner,
+ makePaymentTxnWithSuggestedParams,
+} from '../src';
import {
ABIAddressType,
ABIArrayDynamicType,
@@ -8,18 +18,11 @@ import {
ABIByteType,
ABIStringType,
ABITupleType,
+ ABIType,
ABIUfixedType,
ABIUintType,
- ABIType,
ABIValue,
} from '../src/abi/abi_type';
-import {
- AtomicTransactionComposer,
- ABIMethod,
- makeBasicAccountTransactionSigner,
- generateAccount,
- AtomicTransactionComposerStatus,
-} from '../src';
import { decodeAddress } from '../src/encoding/address';
describe('ABI type checking', () => {
@@ -534,4 +537,41 @@ describe('ABI encoding', () => {
assert.deepStrictEqual(txn.appAccounts?.length, 1);
assert.deepStrictEqual(txn.appAccounts[0], decodeAddress(foreignAcct));
});
+
+ it('should accept at least one signature in the multisig', () => {
+ const account1 = generateAccount();
+ const account2 = generateAccount();
+
+ // Create a multisig signer
+ const msig: MultisigMetadata = {
+ version: 1,
+ threshold: 1,
+ addrs: [account1.addr, account2.addr],
+ };
+ const sks: Uint8Array[] = [account1.sk];
+ const signer = makeMultiSigAccountTransactionSigner(msig, sks);
+
+ // Create a transaction
+ const suggestedParams = {
+ genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=',
+ genesisID: '',
+ firstRound: 0,
+ lastRound: 1000,
+ fee: 1000,
+ flatFee: true,
+ };
+ const actualTxn = makePaymentTxnWithSuggestedParams(
+ account1.addr,
+ account2.addr,
+ 1000,
+ undefined,
+ undefined,
+ suggestedParams
+ );
+
+ // A multisig with 1 signature should be accepted
+ signer([actualTxn], [0]).then((signedTxns) => {
+ assert.deepStrictEqual(signedTxns.length, 1);
+ });
+ });
});
diff --git a/tests/cucumber/docker/Dockerfile b/tests/cucumber/docker/Dockerfile
index 6ed2a1256..4ef89a836 100644
--- a/tests/cucumber/docker/Dockerfile
+++ b/tests/cucumber/docker/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:bionic
+FROM ubuntu:focal
# install wget, gnupg2, make
RUN apt-get update -qqy \
@@ -12,7 +12,7 @@ RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key
&& apt-get -qqy --no-install-recommends install google-chrome-stable firefox
# install node
-RUN wget -q -O - https://deb.nodesource.com/setup_16.x | bash \
+RUN wget -q -O - https://deb.nodesource.com/setup_18.x | bash \
&& apt-get -qqy --no-install-recommends install nodejs \
&& echo "node version: $(node --version)" \
&& echo "npm version: $(npm --version)"
diff --git a/tests/cucumber/integration.tags b/tests/cucumber/integration.tags
index fc6d7852b..eefd3bdd0 100644
--- a/tests/cucumber/integration.tags
+++ b/tests/cucumber/integration.tags
@@ -17,3 +17,4 @@
@simulate.lift_log_limits
@simulate.extra_opcode_budget
@simulate.exec_trace_with_stack_scratch
+@simulate.exec_trace_with_state_change_and_hash
diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js
index 21d2ac3a0..f54f2c9ef 100644
--- a/tests/cucumber/steps/steps.js
+++ b/tests/cucumber/steps/steps.js
@@ -3028,6 +3028,8 @@ module.exports = function getSteps(options) {
return algosdk.OnApplicationComplete.NoOpOC;
case 'update':
return algosdk.OnApplicationComplete.UpdateApplicationOC;
+ case 'create-and-optin':
+ return algosdk.OnApplicationComplete.OptInOC;
case 'optin':
return algosdk.OnApplicationComplete.OptInOC;
case 'delete':
@@ -3369,7 +3371,10 @@ module.exports = function getSteps(options) {
extraPages,
boxesCommaSeparatedString
) {
- if (operationString === 'create') {
+ if (
+ operationString === 'create' ||
+ operationString === 'create-and-optin'
+ ) {
this.currentApplicationIndex = 0;
}
@@ -3855,7 +3860,8 @@ module.exports = function getSteps(options) {
localBytes,
localInts,
extraPages,
- note
+ note,
+ boxesCommaSeparatedString
) {
// open and load in approval program
let approvalProgramBytes;
@@ -3910,6 +3916,11 @@ module.exports = function getSteps(options) {
methodArgs.push(typeToDecode.decode(encodedArg));
}
+ let boxes;
+ if (boxesCommaSeparatedString !== '') {
+ boxes = splitAndProcessBoxReferences(boxesCommaSeparatedString);
+ }
+
this.composer.addMethodCall({
appID: this.currentApplicationIndex,
method: this.method,
@@ -3925,6 +3936,7 @@ module.exports = function getSteps(options) {
numLocalByteSlices: localBytes,
extraPages,
note,
+ boxes,
signer: this.transactionSigner,
});
}
@@ -3942,6 +3954,8 @@ module.exports = function getSteps(options) {
undefined,
undefined,
undefined,
+ undefined,
+ undefined,
undefined
);
}
@@ -3960,11 +3974,33 @@ module.exports = function getSteps(options) {
undefined,
undefined,
undefined,
+ undefined,
+ undefined,
undefined
);
}
);
+ When(
+ 'I add a method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments, boxes {string}.',
+ async function (onComplete, boxesCommaSeparatedString) {
+ await addMethodCallToComposer.call(
+ this,
+ this.transientAccount.addr,
+ onComplete,
+ '',
+ '',
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ boxesCommaSeparatedString
+ );
+ }
+ );
+
When(
'I add a method call with the transient account, the current application, suggested params, on complete {string}, current transaction signer, current method arguments, approval-program {string}, clear-program {string}, global-bytes {int}, global-ints {int}, local-bytes {int}, local-ints {int}, extra-pages {int}.',
async function (
@@ -3987,7 +4023,9 @@ module.exports = function getSteps(options) {
parseInt(globalInts, 10),
parseInt(localBytes, 10),
parseInt(localInts, 10),
- parseInt(extraPages, 10)
+ parseInt(extraPages, 10),
+ undefined,
+ undefined
);
}
);
@@ -4014,7 +4052,9 @@ module.exports = function getSteps(options) {
parseInt(globalInts, 10),
parseInt(localBytes, 10),
parseInt(localInts, 10),
- parseInt(extraPages, 10)
+ parseInt(extraPages, 10),
+ undefined,
+ undefined
);
}
);
@@ -4033,6 +4073,8 @@ module.exports = function getSteps(options) {
undefined,
undefined,
undefined,
+ undefined,
+ undefined,
undefined
);
}
@@ -4052,6 +4094,8 @@ module.exports = function getSteps(options) {
undefined,
undefined,
undefined,
+ undefined,
+ undefined,
undefined
);
}
@@ -4076,7 +4120,8 @@ module.exports = function getSteps(options) {
undefined,
undefined,
undefined,
- nonce
+ nonce,
+ undefined
);
}
);
@@ -4718,9 +4763,9 @@ module.exports = function getSteps(options) {
Then(
'the simulation should succeed without any failure message',
- async function () {
+ function () {
for (const txnGroup of this.simulateResponse.txnGroups) {
- assert.deepStrictEqual(undefined, txnGroup.failedMessage);
+ assert.equal(txnGroup.failureMessage, undefined);
}
}
);
@@ -4827,11 +4872,37 @@ module.exports = function getSteps(options) {
enable: true,
scratchChange: optionList.includes('scratch'),
stackChange: optionList.includes('stack'),
+ stateChange: optionList.includes('state'),
}
);
}
);
+ function avmValueCheck(expectedStringLiteral, actualAvmValue) {
+ const [expectedAvmType, expectedValue] = expectedStringLiteral.split(':');
+
+ if (expectedAvmType === 'uint64') {
+ assert.equal(actualAvmValue.type, 2);
+ if (expectedValue === 0) {
+ assert.equal(actualAvmValue.uint, undefined);
+ } else {
+ assert.equal(actualAvmValue.uint, BigInt(expectedValue));
+ }
+ } else if (expectedAvmType === 'bytes') {
+ assert.equal(actualAvmValue.type, 1);
+ if (expectedValue.length === 0) {
+ assert.equal(actualAvmValue.bytes, undefined);
+ } else {
+ assert.deepStrictEqual(
+ actualAvmValue.bytes,
+ makeUint8Array(Buffer.from(expectedValue, 'base64'))
+ );
+ }
+ } else {
+ assert.fail('expectedAvmType should be either uint64 or bytes');
+ }
+ }
+
Then(
'{int}th unit in the {string} trace at txn-groups path {string} should add value {string} to stack, pop {int} values from stack, write value {string} to scratch slot {string}.',
async function (
@@ -4873,25 +4944,6 @@ module.exports = function getSteps(options) {
return changeUnit;
};
- const avmValueCheck = (stringLiteral, avmValue) => {
- const [avmType, value] = stringLiteral.split(':');
-
- if (avmType === 'uint64') {
- assert.equal(avmValue.type, 2);
- assert.ok(avmValue.uint);
- assert.equal(avmValue.uint, BigInt(value));
- } else if (avmType === 'bytes') {
- assert.equal(avmValue.type, 1);
- assert.ok(avmValue.bytes);
- assert.deepEqual(
- avmValue.bytes,
- makeUint8Array(Buffer.from(value, 'base64'))
- );
- } else {
- assert.fail('avmType should be either uint64 or bytes');
- }
- };
-
assert.ok(this.simulateResponse);
const changeUnit = unitFinder(txnGroupPath, traceType, unitIndex);
@@ -4935,6 +4987,209 @@ module.exports = function getSteps(options) {
}
);
+ Then(
+ 'the current application initial {string} state should be empty.',
+ function (stateType) {
+ assert.ok(this.simulateResponse.initialStates);
+ assert.ok(this.simulateResponse.initialStates.appInitialStates);
+ let initialAppState = null;
+ let found = false;
+ for (const entry of this.simulateResponse.initialStates
+ .appInitialStates) {
+ if (entry.id !== this.currentApplicationIndex) {
+ continue;
+ }
+ initialAppState = entry;
+ found = true;
+ break;
+ }
+ assert.ok(found);
+ if (initialAppState) {
+ switch (stateType) {
+ case 'local':
+ assert.ok(!initialAppState.appLocals);
+ break;
+ case 'global':
+ assert.ok(!initialAppState.appGlobals);
+ break;
+ case 'box':
+ assert.ok(!initialAppState.appBoxes);
+ break;
+ default:
+ assert.fail('state type can only be one of local/global/box');
+ }
+ }
+ }
+ );
+
+ Then(
+ 'the current application initial {string} state should contain {string} with value {string}.',
+ function (stateType, keyStr, valueStr) {
+ assert.ok(this.simulateResponse.initialStates);
+ assert.ok(this.simulateResponse.initialStates.appInitialStates);
+ let initialAppState = null;
+ for (const entry of this.simulateResponse.initialStates
+ .appInitialStates) {
+ if (entry.id !== this.currentApplicationIndex) {
+ continue;
+ }
+ initialAppState = entry;
+ break;
+ }
+ assert.ok(initialAppState);
+ let kvs = null;
+ switch (stateType) {
+ case 'local':
+ assert.ok(initialAppState.appLocals);
+ assert.strictEqual(initialAppState.appLocals.length, 1);
+ assert.ok(initialAppState.appLocals[0].account);
+ algosdk.decodeAddress(initialAppState.appLocals[0].account);
+ assert.ok(initialAppState.appLocals[0].kvs);
+ kvs = initialAppState.appLocals[0].kvs;
+ break;
+ case 'global':
+ assert.ok(initialAppState.appGlobals);
+ assert.ok(!initialAppState.appGlobals.account);
+ assert.ok(initialAppState.appGlobals.kvs);
+ kvs = initialAppState.appGlobals.kvs;
+ break;
+ case 'box':
+ assert.ok(initialAppState.appBoxes);
+ assert.ok(!initialAppState.appBoxes.account);
+ assert.ok(initialAppState.appBoxes.kvs);
+ kvs = initialAppState.appBoxes.kvs;
+ break;
+ default:
+ assert.fail('state type can only be one of local/global/box');
+ }
+ assert.ok(kvs.length > 0);
+
+ const binaryKey = Buffer.from(keyStr);
+
+ let actualValue = null;
+ for (const kv of kvs) {
+ if (binaryKey.equals(Buffer.from(kv.key))) {
+ actualValue = kv.value;
+ break;
+ }
+ }
+ assert.ok(actualValue);
+ avmValueCheck(valueStr, actualValue);
+ }
+ );
+
+ Then(
+ '{int}th unit in the {string} trace at txn-groups path {string} should write to {string} state {string} with new value {string}.',
+ function (
+ unitIndex,
+ traceType,
+ txnGroupPath,
+ stateType,
+ stateName,
+ newValue
+ ) {
+ const unitFinder = (txnGroupPathStr, traceTypeStr, unitIndexInt) => {
+ const txnGroupPathSplit = txnGroupPathStr
+ .split(',')
+ .filter((r) => r !== '')
+ .map(Number);
+ assert.ok(txnGroupPathSplit.length > 0);
+
+ let traces = this.simulateResponse.txnGroups[0].txnResults[
+ txnGroupPathSplit[0]
+ ].execTrace;
+ assert.ok(traces);
+
+ for (let i = 1; i < txnGroupPathSplit.length; i++) {
+ traces = traces.innerTrace[txnGroupPathSplit[i]];
+ assert.ok(traces);
+ }
+
+ let trace = traces.approvalProgramTrace;
+ if (traceTypeStr === 'approval') {
+ trace = traces.approvalProgramTrace;
+ } else if (traceTypeStr === 'clearState') {
+ trace = traces.clearStateProgramTrace;
+ } else if (traceTypeStr === 'logic') {
+ trace = traces.logicSigTrace;
+ }
+
+ assert.ok(
+ unitIndexInt < trace.length,
+ `unitIndexInt (${unitIndexInt}) < trace.length (${trace.length})`
+ );
+
+ const changeUnit = trace[unitIndexInt];
+ return changeUnit;
+ };
+
+ assert.ok(this.simulateResponse);
+
+ const changeUnit = unitFinder(txnGroupPath, traceType, unitIndex);
+ assert.ok(changeUnit.stateChanges);
+ assert.strictEqual(changeUnit.stateChanges.length, 1);
+ const stateChange = changeUnit.stateChanges[0];
+
+ if (stateType === 'global') {
+ assert.strictEqual(stateChange.appStateType, 'g');
+ assert.ok(!stateChange.account);
+ } else if (stateType === 'local') {
+ assert.strictEqual(stateChange.appStateType, 'l');
+ assert.ok(stateChange.account);
+ algosdk.decodeAddress(stateChange.account);
+ } else if (stateType === 'box') {
+ assert.strictEqual(stateChange.appStateType, 'b');
+ assert.ok(!stateChange.account);
+ } else {
+ assert.fail('state type can only be one of local/global/box');
+ }
+
+ assert.strictEqual(stateChange.operation, 'w');
+
+ assert.deepStrictEqual(
+ stateChange.key,
+ makeUint8Array(Buffer.from(stateName))
+ );
+ assert.ok(stateChange.newValue);
+ avmValueCheck(newValue, stateChange.newValue);
+ }
+ );
+
+ Then(
+ '{string} hash at txn-groups path {string} should be {string}.',
+ function (traceType, txnGroupPath, b64ProgHash) {
+ const txnGroupPathSplit = txnGroupPath
+ .split(',')
+ .filter((r) => r !== '')
+ .map(Number);
+ assert.ok(txnGroupPathSplit.length > 0);
+
+ let traces = this.simulateResponse.txnGroups[0].txnResults[
+ txnGroupPathSplit[0]
+ ].execTrace;
+ assert.ok(traces);
+
+ for (let i = 1; i < txnGroupPathSplit.length; i++) {
+ traces = traces.innerTrace[txnGroupPathSplit[i]];
+ assert.ok(traces);
+ }
+
+ let hash = traces.approvalProgramHash;
+
+ if (traceType === 'approval') {
+ hash = traces.approvalProgramHash;
+ } else if (traceType === 'clearState') {
+ hash = traces.clearStateProgramHash;
+ } else if (traceType === 'logic') {
+ hash = traces.logicSigHash;
+ }
+ assert.deepStrictEqual(
+ hash,
+ makeUint8Array(Buffer.from(b64ProgHash, 'base64'))
+ );
+ }
+ );
+
When('we make a Ready call', async function () {
await this.v2Client.ready().do();
});
@@ -4996,6 +5251,13 @@ module.exports = function getSteps(options) {
}
);
+ When(
+ 'we make a GetBlockTxids call against block number {int}',
+ async function (round) {
+ await this.v2Client.getBlockTxids(round).do();
+ }
+ );
+
if (!options.ignoreReturn) {
return steps;
}
diff --git a/tests/cucumber/unit.tags b/tests/cucumber/unit.tags
index 252636093..01358a64d 100644
--- a/tests/cucumber/unit.tags
+++ b/tests/cucumber/unit.tags
@@ -6,6 +6,7 @@
@unit.applications.boxes
@unit.atomic_transaction_composer
@unit.blocksummary
+@unit.blocktxids
@unit.client-no-headers
@unit.dryrun
@unit.dryrun.trace.application
@@ -25,6 +26,7 @@
@unit.responses.statedelta.json
@unit.responses.sync
@unit.responses.timestamp
+@unit.responses.txid.json
@unit.responses.txngroupdeltas.json
@unit.sourcemap
@unit.stateproof.paths