diff --git a/examples/indexer.ts b/examples/indexer.ts index d187a28ac..f853400f4 100644 --- a/examples/indexer.ts +++ b/examples/indexer.ts @@ -39,7 +39,7 @@ async function main() { // example: INDEXER_SEARCH_MIN_AMOUNT // example: INDEXER_PAGINATE_RESULTS - let nextToken = ''; + let nextToken: string | undefined = ''; // nextToken will be undefined if we reached the last page while (nextToken !== undefined) { @@ -51,7 +51,7 @@ async function main() { .nextToken(nextToken) .do(); - nextToken = response['next-token']; + nextToken = response.nextToken; const txns = response.transactions; if (txns.length > 0) console.log(`Transaction IDs: ${response.transactions.map((t) => t.id)}`); diff --git a/examples/utils.ts b/examples/utils.ts index 180b26a9a..3bfab61d5 100644 --- a/examples/utils.ts +++ b/examples/utils.ts @@ -54,7 +54,7 @@ export async function indexerWaitForRound( round: number | bigint, maxAttempts: number ) { - let indexerRound = 0; + let indexerRound = BigInt(0); let attempts = 0; for (;;) { diff --git a/src/client/v2/algod/compile.ts b/src/client/v2/algod/compile.ts index c54db003d..083e022f6 100644 --- a/src/client/v2/algod/compile.ts +++ b/src/client/v2/algod/compile.ts @@ -55,7 +55,7 @@ export default class Compile extends JSONRequest< query: this.query, requestHeaders: txHeaders, }); - return res.body; + return this.prepare(res.body); } // eslint-disable-next-line class-methods-use-this diff --git a/src/client/v2/algod/getBlockTxids.ts b/src/client/v2/algod/getBlockTxids.ts index bba9df96c..724525a65 100644 --- a/src/client/v2/algod/getBlockTxids.ts +++ b/src/client/v2/algod/getBlockTxids.ts @@ -1,7 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; +import { BlockTxidsResponse } from './models/types.js'; -export default class GetBlockTxids extends JSONRequest { +export default class GetBlockTxids extends JSONRequest< + BlockTxidsResponse, + Record +> { round: number; constructor(c: HTTPClient, roundNumber: number) { @@ -14,4 +18,11 @@ export default class GetBlockTxids extends JSONRequest { path() { return `/v2/blocks/${this.round}/txids`; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): BlockTxidsResponse { + return BlockTxidsResponse.fromEncodingData( + BlockTxidsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/algod/suggestedParams.ts b/src/client/v2/algod/suggestedParams.ts index cde86875a..c36c224d7 100644 --- a/src/client/v2/algod/suggestedParams.ts +++ b/src/client/v2/algod/suggestedParams.ts @@ -17,6 +17,11 @@ export interface SuggestedParamsFromAlgod extends SuggestedParams { lastValid: bigint; genesisID: string; genesisHash: Uint8Array; + + /** + * ConsensusVersion indicates the consensus protocol version as of the last round. + */ + consensusVersion: string; } /** @@ -40,6 +45,7 @@ export default class SuggestedParamsRequest extends JSONRequest< genesisID: body['genesis-id'], genesisHash: base64ToBytes(body['genesis-hash']), minFee: BigInt(body['min-fee']), + consensusVersion: body['consensus-version'], }; } /* eslint-enable class-methods-use-this */ diff --git a/src/client/v2/indexer/lookupAccountAppLocalStates.ts b/src/client/v2/indexer/lookupAccountAppLocalStates.ts index 71980ad4d..1eb2722e2 100644 --- a/src/client/v2/indexer/lookupAccountAppLocalStates.ts +++ b/src/client/v2/indexer/lookupAccountAppLocalStates.ts @@ -1,8 +1,12 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import { Address } from '../../../encoding/address.js'; +import { ApplicationLocalStatesResponse } from './models/types.js'; -export default class LookupAccountAppLocalStates extends JSONRequest { +export default class LookupAccountAppLocalStates extends JSONRequest< + ApplicationLocalStatesResponse, + Record +> { private account: string | Address; /** @@ -135,4 +139,11 @@ export default class LookupAccountAppLocalStates extends JSONRequest { this.query['application-id'] = index; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): ApplicationLocalStatesResponse { + return ApplicationLocalStatesResponse.fromEncodingData( + ApplicationLocalStatesResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAccountAssets.ts b/src/client/v2/indexer/lookupAccountAssets.ts index 9062a94ca..6b7a9c28b 100644 --- a/src/client/v2/indexer/lookupAccountAssets.ts +++ b/src/client/v2/indexer/lookupAccountAssets.ts @@ -1,8 +1,12 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import { Address } from '../../../encoding/address.js'; +import { AssetHoldingsResponse } from './models/types.js'; -export default class LookupAccountAssets extends JSONRequest { +export default class LookupAccountAssets extends JSONRequest< + AssetHoldingsResponse, + Record +> { private account: string; /** @@ -136,4 +140,11 @@ export default class LookupAccountAssets extends JSONRequest { this.query['asset-id'] = index; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): AssetHoldingsResponse { + return AssetHoldingsResponse.fromEncodingData( + AssetHoldingsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAccountByID.ts b/src/client/v2/indexer/lookupAccountByID.ts index 0eb2e8b89..b9f72a2bf 100644 --- a/src/client/v2/indexer/lookupAccountByID.ts +++ b/src/client/v2/indexer/lookupAccountByID.ts @@ -1,8 +1,12 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import { Address } from '../../../encoding/address.js'; +import { AccountResponse } from './models/types.js'; -export default class LookupAccountByID extends JSONRequest { +export default class LookupAccountByID extends JSONRequest< + AccountResponse, + Record +> { private account: string; /** @@ -104,4 +108,11 @@ export default class LookupAccountByID extends JSONRequest { this.query.exclude = exclude; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): AccountResponse { + return AccountResponse.fromEncodingData( + AccountResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAccountCreatedApplications.ts b/src/client/v2/indexer/lookupAccountCreatedApplications.ts index 635f45c35..605cd3136 100644 --- a/src/client/v2/indexer/lookupAccountCreatedApplications.ts +++ b/src/client/v2/indexer/lookupAccountCreatedApplications.ts @@ -1,8 +1,12 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import { Address } from '../../../encoding/address.js'; +import { ApplicationsResponse } from './models/types.js'; -export default class LookupAccountCreatedApplications extends JSONRequest { +export default class LookupAccountCreatedApplications extends JSONRequest< + ApplicationsResponse, + Record +> { private account: string; /** @@ -136,4 +140,11 @@ export default class LookupAccountCreatedApplications extends JSONRequest { this.query['application-id'] = index; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): ApplicationsResponse { + return ApplicationsResponse.fromEncodingData( + ApplicationsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAccountCreatedAssets.ts b/src/client/v2/indexer/lookupAccountCreatedAssets.ts index 5d668a5d2..52a568da4 100644 --- a/src/client/v2/indexer/lookupAccountCreatedAssets.ts +++ b/src/client/v2/indexer/lookupAccountCreatedAssets.ts @@ -1,8 +1,12 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import { Address } from '../../../encoding/address.js'; +import { AssetsResponse } from './models/types.js'; -export default class LookupAccountCreatedAssets extends JSONRequest { +export default class LookupAccountCreatedAssets extends JSONRequest< + AssetsResponse, + Record +> { private account: string; /** @@ -137,4 +141,11 @@ export default class LookupAccountCreatedAssets extends JSONRequest { this.query['asset-id'] = index; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): AssetsResponse { + return AssetsResponse.fromEncodingData( + AssetsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAccountTransactions.ts b/src/client/v2/indexer/lookupAccountTransactions.ts index 1f3c150ac..d39b7dcfc 100644 --- a/src/client/v2/indexer/lookupAccountTransactions.ts +++ b/src/client/v2/indexer/lookupAccountTransactions.ts @@ -2,6 +2,7 @@ import { bytesToBase64 } from '../../../encoding/binarydata.js'; import { HTTPClient } from '../../client.js'; import JSONRequest from '../jsonrequest.js'; import { Address } from '../../../encoding/address.js'; +import { TransactionsResponse } from './models/types.js'; /** * Accept base64 string or Uint8Array and output base64 string @@ -15,7 +16,10 @@ export function base64StringFunnel(data: Uint8Array | string) { return bytesToBase64(data); } -export default class LookupAccountTransactions extends JSONRequest { +export default class LookupAccountTransactions extends JSONRequest< + TransactionsResponse, + Record +> { private account: string; /** @@ -245,11 +249,12 @@ export default class LookupAccountTransactions extends JSONRequest { * .do(); * ``` * - * @param before - rfc3339 string + * @param before - rfc3339 string or Date object * @category query */ - beforeTime(before: string) { - this.query['before-time'] = before; + beforeTime(before: string | Date) { + this.query['before-time'] = + before instanceof Date ? before.toISOString() : before; return this; } @@ -266,11 +271,12 @@ export default class LookupAccountTransactions extends JSONRequest { * .do(); * ``` * - * @param after - rfc3339 string + * @param after - rfc3339 string or Date object * @category query */ - afterTime(after: string) { - this.query['after-time'] = after; + afterTime(after: string | Date) { + this.query['after-time'] = + after instanceof Date ? after.toISOString() : after; return this; } @@ -388,4 +394,11 @@ export default class LookupAccountTransactions extends JSONRequest { this.query['rekey-to'] = rekeyTo; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): TransactionsResponse { + return TransactionsResponse.fromEncodingData( + TransactionsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupApplicationLogs.ts b/src/client/v2/indexer/lookupApplicationLogs.ts index e2133edfc..eb3c81d94 100644 --- a/src/client/v2/indexer/lookupApplicationLogs.ts +++ b/src/client/v2/indexer/lookupApplicationLogs.ts @@ -1,7 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; +import { ApplicationLogsResponse } from './models/types.js'; -export default class LookupApplicationLogs extends JSONRequest { +export default class LookupApplicationLogs extends JSONRequest< + ApplicationLogsResponse, + Record +> { /** * Returns log messages generated by the passed in application. * @@ -154,4 +158,11 @@ export default class LookupApplicationLogs extends JSONRequest { this.query.txid = txid; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): ApplicationLogsResponse { + return ApplicationLogsResponse.fromEncodingData( + ApplicationLogsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupApplications.ts b/src/client/v2/indexer/lookupApplications.ts index c717e83d1..08e9da26d 100644 --- a/src/client/v2/indexer/lookupApplications.ts +++ b/src/client/v2/indexer/lookupApplications.ts @@ -1,7 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; +import { ApplicationResponse } from './models/types.js'; -export default class LookupApplications extends JSONRequest { +export default class LookupApplications extends JSONRequest< + ApplicationResponse, + Record +> { /** * Returns information about the passed application. * @@ -57,4 +61,11 @@ export default class LookupApplications extends JSONRequest { this.query['include-all'] = value; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): ApplicationResponse { + return ApplicationResponse.fromEncodingData( + ApplicationResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAssetBalances.ts b/src/client/v2/indexer/lookupAssetBalances.ts index 31b73b026..94438c254 100644 --- a/src/client/v2/indexer/lookupAssetBalances.ts +++ b/src/client/v2/indexer/lookupAssetBalances.ts @@ -1,7 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; +import { AssetBalancesResponse } from './models/types.js'; -export default class LookupAssetBalances extends JSONRequest { +export default class LookupAssetBalances extends JSONRequest< + AssetBalancesResponse, + Record +> { /** * Returns the list of accounts which hold the given asset and their balance. * @@ -145,4 +149,11 @@ export default class LookupAssetBalances extends JSONRequest { this.query['include-all'] = value; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): AssetBalancesResponse { + return AssetBalancesResponse.fromEncodingData( + AssetBalancesResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAssetByID.ts b/src/client/v2/indexer/lookupAssetByID.ts index f04c6aef9..19d5d95ca 100644 --- a/src/client/v2/indexer/lookupAssetByID.ts +++ b/src/client/v2/indexer/lookupAssetByID.ts @@ -1,7 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; +import { AssetResponse } from './models/types.js'; -export default class LookupAssetByID extends JSONRequest { +export default class LookupAssetByID extends JSONRequest< + AssetResponse, + Record +> { /** * Returns asset information of the queried asset. * @@ -56,4 +60,11 @@ export default class LookupAssetByID extends JSONRequest { this.query['include-all'] = value; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): AssetResponse { + return AssetResponse.fromEncodingData( + AssetResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupAssetTransactions.ts b/src/client/v2/indexer/lookupAssetTransactions.ts index 1dc3ebb2e..4fafe7e65 100644 --- a/src/client/v2/indexer/lookupAssetTransactions.ts +++ b/src/client/v2/indexer/lookupAssetTransactions.ts @@ -2,8 +2,12 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import { base64StringFunnel } from './lookupAccountTransactions.js'; import { Address } from '../../../encoding/address.js'; +import { TransactionsResponse } from './models/types.js'; -export default class LookupAssetTransactions extends JSONRequest { +export default class LookupAssetTransactions extends JSONRequest< + TransactionsResponse, + Record +> { /** * Returns transactions relating to the given asset. * @@ -212,11 +216,12 @@ export default class LookupAssetTransactions extends JSONRequest { * .do(); * ``` * - * @param before - rfc3339 string + * @param before - rfc3339 string or Date object * @category query */ - beforeTime(before: string) { - this.query['before-time'] = before; + beforeTime(before: string | Date) { + this.query['before-time'] = + before instanceof Date ? before.toISOString() : before; return this; } @@ -233,11 +238,12 @@ export default class LookupAssetTransactions extends JSONRequest { * .do(); * ``` * - * @param after - rfc3339 string + * @param after - rfc3339 string or Date object * @category query */ - afterTime(after: string) { - this.query['after-time'] = after; + afterTime(after: string | Date) { + this.query['after-time'] = + after instanceof Date ? after.toISOString() : after; return this; } @@ -395,4 +401,11 @@ export default class LookupAssetTransactions extends JSONRequest { this.query['rekey-to'] = rekeyTo; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): TransactionsResponse { + return TransactionsResponse.fromEncodingData( + TransactionsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/lookupBlock.ts b/src/client/v2/indexer/lookupBlock.ts index b0e014512..98f459574 100644 --- a/src/client/v2/indexer/lookupBlock.ts +++ b/src/client/v2/indexer/lookupBlock.ts @@ -1,7 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; +import { Block } from './models/types.js'; -export default class LookupBlock extends JSONRequest { +export default class LookupBlock extends JSONRequest< + Block, + Record +> { /** * Returns the block for the passed round. * @@ -37,4 +41,9 @@ export default class LookupBlock extends JSONRequest { this.query['header-only'] = headerOnly; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): Block { + return Block.fromEncodingData(Block.encodingSchema.fromPreparedJSON(body)); + } } diff --git a/src/client/v2/indexer/lookupTransactionByID.ts b/src/client/v2/indexer/lookupTransactionByID.ts index f03a95863..fbb647631 100644 --- a/src/client/v2/indexer/lookupTransactionByID.ts +++ b/src/client/v2/indexer/lookupTransactionByID.ts @@ -1,7 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; +import { TransactionResponse } from './models/types.js'; -export default class LookupTransactionByID extends JSONRequest { +export default class LookupTransactionByID extends JSONRequest< + TransactionResponse, + Record +> { /** * Returns information about the given transaction. * @@ -28,4 +32,11 @@ export default class LookupTransactionByID extends JSONRequest { path() { return `/v2/transactions/${this.txID}`; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): TransactionResponse { + return TransactionResponse.fromEncodingData( + TransactionResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/makeHealthCheck.ts b/src/client/v2/indexer/makeHealthCheck.ts index c6d3ee14c..baa3a6956 100644 --- a/src/client/v2/indexer/makeHealthCheck.ts +++ b/src/client/v2/indexer/makeHealthCheck.ts @@ -1,4 +1,5 @@ import JSONRequest from '../jsonrequest.js'; +import { HealthCheck } from './models/types.js'; /** * Returns the health object for the service. @@ -12,7 +13,10 @@ import JSONRequest from '../jsonrequest.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-health) * @category GET */ -export default class MakeHealthCheck extends JSONRequest { +export default class MakeHealthCheck extends JSONRequest< + HealthCheck, + Record +> { /** * @returns `/health` */ @@ -20,4 +24,11 @@ export default class MakeHealthCheck extends JSONRequest { path() { return '/health'; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): HealthCheck { + return HealthCheck.fromEncodingData( + HealthCheck.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/searchAccounts.ts b/src/client/v2/indexer/searchAccounts.ts index 956e53a3c..456f26f38 100644 --- a/src/client/v2/indexer/searchAccounts.ts +++ b/src/client/v2/indexer/searchAccounts.ts @@ -1,5 +1,6 @@ import JSONRequest from '../jsonrequest.js'; import { Address } from '../../../encoding/address.js'; +import { AccountsResponse } from './models/types.js'; /** * Returns information about indexed accounts. @@ -12,7 +13,10 @@ import { Address } from '../../../encoding/address.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2accounts) * @category GET */ -export default class SearchAccounts extends JSONRequest { +export default class SearchAccounts extends JSONRequest< + AccountsResponse, + Record +> { /** * @returns `/v2/accounts` */ @@ -267,4 +271,11 @@ export default class SearchAccounts extends JSONRequest { this.query.exclude = exclude; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): AccountsResponse { + return AccountsResponse.fromEncodingData( + AccountsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/searchForApplications.ts b/src/client/v2/indexer/searchForApplications.ts index b971a0673..4ec9a98fc 100644 --- a/src/client/v2/indexer/searchForApplications.ts +++ b/src/client/v2/indexer/searchForApplications.ts @@ -1,5 +1,6 @@ import JSONRequest from '../jsonrequest.js'; import { Address } from '../../../encoding/address.js'; +import { ApplicationsResponse } from './models/types.js'; /** * Returns information about indexed applications. @@ -12,7 +13,10 @@ import { Address } from '../../../encoding/address.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applications) * @category GET */ -export default class SearchForApplications extends JSONRequest { +export default class SearchForApplications extends JSONRequest< + ApplicationsResponse, + Record +> { /** * @returns `/v2/applications` */ @@ -132,4 +136,11 @@ export default class SearchForApplications extends JSONRequest { this.query['include-all'] = value; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): ApplicationsResponse { + return ApplicationsResponse.fromEncodingData( + ApplicationsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/searchForAssets.ts b/src/client/v2/indexer/searchForAssets.ts index c232db531..0ffe0baf3 100644 --- a/src/client/v2/indexer/searchForAssets.ts +++ b/src/client/v2/indexer/searchForAssets.ts @@ -1,5 +1,6 @@ import JSONRequest from '../jsonrequest.js'; import { Address } from '../../../encoding/address.js'; +import { AssetsResponse } from './models/types.js'; /** * Returns information about indexed assets. @@ -12,7 +13,10 @@ import { Address } from '../../../encoding/address.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2assets) * @category GET */ -export default class SearchForAssets extends JSONRequest { +export default class SearchForAssets extends JSONRequest< + AssetsResponse, + Record +> { /** * @returns `/v2/assets` */ @@ -173,4 +177,11 @@ export default class SearchForAssets extends JSONRequest { this.query['include-all'] = value; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): AssetsResponse { + return AssetsResponse.fromEncodingData( + AssetsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/src/client/v2/indexer/searchForTransactions.ts b/src/client/v2/indexer/searchForTransactions.ts index d96ba3945..9b2c26ba8 100644 --- a/src/client/v2/indexer/searchForTransactions.ts +++ b/src/client/v2/indexer/searchForTransactions.ts @@ -1,6 +1,7 @@ import JSONRequest from '../jsonrequest.js'; import { base64StringFunnel } from './lookupAccountTransactions.js'; import { Address } from '../../../encoding/address.js'; +import { TransactionsResponse } from './models/types.js'; /** * Returns information about indexed transactions. @@ -13,7 +14,10 @@ import { Address } from '../../../encoding/address.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2transactions) * @category GET */ -export default class SearchForTransactions extends JSONRequest { +export default class SearchForTransactions extends JSONRequest< + TransactionsResponse, + Record +> { /** * @returns `/v2/transactions` */ @@ -215,11 +219,12 @@ export default class SearchForTransactions extends JSONRequest { * .do(); * ``` * - * @param before - rfc3339 string + * @param before - rfc3339 string or Date object * @category query */ - beforeTime(before: string) { - this.query['before-time'] = before; + beforeTime(before: string | Date) { + this.query['before-time'] = + before instanceof Date ? before.toISOString() : before; return this; } @@ -235,11 +240,12 @@ export default class SearchForTransactions extends JSONRequest { * .do(); * ``` * - * @param after - rfc3339 string + * @param after - rfc3339 string or Date object * @category query */ - afterTime(after: string) { - this.query['after-time'] = after; + afterTime(after: string | Date) { + this.query['after-time'] = + after instanceof Date ? after.toISOString() : after; return this; } @@ -432,4 +438,11 @@ export default class SearchForTransactions extends JSONRequest { this.query['currency-less-than'] = lesser; return this; } + + // eslint-disable-next-line class-methods-use-this + prepare(body: Record): TransactionsResponse { + return TransactionsResponse.fromEncodingData( + TransactionsResponse.encodingSchema.fromPreparedJSON(body) + ); + } } diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index bba3a830b..71959f109 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -1477,62 +1477,390 @@ module.exports = function getSteps(options) { When( 'we make any {string} call to {string}.', // eslint-disable-next-line @typescript-eslint/no-unused-vars - async function (client, _endpoint) { - try { - if (client === 'algod') { - // endpoints are ignored by mock server, see setupMockServerForResponses - if (responseFormat === 'msgp') { - const response = await this.v2Client.block(0).do(); - this.actualMockResponse = response.msgpackPrepare(); - } else { + async function (client, endpoint) { + if (client === 'algod') { + switch (endpoint) { + case 'GetStatus': + this.actualMockResponse = await this.v2Client.status().do(); + break; + case 'GetBlock': + this.actualMockResponse = await this.v2Client.block(10).do(); + break; + case 'WaitForBlock': + this.actualMockResponse = await this.v2Client + .statusAfterBlock(10) + .do(); + break; + case 'TealCompile': + this.actualMockResponse = await this.v2Client + .compile(makeUint8Array()) + .do(); + break; + case 'RawTransaction': + this.actualMockResponse = await this.v2Client + .sendRawTransaction(makeUint8Array()) + .do(); + break; + case 'GetSupply': + this.actualMockResponse = await this.v2Client.supply().do(); + break; + case 'TransactionParams': { + const response = await this.v2Client.getTransactionParams().do(); + this.actualMockResponse = + new algosdk.modelsv2.TransactionParametersResponse({ + consensusVersion: response.consensusVersion, + fee: response.fee, + genesisHash: response.genesisHash, + genesisId: response.genesisID, + lastRound: response.firstValid, + minFee: response.minFee, + }); + break; + } + case 'AccountInformation': + this.actualMockResponse = await this.v2Client + .accountInformation(algosdk.Address.zeroAddress()) + .do(); + break; + case 'GetApplicationByID': + this.actualMockResponse = await this.v2Client + .getApplicationByID(10) + .do(); + break; + case 'GetAssetByID': + this.actualMockResponse = await this.v2Client.getAssetByID(10).do(); + break; + case 'PendingTransactionInformation': + this.actualMockResponse = await this.v2Client + .pendingTransactionInformation('transaction') + .do(); + break; + case 'GetPendingTransactions': + this.actualMockResponse = await this.v2Client + .pendingTransactionsInformation() + .do(); + break; + case 'GetPendingTransactionsByAddress': + this.actualMockResponse = await this.v2Client + .pendingTransactionByAddress(algosdk.Address.zeroAddress()) + .do(); + break; + case 'DryRun': + this.actualMockResponse = await this.v2Client + .dryrun( + new algosdk.modelsv2.DryrunRequest({ + accounts: [], + apps: [], + latestTimestamp: 0, + protocolVersion: '', + round: 0, + sources: [], + txns: [], + }) + ) + .do(); + break; + case 'GetTransactionProof': + // fallthrough + case 'Proof': + this.actualMockResponse = await this.v2Client + .getTransactionProof(10, 'asdf') + .do(); + break; + case 'GetGenesis': this.actualMockResponse = await this.v2Client.genesis().do(); + break; + case 'AccountApplicationInformation': + this.actualMockResponse = await this.v2Client + .accountApplicationInformation(algosdk.Address.zeroAddress(), 10) + .do(); + break; + case 'AccountAssetInformation': + this.actualMockResponse = await this.v2Client + .accountAssetInformation(algosdk.Address.zeroAddress(), 10) + .do(); + break; + case 'GetLightBlockHeaderProof': + this.actualMockResponse = await this.v2Client + .getLightBlockHeaderProof(123) + .do(); + break; + case 'GetStateProof': + this.actualMockResponse = await this.v2Client + .getStateProof(123) + .do(); + break; + case 'GetBlockHash': + this.actualMockResponse = await this.v2Client + .getBlockHash(123) + .do(); + break; + case 'GetSyncRound': + this.actualMockResponse = await this.v2Client.getSyncRound().do(); + break; + case 'GetBlockTimeStampOffset': + this.actualMockResponse = await this.v2Client + .getBlockOffsetTimestamp() + .do(); + break; + case 'GetLedgerStateDelta': + this.actualMockResponse = await this.v2Client + .getLedgerStateDelta(123) + .do(); + break; + case 'GetTransactionGroupLedgerStateDeltaForRound': + this.actualMockResponse = await this.v2Client + .getTransactionGroupLedgerStateDeltasForRound(123) + .do(); + break; + case 'GetLedgerStateDeltaForTransactionGroup': + this.actualMockResponse = await this.v2Client + .getLedgerStateDeltaForTransactionGroup('someID') + .do(); + break; + case 'GetBlockTxids': + this.actualMockResponse = await this.v2Client + .getBlockTxids(123) + .do(); + break; + case 'any': { + // This is an error case + let caughtError = false; + try { + await this.v2Client.status().do(); + } catch (err) { + assert.strictEqual(this.expectedMockResponseCode, 500); + assert.ok( + err.toString().includes('Received status 500'), + `expected response code 500 implies error Internal Server Error but instead had error: ${err}` + ); + + assert.ok(err.response.body); + this.actualMockResponse = err.response.body; + caughtError = true; + } + if (!caughtError) { + throw new Error('Expected error response, got none.'); + } + break; } - } else if (client === 'indexer') { - // endpoints are ignored by mock server, see setupMockServerForResponses - this.actualMockResponse = await this.indexerClient - .makeHealthCheck() - .do(); - } else { - throw Error(`did not recognize desired client "${client}"`); - } - } catch (err) { - if (this.expectedMockResponseCode === 200) { - throw err; + default: + throw new Error(`Unrecognized algod endpoint: ${endpoint}`); } - if (this.expectedMockResponseCode === 500) { - if (!err.toString().includes('Received status 500')) { - throw Error( - `expected response code 500 implies error Internal Server Error but instead had error: ${err}` - ); + } else if (client === 'indexer') { + switch (endpoint) { + case 'lookupAccountByID': + this.actualMockResponse = await this.indexerClient + .lookupAccountByID(algosdk.Address.zeroAddress()) + .do(); + break; + case 'searchForAccounts': + this.actualMockResponse = await this.indexerClient + .searchAccounts() + .do(); + break; + case 'lookupApplicationByID': + this.actualMockResponse = await this.indexerClient + .lookupApplications(10) + .do(); + break; + case 'searchForApplications': + this.actualMockResponse = await this.indexerClient + .searchForApplications() + .do(); + break; + case 'lookupAssetBalances': + this.actualMockResponse = await this.indexerClient + .lookupAssetBalances(10) + .do(); + break; + case 'lookupAssetByID': + this.actualMockResponse = await this.indexerClient + .lookupAssetByID(10) + .do(); + break; + case 'searchForAssets': + this.actualMockResponse = await this.indexerClient + .searchForAssets() + .do(); + break; + case 'lookupAccountTransactions': + this.actualMockResponse = await this.indexerClient + .lookupAccountTransactions(algosdk.Address.zeroAddress()) + .do(); + break; + case 'lookupAssetTransactions': + this.actualMockResponse = await this.indexerClient + .lookupAssetTransactions(10) + .do(); + break; + case 'searchForTransactions': + this.actualMockResponse = await this.indexerClient + .searchForTransactions() + .do(); + break; + case 'lookupBlock': + this.actualMockResponse = await this.indexerClient + .lookupBlock(10) + .do(); + break; + case 'lookupTransaction': + this.actualMockResponse = await this.indexerClient + .lookupTransactionByID('') + .do(); + break; + case 'lookupAccountAppLocalStates': + this.actualMockResponse = await this.indexerClient + .lookupAccountAppLocalStates(algosdk.Address.zeroAddress()) + .do(); + break; + case 'lookupAccountCreatedApplications': + this.actualMockResponse = await this.indexerClient + .lookupAccountCreatedApplications(algosdk.Address.zeroAddress()) + .do(); + break; + case 'lookupAccountAssets': + this.actualMockResponse = await this.indexerClient + .lookupAccountAssets(algosdk.Address.zeroAddress()) + .do(); + break; + case 'lookupAccountCreatedAssets': + this.actualMockResponse = await this.indexerClient + .lookupAccountCreatedAssets(algosdk.Address.zeroAddress()) + .do(); + break; + case 'lookupApplicationLogsByID': + this.actualMockResponse = await this.indexerClient + .lookupApplicationLogs(10) + .do(); + break; + case 'any': { + // This is an error case + let caughtError = false; + try { + await this.indexerClient.searchAccounts().do(); + } catch (err) { + assert.strictEqual(this.expectedMockResponseCode, 500); + assert.ok( + err.toString().includes('Received status 500'), + `expected response code 500 implies error Internal Server Error but instead had error: ${err}` + ); + + assert.ok(err.response.body); + this.actualMockResponse = err.response.body; + caughtError = true; + } + if (!caughtError) { + throw new Error('Expected error response, got none.'); + } + break; } + default: + throw new Error(`Unrecognized indexer endpoint: ${endpoint}`); } + } else { + throw Error(`did not recognize desired client "${client}"`); } } ); + function pruneDefaultValuesFromObject(object) { + if ( + typeof object !== 'object' || + object === null || + Array.isArray(object) + ) { + throw new Error('pruneDefaultValuesFromObject expects an object.'); + } + const prunedObject = makeObject(object); + for (const [key, value] of Object.entries(prunedObject)) { + if ( + value === undefined || + value === null || + value === 0 || + value === BigInt(0) || + value === '' || + value === false || + (Array.isArray(value) && value.length === 0) || + (typeof value === 'object' && Object.keys(value).length === 0) + ) { + delete prunedObject[key]; + continue; + } + if (Array.isArray(value)) { + prunedObject[key] = value.map((element) => + typeof element === 'object' && + !Array.isArray(element) && + element !== null + ? pruneDefaultValuesFromObject(element) + : element + ); + continue; + } + if (typeof value === 'object') { + prunedObject[key] = pruneDefaultValuesFromObject(value); + if (Object.keys(prunedObject[key]).length === 0) { + delete prunedObject[key]; + } + } + } + return prunedObject; + } + Then('the parsed response should equal the mock response.', function () { + let expectedJsonNeedsPruning = true; + + let encodedResponseObject; if (this.expectedMockResponseCode === 200) { - // assert.deepStrictEqual considers a Buffer and Uint8Array with the same contents as unequal. - // These types are fairly interchangable in different parts of the SDK, so we need to normalize - // them before comparing, which is why we chain encoding/decoding below. if (responseFormat === 'json') { - assert.strictEqual( - algosdk.stringifyJSON( - algosdk.parseJSON(expectedMockResponse, { - intDecoding: algosdk.IntDecoding.MIXED, - }) - ), - algosdk.stringifyJSON(this.actualMockResponse) - ); + if (typeof this.actualMockResponse.toEncodingData === 'function') { + if ( + this.actualMockResponse instanceof + algosdk.modelsv2.TransactionGroupLedgerStateDeltasForRoundResponse + ) { + // TransactionGroupLedgerStateDeltasForRoundResponse has an UntypedResponse inside of it, + // so the expected JSON response should not be pruned. + expectedJsonNeedsPruning = false; + } + encodedResponseObject = algosdk.encodeJSON(this.actualMockResponse); + } else { + // Handles non-typed responses such as "GetLedgerStateDelta" + encodedResponseObject = algosdk.stringifyJSON( + this.actualMockResponse + ); + expectedJsonNeedsPruning = false; + } } else { - assert.deepStrictEqual( - algosdk.decodeObj( - new Uint8Array(algosdk.encodeObj(this.actualMockResponse)) - ), - algosdk.decodeObj(expectedMockResponse) + encodedResponseObject = algosdk.encodeMsgpack(this.actualMockResponse); + } + } else { + encodedResponseObject = algosdk.stringifyJSON(this.actualMockResponse); + } + + // We chain encoding/decoding below to normalize the objects for comparison. This helps deal + // with type differences such as bigint vs number and Uint8Array vs Buffer. + + let actualResponseObject; + let parsedExpectedMockResponse; + if (responseFormat === 'json') { + actualResponseObject = algosdk.parseJSON(encodedResponseObject, { + intDecoding: algosdk.IntDecoding.MIXED, + }); + parsedExpectedMockResponse = algosdk.parseJSON(expectedMockResponse, { + intDecoding: algosdk.IntDecoding.MIXED, + }); + if (expectedJsonNeedsPruning) { + // Prune default values from the actual response object to match the expected response object. + parsedExpectedMockResponse = pruneDefaultValuesFromObject( + parsedExpectedMockResponse ); } + } else { + actualResponseObject = algosdk.decodeObj(encodedResponseObject); + parsedExpectedMockResponse = algosdk.decodeObj(expectedMockResponse); } + + assert.deepStrictEqual(actualResponseObject, parsedExpectedMockResponse); }); Then('expect error string to contain {string}', (expectedErrorString) => { @@ -1987,24 +2315,25 @@ module.exports = function getSteps(options) { if (excludeCloseToAsString === 'true') { excludeCloseTo = true; } - await this.indexerClient - .lookupAssetTransactions(assetIndex) - .beforeTime(beforeTime) - .afterTime(afterTime) - .address(address) - .addressRole(addressRole) - .currencyGreaterThan(currencyGreater) - .currencyLessThan(currencyLesser) - .limit(limit) - .minRound(minRound) - .maxRound(maxRound) - .notePrefix(notePrefix) - .round(round) - .sigType(sigType) - .txid(txid) - .txType(txType) - .excludeCloseTo(excludeCloseTo) - .do(); + await doOrDoRaw( + this.indexerClient + .lookupAssetTransactions(assetIndex) + .beforeTime(beforeTime) + .afterTime(afterTime) + .address(address) + .addressRole(addressRole) + .currencyGreaterThan(currencyGreater) + .currencyLessThan(currencyLesser) + .limit(limit) + .minRound(minRound) + .maxRound(maxRound) + .notePrefix(notePrefix) + .round(round) + .sigType(sigType) + .txid(txid) + .txType(txType) + .excludeCloseTo(excludeCloseTo) + ); } ); @@ -2077,22 +2406,23 @@ module.exports = function getSteps(options) { currencyLesser, assetIndex ) { - await this.indexerClient - .lookupAccountTransactions(account) - .beforeTime(beforeTime) - .afterTime(afterTime) - .assetID(assetIndex) - .currencyGreaterThan(currencyGreater) - .currencyLessThan(currencyLesser) - .limit(limit) - .maxRound(maxRound) - .minRound(minRound) - .notePrefix(notePrefix) - .round(round) - .sigType(sigType) - .txid(txid) - .txType(txType) - .do(); + await doOrDoRaw( + this.indexerClient + .lookupAccountTransactions(account) + .beforeTime(beforeTime) + .afterTime(afterTime) + .assetID(assetIndex) + .currencyGreaterThan(currencyGreater) + .currencyLessThan(currencyLesser) + .limit(limit) + .maxRound(maxRound) + .minRound(minRound) + .notePrefix(notePrefix) + .round(round) + .sigType(sigType) + .txid(txid) + .txType(txType) + ); } ); @@ -2143,35 +2473,39 @@ module.exports = function getSteps(options) { When( 'we make a Lookup Block call against round {int}', async function (round) { - await this.indexerClient.lookupBlock(round).do(); + await doOrDoRaw(this.indexerClient.lookupBlock(round)); } ); When( 'we make a Lookup Block call against round {int} and header {string}', async function (int, string) { - await this.indexerClient.lookupBlock(int).headerOnly(string).do(); + await doOrDoRaw(this.indexerClient.lookupBlock(int).headerOnly(string)); } ); When( 'we make a Lookup Account by ID call against account {string} with round {int}', async function (account, round) { - await this.indexerClient.lookupAccountByID(account).round(round).do(); + await doOrDoRaw( + this.indexerClient.lookupAccountByID(account).round(round) + ); } ); When( 'we make a Lookup Account by ID call against account {string} with exclude {string}', async function (account, exclude) { - await this.indexerClient.lookupAccountByID(account).exclude(exclude).do(); + await doOrDoRaw( + this.indexerClient.lookupAccountByID(account).exclude(exclude) + ); } ); When( 'we make a Lookup Asset by ID call against asset index {int}', async function (assetIndex) { - await this.indexerClient.lookupAssetByID(assetIndex).do(); + await doOrDoRaw(this.indexerClient.lookupAssetByID(assetIndex)); } ); @@ -2199,29 +2533,31 @@ module.exports = function getSteps(options) { When( 'we make a LookupApplicationLogsByID call with applicationID {int} limit {int} minRound {int} maxRound {int} nextToken {string} sender {string} and txID {string}', async function (appID, limit, minRound, maxRound, nextToken, sender, txID) { - await this.indexerClient - .lookupApplicationLogs(appID) - .limit(limit) - .maxRound(maxRound) - .minRound(minRound) - .nextToken(nextToken) - .sender(sender) - .txid(txID) - .do(); + await doOrDoRaw( + this.indexerClient + .lookupApplicationLogs(appID) + .limit(limit) + .maxRound(maxRound) + .minRound(minRound) + .nextToken(nextToken) + .sender(sender) + .txid(txID) + ); } ); When( 'we make a Search Accounts call with assetID {int} limit {int} currencyGreaterThan {int} currencyLessThan {int} and round {int}', async function (assetIndex, limit, currencyGreater, currencyLesser, round) { - await this.indexerClient - .searchAccounts() - .assetID(assetIndex) - .currencyGreaterThan(currencyGreater) - .currencyLessThan(currencyLesser) - .limit(limit) - .round(round) - .do(); + await doOrDoRaw( + this.indexerClient + .searchAccounts() + .assetID(assetIndex) + .currencyGreaterThan(currencyGreater) + .currencyLessThan(currencyLesser) + .limit(limit) + .round(round) + ); } ); @@ -2250,14 +2586,16 @@ module.exports = function getSteps(options) { When( 'we make a Search Accounts call with exclude {string}', async function (exclude) { - await this.indexerClient.searchAccounts().exclude(exclude).do(); + await doOrDoRaw(this.indexerClient.searchAccounts().exclude(exclude)); } ); When( 'we make a SearchForApplications call with creator {string}', async function (creator) { - await this.indexerClient.searchForApplications().creator(creator).do(); + await doOrDoRaw( + this.indexerClient.searchForApplications().creator(creator) + ); } ); @@ -2285,25 +2623,26 @@ module.exports = function getSteps(options) { if (excludeCloseToAsString === 'true') { excludeCloseTo = true; } - await this.indexerClient - .searchForTransactions() - .address(account) - .addressRole(addressRole) - .assetID(assetIndex) - .beforeTime(beforeTime) - .afterTime(afterTime) - .currencyGreaterThan(currencyGreater) - .currencyLessThan(currencyLesser) - .limit(limit) - .maxRound(maxRound) - .minRound(minRound) - .notePrefix(notePrefix) - .round(round) - .sigType(sigType) - .txid(txid) - .txType(txType) - .excludeCloseTo(excludeCloseTo) - .do(); + await doOrDoRaw( + this.indexerClient + .searchForTransactions() + .address(account) + .addressRole(addressRole) + .assetID(assetIndex) + .beforeTime(beforeTime) + .afterTime(afterTime) + .currencyGreaterThan(currencyGreater) + .currencyLessThan(currencyLesser) + .limit(limit) + .maxRound(maxRound) + .minRound(minRound) + .notePrefix(notePrefix) + .round(round) + .sigType(sigType) + .txid(txid) + .txType(txType) + .excludeCloseTo(excludeCloseTo) + ); } ); @@ -2362,28 +2701,29 @@ module.exports = function getSteps(options) { When( 'we make a SearchForAssets call with limit {int} creator {string} name {string} unit {string} index {int}', async function (limit, creator, name, unit, index) { - await this.indexerClient - .searchForAssets() - .limit(limit) - .creator(creator) - .name(name) - .unit(unit) - .index(index) - .do(); + await doOrDoRaw( + this.indexerClient + .searchForAssets() + .limit(limit) + .creator(creator) + .name(name) + .unit(unit) + .index(index) + ); } ); When( 'we make a SearchForApplications call with applicationID {int}', async function (index) { - await this.indexerClient.searchForApplications().index(index).do(); + await doOrDoRaw(this.indexerClient.searchForApplications().index(index)); } ); When( 'we make a LookupApplications call with applicationID {int}', async function (index) { - await this.indexerClient.lookupApplications(index).do(); + await doOrDoRaw(this.indexerClient.lookupApplications(index)); } ); @@ -2399,7 +2739,7 @@ module.exports = function getSteps(options) { 'the parsed LookupAssetBalances response should be valid on round {int}, and contain an array of len {int} and element number {int} should have address {string} amount {int} and frozen state {string}', (round, length, idx, address, amount, frozenStateAsString) => { assert.strictEqual( - anyLookupAssetBalancesResponse['current-round'], + anyLookupAssetBalancesResponse.currentRound, BigInt(round) ); assert.strictEqual( @@ -2418,7 +2758,7 @@ module.exports = function getSteps(options) { BigInt(amount) ); assert.strictEqual( - anyLookupAssetBalancesResponse.balances[idx]['is-frozen'], + anyLookupAssetBalancesResponse.balances[idx].isFrozen, frozenState ); } @@ -2427,52 +2767,56 @@ module.exports = function getSteps(options) { When( 'we make a LookupAccountAssets call with accountID {string} assetID {int} includeAll {string} limit {int} next {string}', async function (account, assetID, includeAll, limit, next) { - await this.indexerClient - .lookupAccountAssets(account) - .assetId(assetID) - .includeAll(includeAll === 'true') - .limit(limit) - .nextToken(next) - .do(); + await doOrDoRaw( + this.indexerClient + .lookupAccountAssets(account) + .assetId(assetID) + .includeAll(includeAll === 'true') + .limit(limit) + .nextToken(next) + ); } ); When( 'we make a LookupAccountCreatedAssets call with accountID {string} assetID {int} includeAll {string} limit {int} next {string}', async function (account, assetID, includeAll, limit, next) { - await this.indexerClient - .lookupAccountCreatedAssets(account) - .assetID(assetID) - .includeAll(includeAll === 'true') - .limit(limit) - .nextToken(next) - .do(); + await doOrDoRaw( + this.indexerClient + .lookupAccountCreatedAssets(account) + .assetID(assetID) + .includeAll(includeAll === 'true') + .limit(limit) + .nextToken(next) + ); } ); When( 'we make a LookupAccountAppLocalStates call with accountID {string} applicationID {int} includeAll {string} limit {int} next {string}', async function (account, applicationID, includeAll, limit, next) { - await this.indexerClient - .lookupAccountAppLocalStates(account) - .applicationID(applicationID) - .includeAll(includeAll === 'true') - .limit(limit) - .nextToken(next) - .do(); + await doOrDoRaw( + this.indexerClient + .lookupAccountAppLocalStates(account) + .applicationID(applicationID) + .includeAll(includeAll === 'true') + .limit(limit) + .nextToken(next) + ); } ); When( 'we make a LookupAccountCreatedApplications call with accountID {string} applicationID {int} includeAll {string} limit {int} next {string}', async function (account, applicationID, includeAll, limit, next) { - await this.indexerClient - .lookupAccountCreatedApplications(account) - .applicationID(applicationID) - .includeAll(includeAll === 'true') - .limit(limit) - .nextToken(next) - .do(); + await doOrDoRaw( + this.indexerClient + .lookupAccountCreatedApplications(account) + .applicationID(applicationID) + .includeAll(includeAll === 'true') + .limit(limit) + .nextToken(next) + ); } ); @@ -2488,7 +2832,7 @@ module.exports = function getSteps(options) { 'the parsed LookupAssetTransactions response should be valid on round {int}, and contain an array of len {int} and element number {int} should have sender {string}', (round, length, idx, sender) => { assert.strictEqual( - anyLookupAssetTransactionsResponse['current-round'], + anyLookupAssetTransactionsResponse.currentRound, BigInt(round) ); assert.strictEqual( @@ -2519,12 +2863,12 @@ module.exports = function getSteps(options) { 'the parsed LookupAccountTransactions response should be valid on round {int}, and contain an array of len {int} and element number {int} should have sender {string}', (round, length, idx, sender) => { assert.strictEqual( - round.toString(), - anyLookupAccountTransactionsResponse['current-round'].toString() + anyLookupAccountTransactionsResponse.currentRound, + BigInt(round) ); assert.strictEqual( - length, - anyLookupAccountTransactionsResponse.transactions.length + anyLookupAccountTransactionsResponse.transactions.length, + length ); if (length === 0) { return; @@ -2546,8 +2890,8 @@ module.exports = function getSteps(options) { 'the parsed LookupBlock response should have previous block hash {string}', (prevHash) => { assert.strictEqual( - prevHash, - anyLookupBlockResponse['previous-block-hash'] + algosdk.bytesToBase64(anyLookupBlockResponse.previousBlockHash), + prevHash ); } ); @@ -2565,7 +2909,7 @@ module.exports = function getSteps(options) { Then( 'the parsed LookupAccountByID response should have address {string}', (address) => { - assert.strictEqual(address, anyLookupAccountByIDResponse.account.address); + assert.strictEqual(anyLookupAccountByIDResponse.account.address, address); } ); @@ -2590,17 +2934,14 @@ module.exports = function getSteps(options) { Then( 'the parsed SearchAccounts response should be valid on round {int} and the array should be of len {int} and the element at index {int} should have address {string}', (round, length, idx, address) => { - assert.strictEqual( - round.toString(), - anySearchAccountsResponse['current-round'].toString() - ); - assert.strictEqual(length, anySearchAccountsResponse.accounts.length); + assert.strictEqual(anySearchAccountsResponse.currentRound, BigInt(round)); + assert.strictEqual(anySearchAccountsResponse.accounts.length, length); if (length === 0) { return; } assert.strictEqual( - address, - anySearchAccountsResponse.accounts[idx].address + anySearchAccountsResponse.accounts[idx].address, + address ); } ); @@ -2608,14 +2949,14 @@ module.exports = function getSteps(options) { Then( 'the parsed SearchAccounts response should be valid on round {int} and the array should be of len {int} and the element at index {int} should have authorizing address {string}', (round, length, idx, authAddress) => { - assert.strictEqual(round, anySearchAccountsResponse['current-round']); - assert.strictEqual(length, anySearchAccountsResponse.accounts.length); + assert.strictEqual(anySearchAccountsResponse.currentRound, BigInt(round)); + assert.strictEqual(anySearchAccountsResponse.accounts.length, length); if (length === 0) { return; } assert.strictEqual( - authAddress, - anySearchAccountsResponse.accounts[idx]['auth-addr'] + anySearchAccountsResponse.accounts[idx].authAddr, + authAddress ); } ); @@ -2632,19 +2973,19 @@ module.exports = function getSteps(options) { 'the parsed SearchForTransactions response should be valid on round {int} and the array should be of len {int} and the element at index {int} should have sender {string}', (round, length, idx, sender) => { assert.strictEqual( - round.toString(), - anySearchForTransactionsResponse['current-round'].toString() + anySearchForTransactionsResponse.currentRound, + BigInt(round) ); assert.strictEqual( - length, - anySearchForTransactionsResponse.transactions.length + anySearchForTransactionsResponse.transactions.length, + length ); if (length === 0) { return; } assert.strictEqual( - sender, - anySearchForTransactionsResponse.transactions[idx].sender + anySearchForTransactionsResponse.transactions[idx].sender, + sender ); } ); @@ -2653,19 +2994,19 @@ module.exports = function getSteps(options) { 'the parsed SearchForTransactions response should be valid on round {int} and the array should be of len {int} and the element at index {int} should have rekey-to {string}', (round, length, idx, rekeyTo) => { assert.strictEqual( - round, - anySearchForTransactionsResponse['current-round'] + anySearchForTransactionsResponse.currentRound, + BigInt(round) ); assert.strictEqual( - length, - anySearchForTransactionsResponse.transactions.length + anySearchForTransactionsResponse.transactions.length, + length ); if (length === 0) { return; } assert.strictEqual( - rekeyTo, - anySearchForTransactionsResponse.transactions[idx]['rekey-to'] + anySearchForTransactionsResponse.transactions[idx].rekeyTo, + rekeyTo ); } ); @@ -2682,16 +3023,16 @@ module.exports = function getSteps(options) { 'the parsed SearchForAssets response should be valid on round {int} and the array should be of len {int} and the element at index {int} should have asset index {int}', (round, length, idx, assetIndex) => { assert.strictEqual( - round.toString(), - anySearchForAssetsResponse['current-round'].toString() + anySearchForAssetsResponse.currentRound, + BigInt(round) ); - assert.strictEqual(length, anySearchForAssetsResponse.assets.length); + assert.strictEqual(anySearchForAssetsResponse.assets.length, length); if (length === 0) { return; } assert.strictEqual( - assetIndex.toString(), - anySearchForAssetsResponse.assets[idx].index.toString() + anySearchForAssetsResponse.assets[idx].index, + BigInt(assetIndex) ); } ); @@ -4489,10 +4830,10 @@ module.exports = function getSteps(options) { Then( 'according to {string}, the contents of the box with name {string} in the current application should be {string}. If there is an error it is {string}.', async function (fromClient, boxName, boxValue, errString) { - try { - const boxKey = splitAndProcessAppArgs(boxName)[0]; + const boxKey = splitAndProcessAppArgs(boxName)[0]; - let resp = null; + let resp = null; + try { if (fromClient === 'algod') { resp = await this.v2Client .getApplicationBoxByName(this.currentApplicationIndex, boxKey) @@ -4507,22 +4848,22 @@ module.exports = function getSteps(options) { } else { assert.fail(`expecting algod or indexer, got ${fromClient}`); } - - const actualName = resp.name; - const actualValue = resp.value; - assert.deepStrictEqual(boxKey, actualName); - assert.deepStrictEqual(algosdk.base64ToBytes(boxValue), actualValue); } catch (err) { if (errString !== '') { - assert.deepStrictEqual( - true, + assert.ok( err.message.includes(errString), `expected ${errString} got ${err.message}` ); - } else { - throw err; + return; } + throw err; } + assert.ok(!errString, "expected error, didn't get one"); + + const actualName = resp.name; + const actualValue = resp.value; + assert.deepStrictEqual(boxKey, actualName); + assert.deepStrictEqual(algosdk.base64ToBytes(boxValue), actualValue); } ); @@ -4610,7 +4951,7 @@ module.exports = function getSteps(options) { const maxAttempts = 30; const roundToWaitFor = this.lastTxnConfirmedRound; - let indexerRound = 0; + let indexerRound = BigInt(0); let attempts = 0; for (;;) { @@ -4685,7 +5026,7 @@ module.exports = function getSteps(options) { .compile(tealSrc) .sourcemap(true) .do(); - this.rawSourceMap = algosdk.stringifyJSON(compiledResponse.sourcemap); + this.rawSourceMap = algosdk.encodeJSON(compiledResponse.sourcemap); } ); @@ -5278,7 +5619,7 @@ module.exports = function getSteps(options) { When( 'we make a GetBlockTxids call against block number {int}', async function (round) { - await this.v2Client.getBlockTxids(round).do(); + await this.v2Client.getBlockTxids(round).doRaw(); } ); diff --git a/v2_TO_v3_MIGRATION_GUIDE.md b/v2_TO_v3_MIGRATION_GUIDE.md index 8d6b305cd..4126adfbe 100644 --- a/v2_TO_v3_MIGRATION_GUIDE.md +++ b/v2_TO_v3_MIGRATION_GUIDE.md @@ -244,7 +244,7 @@ That type has been removed and this behavior is no longer possible in v3. Instea #### Multisig Transaction Class -The `MultisigTransaction` class have been removed, as it was unnecessary. +The `MultisigTransaction` class has been removed, as it was unnecessary. #### Transaction Group Class @@ -278,7 +278,7 @@ Auction bids have been removed from the library in v3, as they were only relevan In v2, the `Algodv2` and `Indexer` clients, as well as each individual request class, had a `setIntDecoding` method which could be used to configure how integers in the response were parsed into either a JavaScript `number` or `bigint`. These methods have been removed in v3. -Instead, Algod responses are now fully typed, and individual fields are typed as either `number` or `bigint`. The types defined in the `modelsv2` namespace have been updated to reflect this. In v2, these classes used `number | bigint` for all numeric fields, but in v3, they will use either `number` or `bigint` depending on the field. +Instead, Algod and Indexer responses are now fully typed, and individual fields are typed as either `number` or `bigint`. The types defined in the `modelsv2` and `indexerModels` namespaces have been updated to reflect this. In v2, these classes used `number | bigint` for all numeric fields, but in v3, they will use either `number` or `bigint` depending on the field. Generally speaking, the fields will be `bigint` based on the following criteria: @@ -289,9 +289,7 @@ Generally speaking, the fields will be `bigint` based on the following criteria: - If the field can be any value in the uint64 range, it will be `bigint` - Other fields which are guaranteed to be small will be `number` -Indexer responses are not yet typed, and all numeric fields are returned as `bigint`. - -Additionally, Algod and Indexer request and response models used to be subclasses of a `BaseModel` type. This type has been removed in v3, and instead all models adhere to the `Encodable` interface. More information about encoding changes can be found in the [Encoding and Decoding](#encoding-and-decoding) section. +Additionally, Algod and Indexer request and response models used to be subclasses of a `BaseModel` type. This type has been removed in v3, and instead all models adhere to the `Encodable` interface. More information about encoding changes can be found in the [Object Encoding and Decoding](#object-encoding-and-decoding) section. ### JSON Operations @@ -301,7 +299,7 @@ In order to facilitate `bigint` as a first-class type in this SDK, additional JS `stringifyJSON` can be used to convert a JavaScript object containing `bigint`s into a JSON string, something `JSON.stringify` cannot do. -If your v2 code uses `JSON.parse` or `JSON.stringify` on types which can now contain `bigint`s in v3, such as `Transaction` representations or REST API responses, consider using these new functions instead. +If your v2 code uses `JSON.parse` or `JSON.stringify` on types which can now contain `bigint`s in v3, you may receive an error such as `TypeError: Do not know how to serialize a BigInt`. Consider using these new functions instead. Or, if the types are `Encodable`, use the new `encodeJSON` and `decodeJSON` functions described in the [Object Encoding and Decoding](#object-encoding-and-decoding) section. ### IntDecoding @@ -315,7 +313,7 @@ Specifically, the `DryrunResult` class and its dependent types have been removed The `DryrunTransactionResult` class, which made up the elements of the v2 `DryrunResult.txns` array, used to have methods `appTrace` and `lsigTrace`. These have been replaced by the new `dryrunTxnResultAppTrace` and `dryrunTxnResultLogicSigTrace` functions, which accept a `DryrunTxnResult`. These new functions should produce identical results to the old ones. -### Encoding and Decoding +### Object Encoding and Decoding In v2 of the SDK, the `Transaction`, `LogicSig`, `BaseModel` and other classes had `get_obj_for_encoding` methods and `from_obj_for_encoding` static methods. These were used during the process of encoding or decoding objects from msgpack or JSON. These ad-hoc methods have been removed in v3, and in their place a new `Encodable` interface has been introduced, along with functions `encodeMsgpack`, `decodeMsgpack`, `encodeJSON`, and `decodeJSON`.