diff --git a/src/client/client.ts b/src/client/client.ts index 7a7dff65b..417c1c2d8 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -14,12 +14,60 @@ interface ErrorWithAdditionalInfo extends Error { statusCode: number; } -export interface HTTPClientResponse { - body: Uint8Array | any; // when content-type=JSON, body is a JSON object, otherwise it's a Uint8Array +export class HTTPClientResponse { + /** + * The raw response bytes + */ + body: Uint8Array; + /** + * If the expected response type is JSON, this is the response bytes converted to a string. + */ text?: string; + format: 'application/msgpack' | 'application/json'; headers: Record; status: number; ok: boolean; + + constructor(options: { + body: Uint8Array; + text?: string; + format: 'application/msgpack' | 'application/json'; + headers: Record; + status: number; + ok: boolean; + }) { + this.body = options.body; + this.text = options.text; + this.format = options.format; + this.headers = options.headers; + this.status = options.status; + this.ok = options.ok; + } + + /** + * Returns the response body as a string, ready to be parsed as JSON. + */ + getJSONText(): string { + if (this.text === undefined) { + throw new Error( + `Response body does not contain JSON data. Format is ${this.format}` + ); + } + return this.text; + } + + /** + * Parses the response body as JSON with the given options. + */ + parseBodyAsJSON(jsonOptions: utils.ParseJSONOptions) { + if (this.text === undefined) { + throw new Error( + `Response body does not contain JSON data. Format is ${this.format}` + ); + } + // eslint-disable-next-line no-use-before-define + return HTTPClient.parseJSON(this.text, this.status, jsonOptions); + } } /** @@ -175,27 +223,21 @@ export class HTTPClient { */ private static prepareResponse( res: BaseHTTPClientResponse, - format: 'application/msgpack' | 'application/json', - parseBody: boolean, - jsonOptions: utils.ParseJSONOptions + format: 'application/msgpack' | 'application/json' ): HTTPClientResponse { - let { body } = res; + const { body } = res; let text: string | undefined; if (format !== 'application/msgpack') { text = (body && new TextDecoder().decode(body)) || ''; } - if (parseBody && format === 'application/json') { - body = HTTPClient.parseJSON(text!, res.status, jsonOptions); - } - - return { + return new HTTPClientResponse({ ...res, - body, + format, text, ok: Math.trunc(res.status / 100) === 2, - }; + }); } /** @@ -204,17 +246,12 @@ export class HTTPClient { * by adding the status and preparing the internal response * @private */ - private static prepareResponseError( - err: any, - jsonOptions: utils.ParseJSONOptions - ) { + private static prepareResponseError(err: any) { if (err.response) { // eslint-disable-next-line no-param-reassign err.response = HTTPClient.prepareResponse( err.response, - 'application/json', - true, - jsonOptions + 'application/json' ); // eslint-disable-next-line no-param-reassign err.status = err.response.status; @@ -237,16 +274,12 @@ export class HTTPClient { */ async get({ relativePath, - jsonOptions, query, requestHeaders, - parseBody, }: { relativePath: string; - jsonOptions: utils.ParseJSONOptions; query?: Query; requestHeaders?: Record; - parseBody: boolean; }): Promise { const format = getAcceptFormat(query); const fullHeaders = { ...(requestHeaders ?? {}), accept: format }; @@ -258,9 +291,9 @@ export class HTTPClient { fullHeaders ); - return HTTPClient.prepareResponse(res, format, parseBody, jsonOptions); + return HTTPClient.prepareResponse(res, format); } catch (err) { - throw HTTPClient.prepareResponseError(err, jsonOptions); + throw HTTPClient.prepareResponseError(err); } } @@ -273,17 +306,13 @@ export class HTTPClient { async post({ relativePath, data, - jsonOptions, query, requestHeaders, - parseBody, }: { relativePath: string; data: any; - jsonOptions: utils.ParseJSONOptions; query?: Query; requestHeaders?: Record; - parseBody: boolean; }): Promise { const fullHeaders = { 'content-type': 'application/json', @@ -298,14 +327,9 @@ export class HTTPClient { fullHeaders ); - return HTTPClient.prepareResponse( - res, - 'application/json', - parseBody, - jsonOptions - ); + return HTTPClient.prepareResponse(res, 'application/json'); } catch (err) { - throw HTTPClient.prepareResponseError(err, jsonOptions); + throw HTTPClient.prepareResponseError(err); } } @@ -318,15 +342,11 @@ export class HTTPClient { async delete({ relativePath, data, - jsonOptions, requestHeaders, - parseBody, }: { relativePath: string; data: any; - jsonOptions: utils.ParseJSONOptions; requestHeaders?: Record; - parseBody: boolean; }) { const fullHeaders = { 'content-type': 'application/json', @@ -343,14 +363,9 @@ export class HTTPClient { fullHeaders ); - return HTTPClient.prepareResponse( - res, - 'application/json', - parseBody, - jsonOptions - ); + return HTTPClient.prepareResponse(res, 'application/json'); } catch (err) { - throw HTTPClient.prepareResponseError(err, jsonOptions); + throw HTTPClient.prepareResponseError(err); } } } diff --git a/src/client/kmd.ts b/src/client/kmd.ts index 1e9a09e21..2c4f85a47 100644 --- a/src/client/kmd.ts +++ b/src/client/kmd.ts @@ -21,39 +21,33 @@ export class KmdClient extends ServiceClient { private async get(relativePath: string): Promise { const res = await this.c.get({ relativePath, - jsonOptions: { - // Using SAFE for all KMD endpoints because no integers in responses should ever be too big - intDecoding: IntDecoding.SAFE, - }, - parseBody: true, }); - return res.body; + return res.parseBodyAsJSON({ + // Using SAFE for all KMD endpoints because no integers in responses should ever be too big + intDecoding: IntDecoding.SAFE, + }); } private async delete(relativePath: string, data: any): Promise { const res = await this.c.delete({ relativePath, data, - jsonOptions: { - // Using SAFE for all KMD endpoints because no integers in responses should ever be too big - intDecoding: IntDecoding.SAFE, - }, - parseBody: true, }); - return res.body; + return res.parseBodyAsJSON({ + // Using SAFE for all KMD endpoints because no integers in responses should ever be too big + intDecoding: IntDecoding.SAFE, + }); } private async post(relativePath: string, data: any): Promise { const res = await this.c.post({ relativePath, data, - parseBody: true, - jsonOptions: { - // Using SAFE for all KMD endpoints because no integers in responses should ever be too big - intDecoding: IntDecoding.SAFE, - }, }); - return res.body; + return res.parseBodyAsJSON({ + // Using SAFE for all KMD endpoints because no integers in responses should ever be too big + intDecoding: IntDecoding.SAFE, + }); } /** diff --git a/src/client/v2/algod/accountApplicationInformation.ts b/src/client/v2/algod/accountApplicationInformation.ts index ad2ff9ee5..6b26c3df3 100644 --- a/src/client/v2/algod/accountApplicationInformation.ts +++ b/src/client/v2/algod/accountApplicationInformation.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { AccountApplicationResponse } from './models/types.js'; import { Address } from '../../../encoding/address.js'; -export default class AccountApplicationInformation extends JSONRequest< - AccountApplicationResponse, - Record -> { +export default class AccountApplicationInformation extends JSONRequest { private account: string; constructor( @@ -23,9 +21,7 @@ export default class AccountApplicationInformation extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AccountApplicationResponse { - return AccountApplicationResponse.fromEncodingData( - AccountApplicationResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AccountApplicationResponse { + return decodeJSON(response.getJSONText(), AccountApplicationResponse); } } diff --git a/src/client/v2/algod/accountAssetInformation.ts b/src/client/v2/algod/accountAssetInformation.ts index 4f1eca9be..eef272257 100644 --- a/src/client/v2/algod/accountAssetInformation.ts +++ b/src/client/v2/algod/accountAssetInformation.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { AccountAssetResponse } from './models/types.js'; import { Address } from '../../../encoding/address.js'; -export default class AccountAssetInformation extends JSONRequest< - AccountAssetResponse, - Record -> { +export default class AccountAssetInformation extends JSONRequest { private account: string; constructor( @@ -23,9 +21,7 @@ export default class AccountAssetInformation extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AccountAssetResponse { - return AccountAssetResponse.fromEncodingData( - AccountAssetResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AccountAssetResponse { + return decodeJSON(response.getJSONText(), AccountAssetResponse); } } diff --git a/src/client/v2/algod/accountInformation.ts b/src/client/v2/algod/accountInformation.ts index 57150cea9..431029761 100644 --- a/src/client/v2/algod/accountInformation.ts +++ b/src/client/v2/algod/accountInformation.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Account } from './models/types.js'; import { Address } from '../../../encoding/address.js'; -export default class AccountInformation extends JSONRequest< - Account, - Record -> { +export default class AccountInformation extends JSONRequest { private account: string; constructor(c: HTTPClient, account: string | Address) { @@ -38,9 +36,7 @@ export default class AccountInformation extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): Account { - return Account.fromEncodingData( - Account.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): Account { + return decodeJSON(response.getJSONText(), Account); } } diff --git a/src/client/v2/algod/block.ts b/src/client/v2/algod/block.ts index d94d047ba..52ac5bfbb 100644 --- a/src/client/v2/algod/block.ts +++ b/src/client/v2/algod/block.ts @@ -1,12 +1,12 @@ -import * as encoding from '../../../encoding/encoding.js'; import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeMsgpack } from '../../../encoding/encoding.js'; import { BlockResponse } from './models/types.js'; /** * block gets the block info for the given round. this call may block */ -export default class Block extends JSONRequest { +export default class Block extends JSONRequest { private round: number; constructor(c: HTTPClient, roundNumber: number) { @@ -20,7 +20,7 @@ export default class Block extends JSONRequest { } // eslint-disable-next-line class-methods-use-this - prepare(body: Uint8Array): BlockResponse { - return encoding.decodeMsgpack(body, BlockResponse); + prepare(response: HTTPClientResponse): BlockResponse { + return decodeMsgpack(response.body, BlockResponse); } } diff --git a/src/client/v2/algod/compile.ts b/src/client/v2/algod/compile.ts index 083e022f6..e69b08ff3 100644 --- a/src/client/v2/algod/compile.ts +++ b/src/client/v2/algod/compile.ts @@ -1,6 +1,6 @@ import { coerceToBytes } from '../../../encoding/binarydata.js'; -import IntDecoding from '../../../types/intDecoding.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { CompileResponse } from './models/types.js'; import JSONRequest from '../jsonrequest.js'; @@ -20,10 +20,7 @@ export function setHeaders(headers: Record = {}) { /** * Executes compile */ -export default class Compile extends JSONRequest< - CompileResponse, - Record -> { +export default class Compile extends JSONRequest { constructor( c: HTTPClient, private source: string | Uint8Array @@ -41,27 +38,20 @@ export default class Compile extends JSONRequest< return this; } - /** - * Executes compile - * @param headers - A headers object - */ - async do(headers = {}) { + protected executeRequest( + headers: Record + ): Promise { const txHeaders = setHeaders(headers); - const res = await this.c.post({ + return this.c.post({ relativePath: this.path(), data: coerceToBytes(this.source), - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, query: this.query, requestHeaders: txHeaders, }); - return this.prepare(res.body); } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): CompileResponse { - return CompileResponse.fromEncodingData( - CompileResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): CompileResponse { + return decodeJSON(response.getJSONText(), CompileResponse); } } diff --git a/src/client/v2/algod/disassemble.ts b/src/client/v2/algod/disassemble.ts index 06ef9ce6e..f07acf509 100644 --- a/src/client/v2/algod/disassemble.ts +++ b/src/client/v2/algod/disassemble.ts @@ -1,6 +1,6 @@ import { coerceToBytes } from '../../../encoding/binarydata.js'; -import IntDecoding from '../../../types/intDecoding.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { DisassembleResponse } from './models/types.js'; import JSONRequest from '../jsonrequest.js'; @@ -20,10 +20,7 @@ export function setHeaders(headers: Record = {}) { /** * Executes disassemble */ -export default class Disassemble extends JSONRequest< - DisassembleResponse, - Record -> { +export default class Disassemble extends JSONRequest { constructor( c: HTTPClient, private source: string | Uint8Array @@ -36,27 +33,20 @@ export default class Disassemble extends JSONRequest< return `/v2/teal/disassemble`; } - /** - * Executes disassemble - * @param headers - A headers object - */ - async do(headers = {}) { + protected executeRequest( + headers: Record + ): Promise { const txHeaders = setHeaders(headers); - const res = await this.c.post({ + return this.c.post({ relativePath: this.path(), data: coerceToBytes(this.source), - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, query: this.query, requestHeaders: txHeaders, }); - return res.body; } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): DisassembleResponse { - return DisassembleResponse.fromEncodingData( - DisassembleResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): DisassembleResponse { + return decodeJSON(response.getJSONText(), DisassembleResponse); } } diff --git a/src/client/v2/algod/dryrun.ts b/src/client/v2/algod/dryrun.ts index 6558c1817..8556607ae 100644 --- a/src/client/v2/algod/dryrun.ts +++ b/src/client/v2/algod/dryrun.ts @@ -1,20 +1,16 @@ -import * as encoding from '../../../encoding/encoding.js'; -import IntDecoding from '../../../types/intDecoding.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON, encodeMsgpack } from '../../../encoding/encoding.js'; import JSONRequest from '../jsonrequest.js'; import { setHeaders } from './compile.js'; import { DryrunResponse } from './models/types.js'; import * as modelsv2 from './models/types.js'; -export default class Dryrun extends JSONRequest< - DryrunResponse, - Record -> { +export default class Dryrun extends JSONRequest { private blob: Uint8Array; constructor(c: HTTPClient, dr: modelsv2.DryrunRequest) { super(c); - this.blob = encoding.encodeMsgpack(dr); + this.blob = encodeMsgpack(dr); } // eslint-disable-next-line class-methods-use-this @@ -22,26 +18,19 @@ export default class Dryrun extends JSONRequest< return '/v2/teal/dryrun'; } - /** - * Executes dryrun - * @param headers - A headers object - */ - async do(headers = {}) { + protected executeRequest( + headers: Record + ): Promise { const txHeaders = setHeaders(headers); - const res = await this.c.post({ + return this.c.post({ relativePath: this.path(), data: this.blob, - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, requestHeaders: txHeaders, }); - return this.prepare(res.body); } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): DryrunResponse { - return DryrunResponse.fromEncodingData( - DryrunResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): DryrunResponse { + return decodeJSON(response.getJSONText(), DryrunResponse); } } diff --git a/src/client/v2/algod/genesis.ts b/src/client/v2/algod/genesis.ts index 4c4cfac36..497b4c797 100644 --- a/src/client/v2/algod/genesis.ts +++ b/src/client/v2/algod/genesis.ts @@ -1,8 +1,14 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; -export default class Genesis extends JSONRequest { +export default class Genesis extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return '/genesis'; } + + // eslint-disable-next-line class-methods-use-this + prepare(response: HTTPClientResponse): string { + return response.getJSONText(); + } } diff --git a/src/client/v2/algod/getApplicationBoxByName.ts b/src/client/v2/algod/getApplicationBoxByName.ts index 58080d393..415f88709 100644 --- a/src/client/v2/algod/getApplicationBoxByName.ts +++ b/src/client/v2/algod/getApplicationBoxByName.ts @@ -1,5 +1,6 @@ import { bytesToBase64 } from '../../../encoding/binarydata.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import JSONRequest from '../jsonrequest.js'; import { Box } from './models/types.js'; @@ -18,10 +19,7 @@ import { Box } from './models/types.js'; * @param index - The application ID to look up. * @category GET */ -export default class GetApplicationBoxByName extends JSONRequest< - Box, - Record -> { +export default class GetApplicationBoxByName extends JSONRequest { constructor( c: HTTPClient, private index: number, @@ -41,7 +39,7 @@ export default class GetApplicationBoxByName extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): Box { - return Box.fromEncodingData(Box.encodingSchema.fromPreparedJSON(body)); + prepare(response: HTTPClientResponse): Box { + return decodeJSON(response.getJSONText(), Box); } } diff --git a/src/client/v2/algod/getApplicationBoxes.ts b/src/client/v2/algod/getApplicationBoxes.ts index 58ba52aca..96b9cbd96 100644 --- a/src/client/v2/algod/getApplicationBoxes.ts +++ b/src/client/v2/algod/getApplicationBoxes.ts @@ -1,5 +1,6 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { BoxesResponse } from './models/types.js'; /** @@ -16,10 +17,7 @@ import { BoxesResponse } from './models/types.js'; * @param index - The application ID to look up. * @category GET */ -export default class GetApplicationBoxes extends JSONRequest< - BoxesResponse, - Record -> { +export default class GetApplicationBoxes extends JSONRequest { constructor( c: HTTPClient, private index: number @@ -56,9 +54,7 @@ export default class GetApplicationBoxes extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): BoxesResponse { - return BoxesResponse.fromEncodingData( - BoxesResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): BoxesResponse { + return decodeJSON(response.getJSONText(), BoxesResponse); } } diff --git a/src/client/v2/algod/getApplicationByID.ts b/src/client/v2/algod/getApplicationByID.ts index 2660244aa..60be5ea7c 100644 --- a/src/client/v2/algod/getApplicationByID.ts +++ b/src/client/v2/algod/getApplicationByID.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Application } from './models/types.js'; -export default class GetApplicationByID extends JSONRequest< - Application, - Record -> { +export default class GetApplicationByID extends JSONRequest { constructor( c: HTTPClient, private index: number | bigint @@ -18,9 +16,7 @@ export default class GetApplicationByID extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): Application { - return Application.fromEncodingData( - Application.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): Application { + return decodeJSON(response.getJSONText(), Application); } } diff --git a/src/client/v2/algod/getAssetByID.ts b/src/client/v2/algod/getAssetByID.ts index 8d340084e..9ad064eaa 100644 --- a/src/client/v2/algod/getAssetByID.ts +++ b/src/client/v2/algod/getAssetByID.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Asset } from './models/types.js'; -export default class GetAssetByID extends JSONRequest< - Asset, - Record -> { +export default class GetAssetByID extends JSONRequest { constructor( c: HTTPClient, private index: number | bigint @@ -18,7 +16,7 @@ export default class GetAssetByID extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): Asset { - return Asset.fromEncodingData(Asset.encodingSchema.fromPreparedJSON(body)); + prepare(response: HTTPClientResponse): Asset { + return decodeJSON(response.getJSONText(), Asset); } } diff --git a/src/client/v2/algod/getBlockHash.ts b/src/client/v2/algod/getBlockHash.ts index 48d34b6e8..57991a430 100644 --- a/src/client/v2/algod/getBlockHash.ts +++ b/src/client/v2/algod/getBlockHash.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { BlockHashResponse } from './models/types.js'; -export default class GetBlockHash extends JSONRequest< - BlockHashResponse, - Record -> { +export default class GetBlockHash extends JSONRequest { round: number | bigint; constructor(c: HTTPClient, roundNumber: number) { @@ -18,9 +16,7 @@ export default class GetBlockHash extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): BlockHashResponse { - return BlockHashResponse.fromEncodingData( - BlockHashResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): BlockHashResponse { + return decodeJSON(response.getJSONText(), BlockHashResponse); } } diff --git a/src/client/v2/algod/getBlockOffsetTimestamp.ts b/src/client/v2/algod/getBlockOffsetTimestamp.ts index 34328e797..df7d6400d 100644 --- a/src/client/v2/algod/getBlockOffsetTimestamp.ts +++ b/src/client/v2/algod/getBlockOffsetTimestamp.ts @@ -1,19 +1,16 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { GetBlockTimeStampOffsetResponse } from './models/types.js'; -export default class GetBlockOffsetTimestamp extends JSONRequest< - GetBlockTimeStampOffsetResponse, - Record -> { +export default class GetBlockOffsetTimestamp extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return `/v2/devmode/blocks/offset`; } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): GetBlockTimeStampOffsetResponse { - return GetBlockTimeStampOffsetResponse.fromEncodingData( - GetBlockTimeStampOffsetResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): GetBlockTimeStampOffsetResponse { + return decodeJSON(response.getJSONText(), GetBlockTimeStampOffsetResponse); } } diff --git a/src/client/v2/algod/getBlockTxids.ts b/src/client/v2/algod/getBlockTxids.ts index 724525a65..479721c71 100644 --- a/src/client/v2/algod/getBlockTxids.ts +++ b/src/client/v2/algod/getBlockTxids.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { BlockTxidsResponse } from './models/types.js'; -export default class GetBlockTxids extends JSONRequest< - BlockTxidsResponse, - Record -> { +export default class GetBlockTxids extends JSONRequest { round: number; constructor(c: HTTPClient, roundNumber: number) { @@ -20,9 +18,7 @@ export default class GetBlockTxids extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): BlockTxidsResponse { - return BlockTxidsResponse.fromEncodingData( - BlockTxidsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): BlockTxidsResponse { + return decodeJSON(response.getJSONText(), BlockTxidsResponse); } } diff --git a/src/client/v2/algod/getLedgerStateDelta.ts b/src/client/v2/algod/getLedgerStateDelta.ts index 0e722ed95..131154c6b 100644 --- a/src/client/v2/algod/getLedgerStateDelta.ts +++ b/src/client/v2/algod/getLedgerStateDelta.ts @@ -1,17 +1,24 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeMsgpack } from '../../../encoding/encoding.js'; +import { LedgerStateDelta } from '../../../types/statedelta.js'; -export default class GetLedgerStateDelta extends JSONRequest { +export default class GetLedgerStateDelta extends JSONRequest { constructor( c: HTTPClient, private round: number ) { super(c); - this.query = { format: 'json' }; + this.query = { format: 'msgpack' }; } // eslint-disable-next-line class-methods-use-this path() { return `/v2/deltas/${this.round}`; } + + // eslint-disable-next-line class-methods-use-this + prepare(response: HTTPClientResponse): LedgerStateDelta { + return decodeMsgpack(response.body, LedgerStateDelta); + } } diff --git a/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts b/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts index bd3738e43..c566ea777 100644 --- a/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts +++ b/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts @@ -1,17 +1,24 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeMsgpack } from '../../../encoding/encoding.js'; +import { LedgerStateDelta } from '../../../types/statedelta.js'; -export default class GetLedgerStateDeltaForTransactionGroup extends JSONRequest { +export default class GetLedgerStateDeltaForTransactionGroup extends JSONRequest { constructor( c: HTTPClient, private id: string ) { super(c); - this.query = { format: 'json' }; + this.query = { format: 'msgpack' }; } // eslint-disable-next-line class-methods-use-this path() { return `/v2/deltas/txn/group/${this.id}`; } + + // eslint-disable-next-line class-methods-use-this + prepare(response: HTTPClientResponse): LedgerStateDelta { + return decodeMsgpack(response.body, LedgerStateDelta); + } } diff --git a/src/client/v2/algod/getSyncRound.ts b/src/client/v2/algod/getSyncRound.ts index a7992e824..20648f574 100644 --- a/src/client/v2/algod/getSyncRound.ts +++ b/src/client/v2/algod/getSyncRound.ts @@ -1,19 +1,16 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { GetSyncRoundResponse } from './models/types.js'; -export default class GetSyncRound extends JSONRequest< - GetSyncRoundResponse, - Record -> { +export default class GetSyncRound extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return `/v2/ledger/sync`; } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): GetSyncRoundResponse { - return GetSyncRoundResponse.fromEncodingData( - GetSyncRoundResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): GetSyncRoundResponse { + return decodeJSON(response.getJSONText(), GetSyncRoundResponse); } } diff --git a/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts b/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts index fea48f877..3e17b6ef0 100644 --- a/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts +++ b/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts @@ -1,17 +1,15 @@ import JSONRequest from '../jsonrequest.js'; import { TransactionGroupLedgerStateDeltasForRoundResponse } from './models/types.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeMsgpack } from '../../../encoding/encoding.js'; -export default class GetTransactionGroupLedgerStateDeltasForRound extends JSONRequest< - TransactionGroupLedgerStateDeltasForRoundResponse, - Record -> { +export default class GetTransactionGroupLedgerStateDeltasForRound extends JSONRequest { constructor( c: HTTPClient, private round: number ) { super(c); - this.query = { format: 'json' }; + this.query = { format: 'msgpack' }; } // eslint-disable-next-line class-methods-use-this @@ -21,12 +19,11 @@ export default class GetTransactionGroupLedgerStateDeltasForRound extends JSONRe // eslint-disable-next-line class-methods-use-this prepare( - body: Record + response: HTTPClientResponse ): TransactionGroupLedgerStateDeltasForRoundResponse { - return TransactionGroupLedgerStateDeltasForRoundResponse.fromEncodingData( - TransactionGroupLedgerStateDeltasForRoundResponse.encodingSchema.fromPreparedJSON( - body - ) + return decodeMsgpack( + response.body, + TransactionGroupLedgerStateDeltasForRoundResponse ); } } diff --git a/src/client/v2/algod/getTransactionProof.ts b/src/client/v2/algod/getTransactionProof.ts index a5ddd1c98..ac3d5c524 100644 --- a/src/client/v2/algod/getTransactionProof.ts +++ b/src/client/v2/algod/getTransactionProof.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { TransactionProofResponse } from './models/types.js'; -export default class GetTransactionProof extends JSONRequest< - TransactionProofResponse, - Record -> { +export default class GetTransactionProof extends JSONRequest { constructor( c: HTTPClient, private round: number, @@ -41,9 +39,7 @@ export default class GetTransactionProof extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): TransactionProofResponse { - return TransactionProofResponse.fromEncodingData( - TransactionProofResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): TransactionProofResponse { + return decodeJSON(response.getJSONText(), TransactionProofResponse); } } diff --git a/src/client/v2/algod/healthCheck.ts b/src/client/v2/algod/healthCheck.ts index 7db804e84..9f0254778 100644 --- a/src/client/v2/algod/healthCheck.ts +++ b/src/client/v2/algod/healthCheck.ts @@ -1,25 +1,15 @@ import JSONRequest from '../jsonrequest.js'; -import IntDecoding from '../../../types/intDecoding.js'; +import { HTTPClientResponse } from '../../client.js'; /** * healthCheck returns an empty object iff the node is running */ -export default class HealthCheck extends JSONRequest { +export default class HealthCheck extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return '/health'; } - async do(headers = {}) { - const res = await this.c.get({ - relativePath: this.path(), - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, - requestHeaders: headers, - }); - if (!res.ok) { - throw new Error(`Health response: ${res.status}`); - } - return {}; - } + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars + prepare(_response: HTTPClientResponse): void {} } diff --git a/src/client/v2/algod/lightBlockHeaderProof.ts b/src/client/v2/algod/lightBlockHeaderProof.ts index d39c223b8..37e6720f3 100644 --- a/src/client/v2/algod/lightBlockHeaderProof.ts +++ b/src/client/v2/algod/lightBlockHeaderProof.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { LightBlockHeaderProof as LBHP } from './models/types.js'; -export default class LightBlockHeaderProof extends JSONRequest< - LBHP, - Record -> { +export default class LightBlockHeaderProof extends JSONRequest { constructor( c: HTTPClient, private round: number @@ -18,7 +16,7 @@ export default class LightBlockHeaderProof extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): LBHP { - return LBHP.fromEncodingData(LBHP.encodingSchema.fromPreparedJSON(body)); + prepare(response: HTTPClientResponse): LBHP { + return decodeJSON(response.getJSONText(), LBHP); } } diff --git a/src/client/v2/algod/models/types.ts b/src/client/v2/algod/models/types.ts index acd84a3ab..7fc46ea60 100644 --- a/src/client/v2/algod/models/types.ts +++ b/src/client/v2/algod/models/types.ts @@ -16,6 +16,7 @@ import { } from '../../../../encoding/schema/index.js'; import { base64ToBytes } from '../../../../encoding/binarydata.js'; import { Block } from '../../../../types/block.js'; +import { LedgerStateDelta } from '../../../../types/statedelta.js'; import { SignedTransaction } from '../../../../signedTransaction.js'; import { Address } from '../../../../encoding/address.js'; import { UntypedValue } from '../../untypedmodel.js'; @@ -4668,7 +4669,7 @@ export class LedgerStateDeltaForTransactionGroup implements Encodable { (this.encodingSchemaValue as NamedMapSchema).pushEntries( { key: 'Delta', - valueSchema: UntypedValue.encodingSchema, + valueSchema: LedgerStateDelta.encodingSchema, omitEmpty: true, }, { @@ -4684,7 +4685,7 @@ export class LedgerStateDeltaForTransactionGroup implements Encodable { /** * Ledger StateDelta object */ - public delta: UntypedValue; + public delta: LedgerStateDelta; public ids: string[]; @@ -4693,7 +4694,7 @@ export class LedgerStateDeltaForTransactionGroup implements Encodable { * @param delta - Ledger StateDelta object * @param ids - */ - constructor({ delta, ids }: { delta: UntypedValue; ids: string[] }) { + constructor({ delta, ids }: { delta: LedgerStateDelta; ids: string[] }) { this.delta = delta; this.ids = ids; } @@ -4717,7 +4718,7 @@ export class LedgerStateDeltaForTransactionGroup implements Encodable { ); } return new LedgerStateDeltaForTransactionGroup({ - delta: UntypedValue.fromEncodingData(data.get('Delta') ?? new Map()), + delta: LedgerStateDelta.fromEncodingData(data.get('Delta') ?? new Map()), ids: data.get('Ids'), }); } diff --git a/src/client/v2/algod/pendingTransactionInformation.ts b/src/client/v2/algod/pendingTransactionInformation.ts index e4e0e49e0..8fcb3a231 100644 --- a/src/client/v2/algod/pendingTransactionInformation.ts +++ b/src/client/v2/algod/pendingTransactionInformation.ts @@ -1,15 +1,12 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; -import * as encoding from '../../../encoding/encoding.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeMsgpack } from '../../../encoding/encoding.js'; import { PendingTransactionResponse } from './models/types.js'; /** * returns the transaction information for a specific txid of a pending transaction */ -export default class PendingTransactionInformation extends JSONRequest< - PendingTransactionResponse, - Uint8Array -> { +export default class PendingTransactionInformation extends JSONRequest { constructor( c: HTTPClient, private txid: string @@ -19,8 +16,8 @@ export default class PendingTransactionInformation extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Uint8Array): PendingTransactionResponse { - return encoding.decodeMsgpack(body, PendingTransactionResponse); + prepare(response: HTTPClientResponse): PendingTransactionResponse { + return decodeMsgpack(response.body, PendingTransactionResponse); } path() { diff --git a/src/client/v2/algod/pendingTransactions.ts b/src/client/v2/algod/pendingTransactions.ts index 71a7b8cac..347674a67 100644 --- a/src/client/v2/algod/pendingTransactions.ts +++ b/src/client/v2/algod/pendingTransactions.ts @@ -1,15 +1,12 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; -import * as encoding from '../../../encoding/encoding.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeMsgpack } from '../../../encoding/encoding.js'; import { PendingTransactionsResponse } from './models/types.js'; /** * pendingTransactionsInformation returns transactions that are pending in the pool */ -export default class PendingTransactions extends JSONRequest< - PendingTransactionsResponse, - Uint8Array -> { +export default class PendingTransactions extends JSONRequest { constructor(c: HTTPClient) { super(c); this.query.format = 'msgpack'; @@ -20,8 +17,8 @@ export default class PendingTransactions extends JSONRequest< return '/v2/transactions/pending'; } - prepare(body: Uint8Array): PendingTransactionsResponse { - return encoding.decodeMsgpack(body, PendingTransactionsResponse); + prepare(response: HTTPClientResponse): PendingTransactionsResponse { + return decodeMsgpack(response.body, PendingTransactionsResponse); } /* eslint-enable class-methods-use-this */ diff --git a/src/client/v2/algod/pendingTransactionsByAddress.ts b/src/client/v2/algod/pendingTransactionsByAddress.ts index 6c1e448ad..7dbdd01eb 100644 --- a/src/client/v2/algod/pendingTransactionsByAddress.ts +++ b/src/client/v2/algod/pendingTransactionsByAddress.ts @@ -1,16 +1,13 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; -import * as encoding from '../../../encoding/encoding.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeMsgpack } from '../../../encoding/encoding.js'; import { PendingTransactionsResponse } from './models/types.js'; import { Address } from '../../../encoding/address.js'; /** * returns all transactions for a PK [addr] in the [first, last] rounds range. */ -export default class PendingTransactionsByAddress extends JSONRequest< - PendingTransactionsResponse, - Uint8Array -> { +export default class PendingTransactionsByAddress extends JSONRequest { private address: string; constructor(c: HTTPClient, address: string | Address) { @@ -20,8 +17,8 @@ export default class PendingTransactionsByAddress extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Uint8Array): PendingTransactionsResponse { - return encoding.decodeMsgpack(body, PendingTransactionsResponse); + prepare(response: HTTPClientResponse): PendingTransactionsResponse { + return decodeMsgpack(response.body, PendingTransactionsResponse); } path() { diff --git a/src/client/v2/algod/ready.ts b/src/client/v2/algod/ready.ts index 1ee2bc7c1..466f6c863 100644 --- a/src/client/v2/algod/ready.ts +++ b/src/client/v2/algod/ready.ts @@ -1,8 +1,12 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; -export default class Ready extends JSONRequest { +export default class Ready extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return `/ready`; } + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars + prepare(_response: HTTPClientResponse): void {} } diff --git a/src/client/v2/algod/sendRawTransaction.ts b/src/client/v2/algod/sendRawTransaction.ts index 031c48881..796cac216 100644 --- a/src/client/v2/algod/sendRawTransaction.ts +++ b/src/client/v2/algod/sendRawTransaction.ts @@ -1,7 +1,7 @@ import { concatArrays } from '../../../utils/utils.js'; -import IntDecoding from '../../../types/intDecoding.js'; import { PostTransactionsResponse } from './models/types.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import JSONRequest from '../jsonrequest.js'; /** @@ -27,10 +27,7 @@ function isByteArray(array: any): array is Uint8Array { /** * broadcasts the passed signed txns to the network */ -export default class SendRawTransaction extends JSONRequest< - PostTransactionsResponse, - Record -> { +export default class SendRawTransaction extends JSONRequest { private txnBytesToPost: Uint8Array; constructor(c: HTTPClient, stxOrStxs: Uint8Array | Uint8Array[]) { @@ -54,22 +51,19 @@ export default class SendRawTransaction extends JSONRequest< return '/v2/transactions'; } - async do(headers = {}) { + protected executeRequest( + headers: Record + ): Promise { const txHeaders = setSendTransactionHeaders(headers); - const res = await this.c.post({ + return this.c.post({ relativePath: this.path(), data: this.txnBytesToPost, - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, requestHeaders: txHeaders, }); - return this.prepare(res.body); } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): PostTransactionsResponse { - return PostTransactionsResponse.fromEncodingData( - PostTransactionsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): PostTransactionsResponse { + return decodeJSON(response.getJSONText(), PostTransactionsResponse); } } diff --git a/src/client/v2/algod/setBlockOffsetTimestamp.ts b/src/client/v2/algod/setBlockOffsetTimestamp.ts index 61db717e5..6f1bc2ea1 100644 --- a/src/client/v2/algod/setBlockOffsetTimestamp.ts +++ b/src/client/v2/algod/setBlockOffsetTimestamp.ts @@ -1,8 +1,7 @@ import JSONRequest from '../jsonrequest.js'; -import IntDecoding from '../../../types/intDecoding.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; -export default class SetBlockOffsetTimestamp extends JSONRequest { +export default class SetBlockOffsetTimestamp extends JSONRequest { constructor( c: HTTPClient, private offset: number @@ -14,14 +13,16 @@ export default class SetBlockOffsetTimestamp extends JSONRequest { return `/v2/devmode/blocks/offset/${this.offset}`; } - async do(headers = {}) { - const res = await this.c.post({ + protected executeRequest( + headers: Record + ): Promise { + return this.c.post({ relativePath: this.path(), data: null, - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, requestHeaders: headers, }); - return res.body; } + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars + prepare(_response: HTTPClientResponse): void {} } diff --git a/src/client/v2/algod/setSyncRound.ts b/src/client/v2/algod/setSyncRound.ts index 40d55f37e..339688bbd 100644 --- a/src/client/v2/algod/setSyncRound.ts +++ b/src/client/v2/algod/setSyncRound.ts @@ -1,8 +1,7 @@ import JSONRequest from '../jsonrequest.js'; -import IntDecoding from '../../../types/intDecoding.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; -export default class SetSyncRound extends JSONRequest { +export default class SetSyncRound extends JSONRequest { constructor( c: HTTPClient, private round: number @@ -14,14 +13,16 @@ export default class SetSyncRound extends JSONRequest { return `/v2/ledger/sync/${this.round}`; } - async do(headers = {}) { - const res = await this.c.post({ + protected executeRequest( + headers: Record + ): Promise { + return this.c.post({ relativePath: this.path(), data: null, - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, requestHeaders: headers, }); - return res.body; } + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars + prepare(_response: HTTPClientResponse): void {} } diff --git a/src/client/v2/algod/simulateTransaction.ts b/src/client/v2/algod/simulateTransaction.ts index f8a1d5888..382c5a879 100644 --- a/src/client/v2/algod/simulateTransaction.ts +++ b/src/client/v2/algod/simulateTransaction.ts @@ -1,6 +1,5 @@ -import * as encoding from '../../../encoding/encoding.js'; -import IntDecoding from '../../../types/intDecoding.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { encodeMsgpack, decodeMsgpack } from '../../../encoding/encoding.js'; import JSONRequest from '../jsonrequest.js'; import { SimulateRequest, SimulateResponse } from './models/types.js'; @@ -23,16 +22,13 @@ export function setSimulateTransactionsHeaders( /** * Simulates signed txns. */ -export default class SimulateRawTransactions extends JSONRequest< - SimulateResponse, - Uint8Array -> { +export default class SimulateRawTransactions extends JSONRequest { private requestBytes: Uint8Array; constructor(c: HTTPClient, request: SimulateRequest) { super(c); this.query.format = 'msgpack'; - this.requestBytes = encoding.encodeMsgpack(request); + this.requestBytes = encodeMsgpack(request); } // eslint-disable-next-line class-methods-use-this @@ -40,21 +36,20 @@ export default class SimulateRawTransactions extends JSONRequest< return '/v2/transactions/simulate'; } - async do(headers = {}) { + protected executeRequest( + headers: Record + ): Promise { const txHeaders = setSimulateTransactionsHeaders(headers); - const res = await this.c.post({ + return this.c.post({ relativePath: this.path(), data: this.requestBytes, - parseBody: false, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, query: this.query, requestHeaders: txHeaders, }); - return this.prepare(res.body); } // eslint-disable-next-line class-methods-use-this - prepare(body: Uint8Array): SimulateResponse { - return encoding.decodeMsgpack(body, SimulateResponse); + prepare(response: HTTPClientResponse): SimulateResponse { + return decodeMsgpack(response.body, SimulateResponse); } } diff --git a/src/client/v2/algod/stateproof.ts b/src/client/v2/algod/stateproof.ts index ead661885..c61f692e9 100644 --- a/src/client/v2/algod/stateproof.ts +++ b/src/client/v2/algod/stateproof.ts @@ -1,8 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { StateProof as SP } from './models/types.js'; -export default class StateProof extends JSONRequest> { +export default class StateProof extends JSONRequest { constructor( c: HTTPClient, private round: number @@ -15,7 +16,7 @@ export default class StateProof extends JSONRequest> { } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): SP { - return SP.fromEncodingData(SP.encodingSchema.fromPreparedJSON(body)); + prepare(response: HTTPClientResponse): SP { + return decodeJSON(response.getJSONText(), SP); } } diff --git a/src/client/v2/algod/status.ts b/src/client/v2/algod/status.ts index 2c79f4eb0..a3d48f4a2 100644 --- a/src/client/v2/algod/status.ts +++ b/src/client/v2/algod/status.ts @@ -1,19 +1,16 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { NodeStatusResponse } from './models/types.js'; -export default class Status extends JSONRequest< - NodeStatusResponse, - Record -> { +export default class Status extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return '/v2/status'; } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): NodeStatusResponse { - return NodeStatusResponse.fromEncodingData( - NodeStatusResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): NodeStatusResponse { + return decodeJSON(response.getJSONText(), NodeStatusResponse); } } diff --git a/src/client/v2/algod/statusAfterBlock.ts b/src/client/v2/algod/statusAfterBlock.ts index 511cee78f..43cba1069 100644 --- a/src/client/v2/algod/statusAfterBlock.ts +++ b/src/client/v2/algod/statusAfterBlock.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { NodeStatusResponse } from './models/types.js'; -export default class StatusAfterBlock extends JSONRequest< - NodeStatusResponse, - Record -> { +export default class StatusAfterBlock extends JSONRequest { constructor( c: HTTPClient, private round: number @@ -19,9 +17,7 @@ export default class StatusAfterBlock extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): NodeStatusResponse { - return NodeStatusResponse.fromEncodingData( - NodeStatusResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): NodeStatusResponse { + return decodeJSON(response.getJSONText(), NodeStatusResponse); } } diff --git a/src/client/v2/algod/suggestedParams.ts b/src/client/v2/algod/suggestedParams.ts index c36c224d7..df5c393ef 100644 --- a/src/client/v2/algod/suggestedParams.ts +++ b/src/client/v2/algod/suggestedParams.ts @@ -1,6 +1,8 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; +import { TransactionParametersResponse } from './models/types.js'; import { SuggestedParams } from '../../../types/transactions/base.js'; -import { base64ToBytes } from '../../../encoding/binarydata.js'; /** * SuggestedParamsFromAlgod contains the suggested parameters for a new transaction, as returned by @@ -27,25 +29,26 @@ export interface SuggestedParamsFromAlgod extends SuggestedParams { /** * Returns the common needed parameters for a new transaction, in a format the transaction builder expects */ -export default class SuggestedParamsRequest extends JSONRequest< - SuggestedParamsFromAlgod, - Record -> { +export default class SuggestedParamsRequest extends JSONRequest { /* eslint-disable class-methods-use-this */ path() { return '/v2/transactions/params'; } - prepare(body: Record): SuggestedParamsFromAlgod { + prepare(response: HTTPClientResponse): SuggestedParamsFromAlgod { + const params = decodeJSON( + response.getJSONText(), + TransactionParametersResponse + ); return { flatFee: false, - fee: BigInt(body.fee), - firstValid: BigInt(body['last-round']), - lastValid: BigInt(body['last-round']) + BigInt(1000), - genesisID: body['genesis-id'], - genesisHash: base64ToBytes(body['genesis-hash']), - minFee: BigInt(body['min-fee']), - consensusVersion: body['consensus-version'], + fee: params.fee, + firstValid: params.lastRound, + lastValid: params.lastRound + BigInt(1000), + genesisID: params.genesisId, + genesisHash: params.genesisHash, + minFee: params.minFee, + consensusVersion: params.consensusVersion, }; } /* eslint-enable class-methods-use-this */ diff --git a/src/client/v2/algod/supply.ts b/src/client/v2/algod/supply.ts index 578890803..523eb3cb9 100644 --- a/src/client/v2/algod/supply.ts +++ b/src/client/v2/algod/supply.ts @@ -1,19 +1,16 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { SupplyResponse } from './models/types.js'; -export default class Supply extends JSONRequest< - SupplyResponse, - Record -> { +export default class Supply extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return '/v2/ledger/supply'; } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): SupplyResponse { - return SupplyResponse.fromEncodingData( - SupplyResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): SupplyResponse { + return decodeJSON(response.getJSONText(), SupplyResponse); } } diff --git a/src/client/v2/algod/unsetSyncRound.ts b/src/client/v2/algod/unsetSyncRound.ts index cb2a88057..63407fdd7 100644 --- a/src/client/v2/algod/unsetSyncRound.ts +++ b/src/client/v2/algod/unsetSyncRound.ts @@ -1,20 +1,22 @@ import JSONRequest from '../jsonrequest.js'; -import IntDecoding from '../../../types/intDecoding.js'; +import { HTTPClientResponse } from '../../client.js'; -export default class UnsetSyncRound extends JSONRequest { +export default class UnsetSyncRound extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return `/v2/ledger/sync`; } - async do(headers = {}) { - const res = await this.c.delete({ + protected executeRequest( + headers: Record + ): Promise { + return this.c.delete({ relativePath: this.path(), data: undefined, - parseBody: false, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, requestHeaders: headers, }); - return res.body; } + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars + prepare(_response: HTTPClientResponse): void {} } diff --git a/src/client/v2/algod/versions.ts b/src/client/v2/algod/versions.ts index 42ce94c5c..82753d144 100644 --- a/src/client/v2/algod/versions.ts +++ b/src/client/v2/algod/versions.ts @@ -1,22 +1,19 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Version } from './models/types.js'; /** * retrieves the VersionResponse from the running node */ -export default class Versions extends JSONRequest< - Version, - Record -> { +export default class Versions extends JSONRequest { // eslint-disable-next-line class-methods-use-this path() { return '/versions'; } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): Version { - return Version.fromEncodingData( - Version.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): Version { + return decodeJSON(response.getJSONText(), Version); } } diff --git a/src/client/v2/indexer/lookupAccountAppLocalStates.ts b/src/client/v2/indexer/lookupAccountAppLocalStates.ts index 1eb2722e2..20ecf3c61 100644 --- a/src/client/v2/indexer/lookupAccountAppLocalStates.ts +++ b/src/client/v2/indexer/lookupAccountAppLocalStates.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { ApplicationLocalStatesResponse } from './models/types.js'; -export default class LookupAccountAppLocalStates extends JSONRequest< - ApplicationLocalStatesResponse, - Record -> { +export default class LookupAccountAppLocalStates extends JSONRequest { private account: string | Address; /** @@ -141,9 +139,7 @@ export default class LookupAccountAppLocalStates extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): ApplicationLocalStatesResponse { - return ApplicationLocalStatesResponse.fromEncodingData( - ApplicationLocalStatesResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): ApplicationLocalStatesResponse { + return decodeJSON(response.getJSONText(), ApplicationLocalStatesResponse); } } diff --git a/src/client/v2/indexer/lookupAccountAssets.ts b/src/client/v2/indexer/lookupAccountAssets.ts index 6b7a9c28b..40087921e 100644 --- a/src/client/v2/indexer/lookupAccountAssets.ts +++ b/src/client/v2/indexer/lookupAccountAssets.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { AssetHoldingsResponse } from './models/types.js'; -export default class LookupAccountAssets extends JSONRequest< - AssetHoldingsResponse, - Record -> { +export default class LookupAccountAssets extends JSONRequest { private account: string; /** @@ -142,9 +140,7 @@ export default class LookupAccountAssets extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AssetHoldingsResponse { - return AssetHoldingsResponse.fromEncodingData( - AssetHoldingsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AssetHoldingsResponse { + return decodeJSON(response.getJSONText(), AssetHoldingsResponse); } } diff --git a/src/client/v2/indexer/lookupAccountByID.ts b/src/client/v2/indexer/lookupAccountByID.ts index b9f72a2bf..cb9cc0dfa 100644 --- a/src/client/v2/indexer/lookupAccountByID.ts +++ b/src/client/v2/indexer/lookupAccountByID.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { AccountResponse } from './models/types.js'; -export default class LookupAccountByID extends JSONRequest< - AccountResponse, - Record -> { +export default class LookupAccountByID extends JSONRequest { private account: string; /** @@ -110,9 +108,7 @@ export default class LookupAccountByID extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AccountResponse { - return AccountResponse.fromEncodingData( - AccountResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AccountResponse { + return decodeJSON(response.getJSONText(), AccountResponse); } } diff --git a/src/client/v2/indexer/lookupAccountCreatedApplications.ts b/src/client/v2/indexer/lookupAccountCreatedApplications.ts index 605cd3136..f27ef9a16 100644 --- a/src/client/v2/indexer/lookupAccountCreatedApplications.ts +++ b/src/client/v2/indexer/lookupAccountCreatedApplications.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { ApplicationsResponse } from './models/types.js'; -export default class LookupAccountCreatedApplications extends JSONRequest< - ApplicationsResponse, - Record -> { +export default class LookupAccountCreatedApplications extends JSONRequest { private account: string; /** @@ -142,9 +140,7 @@ export default class LookupAccountCreatedApplications extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): ApplicationsResponse { - return ApplicationsResponse.fromEncodingData( - ApplicationsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): ApplicationsResponse { + return decodeJSON(response.getJSONText(), ApplicationsResponse); } } diff --git a/src/client/v2/indexer/lookupAccountCreatedAssets.ts b/src/client/v2/indexer/lookupAccountCreatedAssets.ts index 52a568da4..2b138cc36 100644 --- a/src/client/v2/indexer/lookupAccountCreatedAssets.ts +++ b/src/client/v2/indexer/lookupAccountCreatedAssets.ts @@ -1,12 +1,10 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { AssetsResponse } from './models/types.js'; -export default class LookupAccountCreatedAssets extends JSONRequest< - AssetsResponse, - Record -> { +export default class LookupAccountCreatedAssets extends JSONRequest { private account: string; /** @@ -143,9 +141,7 @@ export default class LookupAccountCreatedAssets extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AssetsResponse { - return AssetsResponse.fromEncodingData( - AssetsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AssetsResponse { + return decodeJSON(response.getJSONText(), AssetsResponse); } } diff --git a/src/client/v2/indexer/lookupAccountTransactions.ts b/src/client/v2/indexer/lookupAccountTransactions.ts index d39b7dcfc..61816c4f3 100644 --- a/src/client/v2/indexer/lookupAccountTransactions.ts +++ b/src/client/v2/indexer/lookupAccountTransactions.ts @@ -1,5 +1,6 @@ import { bytesToBase64 } from '../../../encoding/binarydata.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import JSONRequest from '../jsonrequest.js'; import { Address } from '../../../encoding/address.js'; import { TransactionsResponse } from './models/types.js'; @@ -16,10 +17,7 @@ export function base64StringFunnel(data: Uint8Array | string) { return bytesToBase64(data); } -export default class LookupAccountTransactions extends JSONRequest< - TransactionsResponse, - Record -> { +export default class LookupAccountTransactions extends JSONRequest { private account: string; /** @@ -396,9 +394,7 @@ export default class LookupAccountTransactions extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): TransactionsResponse { - return TransactionsResponse.fromEncodingData( - TransactionsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): TransactionsResponse { + return decodeJSON(response.getJSONText(), TransactionsResponse); } } diff --git a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts index 132936ac1..2190f367a 100644 --- a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts +++ b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts @@ -1,12 +1,10 @@ import { bytesToBase64 } from '../../../encoding/binarydata.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import JSONRequest from '../jsonrequest.js'; import { Box } from './models/types.js'; -export default class LookupApplicationBoxByIDandName extends JSONRequest< - Box, - Record -> { +export default class LookupApplicationBoxByIDandName extends JSONRequest { /** * Returns information about indexed application boxes. * @@ -42,7 +40,7 @@ export default class LookupApplicationBoxByIDandName extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): Box { - return Box.fromEncodingData(Box.encodingSchema.fromPreparedJSON(body)); + prepare(response: HTTPClientResponse): Box { + return decodeJSON(response.getJSONText(), Box); } } diff --git a/src/client/v2/indexer/lookupApplicationLogs.ts b/src/client/v2/indexer/lookupApplicationLogs.ts index eb3c81d94..3eb8c2545 100644 --- a/src/client/v2/indexer/lookupApplicationLogs.ts +++ b/src/client/v2/indexer/lookupApplicationLogs.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { ApplicationLogsResponse } from './models/types.js'; -export default class LookupApplicationLogs extends JSONRequest< - ApplicationLogsResponse, - Record -> { +export default class LookupApplicationLogs extends JSONRequest { /** * Returns log messages generated by the passed in application. * @@ -160,9 +158,7 @@ export default class LookupApplicationLogs extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): ApplicationLogsResponse { - return ApplicationLogsResponse.fromEncodingData( - ApplicationLogsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): ApplicationLogsResponse { + return decodeJSON(response.getJSONText(), ApplicationLogsResponse); } } diff --git a/src/client/v2/indexer/lookupApplications.ts b/src/client/v2/indexer/lookupApplications.ts index 08e9da26d..1148f4d99 100644 --- a/src/client/v2/indexer/lookupApplications.ts +++ b/src/client/v2/indexer/lookupApplications.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { ApplicationResponse } from './models/types.js'; -export default class LookupApplications extends JSONRequest< - ApplicationResponse, - Record -> { +export default class LookupApplications extends JSONRequest { /** * Returns information about the passed application. * @@ -63,9 +61,7 @@ export default class LookupApplications extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): ApplicationResponse { - return ApplicationResponse.fromEncodingData( - ApplicationResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): ApplicationResponse { + return decodeJSON(response.getJSONText(), ApplicationResponse); } } diff --git a/src/client/v2/indexer/lookupAssetBalances.ts b/src/client/v2/indexer/lookupAssetBalances.ts index 94438c254..1c1eea06b 100644 --- a/src/client/v2/indexer/lookupAssetBalances.ts +++ b/src/client/v2/indexer/lookupAssetBalances.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { AssetBalancesResponse } from './models/types.js'; -export default class LookupAssetBalances extends JSONRequest< - AssetBalancesResponse, - Record -> { +export default class LookupAssetBalances extends JSONRequest { /** * Returns the list of accounts which hold the given asset and their balance. * @@ -151,9 +149,7 @@ export default class LookupAssetBalances extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AssetBalancesResponse { - return AssetBalancesResponse.fromEncodingData( - AssetBalancesResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AssetBalancesResponse { + return decodeJSON(response.getJSONText(), AssetBalancesResponse); } } diff --git a/src/client/v2/indexer/lookupAssetByID.ts b/src/client/v2/indexer/lookupAssetByID.ts index 19d5d95ca..7cd85ad44 100644 --- a/src/client/v2/indexer/lookupAssetByID.ts +++ b/src/client/v2/indexer/lookupAssetByID.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { AssetResponse } from './models/types.js'; -export default class LookupAssetByID extends JSONRequest< - AssetResponse, - Record -> { +export default class LookupAssetByID extends JSONRequest { /** * Returns asset information of the queried asset. * @@ -62,9 +60,7 @@ export default class LookupAssetByID extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AssetResponse { - return AssetResponse.fromEncodingData( - AssetResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AssetResponse { + return decodeJSON(response.getJSONText(), AssetResponse); } } diff --git a/src/client/v2/indexer/lookupAssetTransactions.ts b/src/client/v2/indexer/lookupAssetTransactions.ts index 4fafe7e65..6b73a7511 100644 --- a/src/client/v2/indexer/lookupAssetTransactions.ts +++ b/src/client/v2/indexer/lookupAssetTransactions.ts @@ -1,13 +1,11 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { base64StringFunnel } from './lookupAccountTransactions.js'; import { Address } from '../../../encoding/address.js'; import { TransactionsResponse } from './models/types.js'; -export default class LookupAssetTransactions extends JSONRequest< - TransactionsResponse, - Record -> { +export default class LookupAssetTransactions extends JSONRequest { /** * Returns transactions relating to the given asset. * @@ -403,9 +401,7 @@ export default class LookupAssetTransactions extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): TransactionsResponse { - return TransactionsResponse.fromEncodingData( - TransactionsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): TransactionsResponse { + return decodeJSON(response.getJSONText(), TransactionsResponse); } } diff --git a/src/client/v2/indexer/lookupBlock.ts b/src/client/v2/indexer/lookupBlock.ts index 98f459574..8771590df 100644 --- a/src/client/v2/indexer/lookupBlock.ts +++ b/src/client/v2/indexer/lookupBlock.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Block } from './models/types.js'; -export default class LookupBlock extends JSONRequest< - Block, - Record -> { +export default class LookupBlock extends JSONRequest { /** * Returns the block for the passed round. * @@ -43,7 +41,7 @@ export default class LookupBlock extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): Block { - return Block.fromEncodingData(Block.encodingSchema.fromPreparedJSON(body)); + prepare(response: HTTPClientResponse): Block { + return decodeJSON(response.getJSONText(), Block); } } diff --git a/src/client/v2/indexer/lookupTransactionByID.ts b/src/client/v2/indexer/lookupTransactionByID.ts index fbb647631..a01fa6f85 100644 --- a/src/client/v2/indexer/lookupTransactionByID.ts +++ b/src/client/v2/indexer/lookupTransactionByID.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { TransactionResponse } from './models/types.js'; -export default class LookupTransactionByID extends JSONRequest< - TransactionResponse, - Record -> { +export default class LookupTransactionByID extends JSONRequest { /** * Returns information about the given transaction. * @@ -34,9 +32,7 @@ export default class LookupTransactionByID extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): TransactionResponse { - return TransactionResponse.fromEncodingData( - TransactionResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): TransactionResponse { + return decodeJSON(response.getJSONText(), TransactionResponse); } } diff --git a/src/client/v2/indexer/makeHealthCheck.ts b/src/client/v2/indexer/makeHealthCheck.ts index baa3a6956..415ca738e 100644 --- a/src/client/v2/indexer/makeHealthCheck.ts +++ b/src/client/v2/indexer/makeHealthCheck.ts @@ -1,4 +1,6 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { HealthCheck } from './models/types.js'; /** @@ -13,10 +15,7 @@ import { HealthCheck } from './models/types.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-health) * @category GET */ -export default class MakeHealthCheck extends JSONRequest< - HealthCheck, - Record -> { +export default class MakeHealthCheck extends JSONRequest { /** * @returns `/health` */ @@ -26,9 +25,7 @@ export default class MakeHealthCheck extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): HealthCheck { - return HealthCheck.fromEncodingData( - HealthCheck.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): HealthCheck { + return decodeJSON(response.getJSONText(), HealthCheck); } } diff --git a/src/client/v2/indexer/searchAccounts.ts b/src/client/v2/indexer/searchAccounts.ts index 456f26f38..8acb55fe2 100644 --- a/src/client/v2/indexer/searchAccounts.ts +++ b/src/client/v2/indexer/searchAccounts.ts @@ -1,4 +1,6 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { AccountsResponse } from './models/types.js'; @@ -13,10 +15,7 @@ import { AccountsResponse } from './models/types.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2accounts) * @category GET */ -export default class SearchAccounts extends JSONRequest< - AccountsResponse, - Record -> { +export default class SearchAccounts extends JSONRequest { /** * @returns `/v2/accounts` */ @@ -273,9 +272,7 @@ export default class SearchAccounts extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AccountsResponse { - return AccountsResponse.fromEncodingData( - AccountsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AccountsResponse { + return decodeJSON(response.getJSONText(), AccountsResponse); } } diff --git a/src/client/v2/indexer/searchForApplicationBoxes.ts b/src/client/v2/indexer/searchForApplicationBoxes.ts index ae29938f7..b3186290f 100644 --- a/src/client/v2/indexer/searchForApplicationBoxes.ts +++ b/src/client/v2/indexer/searchForApplicationBoxes.ts @@ -1,11 +1,9 @@ import JSONRequest from '../jsonrequest.js'; -import { HTTPClient } from '../../client.js'; +import { HTTPClient, HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { BoxesResponse } from './models/types.js'; -export default class SearchForApplicationBoxes extends JSONRequest< - BoxesResponse, - Record -> { +export default class SearchForApplicationBoxes extends JSONRequest { /** * Returns information about indexed application boxes. * @@ -96,9 +94,7 @@ export default class SearchForApplicationBoxes extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): BoxesResponse { - return BoxesResponse.fromEncodingData( - BoxesResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): BoxesResponse { + return decodeJSON(response.getJSONText(), BoxesResponse); } } diff --git a/src/client/v2/indexer/searchForApplications.ts b/src/client/v2/indexer/searchForApplications.ts index 4ec9a98fc..d18017922 100644 --- a/src/client/v2/indexer/searchForApplications.ts +++ b/src/client/v2/indexer/searchForApplications.ts @@ -1,4 +1,6 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { ApplicationsResponse } from './models/types.js'; @@ -13,10 +15,7 @@ import { ApplicationsResponse } from './models/types.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2applications) * @category GET */ -export default class SearchForApplications extends JSONRequest< - ApplicationsResponse, - Record -> { +export default class SearchForApplications extends JSONRequest { /** * @returns `/v2/applications` */ @@ -138,9 +137,7 @@ export default class SearchForApplications extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): ApplicationsResponse { - return ApplicationsResponse.fromEncodingData( - ApplicationsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): ApplicationsResponse { + return decodeJSON(response.getJSONText(), ApplicationsResponse); } } diff --git a/src/client/v2/indexer/searchForAssets.ts b/src/client/v2/indexer/searchForAssets.ts index 0ffe0baf3..7b23bcbcd 100644 --- a/src/client/v2/indexer/searchForAssets.ts +++ b/src/client/v2/indexer/searchForAssets.ts @@ -1,4 +1,6 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { Address } from '../../../encoding/address.js'; import { AssetsResponse } from './models/types.js'; @@ -13,10 +15,7 @@ import { AssetsResponse } from './models/types.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2assets) * @category GET */ -export default class SearchForAssets extends JSONRequest< - AssetsResponse, - Record -> { +export default class SearchForAssets extends JSONRequest { /** * @returns `/v2/assets` */ @@ -179,9 +178,7 @@ export default class SearchForAssets extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): AssetsResponse { - return AssetsResponse.fromEncodingData( - AssetsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): AssetsResponse { + return decodeJSON(response.getJSONText(), AssetsResponse); } } diff --git a/src/client/v2/indexer/searchForTransactions.ts b/src/client/v2/indexer/searchForTransactions.ts index 9b2c26ba8..afb446551 100644 --- a/src/client/v2/indexer/searchForTransactions.ts +++ b/src/client/v2/indexer/searchForTransactions.ts @@ -1,4 +1,6 @@ import JSONRequest from '../jsonrequest.js'; +import { HTTPClientResponse } from '../../client.js'; +import { decodeJSON } from '../../../encoding/encoding.js'; import { base64StringFunnel } from './lookupAccountTransactions.js'; import { Address } from '../../../encoding/address.js'; import { TransactionsResponse } from './models/types.js'; @@ -14,10 +16,7 @@ import { TransactionsResponse } from './models/types.js'; * [Response data schema details](https://developer.algorand.org/docs/rest-apis/indexer/#get-v2transactions) * @category GET */ -export default class SearchForTransactions extends JSONRequest< - TransactionsResponse, - Record -> { +export default class SearchForTransactions extends JSONRequest { /** * @returns `/v2/transactions` */ @@ -440,9 +439,7 @@ export default class SearchForTransactions extends JSONRequest< } // eslint-disable-next-line class-methods-use-this - prepare(body: Record): TransactionsResponse { - return TransactionsResponse.fromEncodingData( - TransactionsResponse.encodingSchema.fromPreparedJSON(body) - ); + prepare(response: HTTPClientResponse): TransactionsResponse { + return decodeJSON(response.getJSONText(), TransactionsResponse); } } diff --git a/src/client/v2/jsonrequest.ts b/src/client/v2/jsonrequest.ts index 3997474b4..7a796d061 100644 --- a/src/client/v2/jsonrequest.ts +++ b/src/client/v2/jsonrequest.ts @@ -1,5 +1,4 @@ -import { HTTPClient } from '../client.js'; -import IntDecoding from '../../types/intDecoding.js'; +import { HTTPClient, HTTPClientResponse } from '../client.js'; /** * Base abstract class for JSON requests. @@ -8,10 +7,7 @@ import IntDecoding from '../../types/intDecoding.js'; * * Body: The structure of the response's body */ -export default abstract class JSONRequest< - Data = Record, - Body = Data | Uint8Array, -> { +export default abstract class JSONRequest { c: HTTPClient; query: Record; @@ -32,31 +28,34 @@ export default abstract class JSONRequest< /** * Prepare a JSON response before returning it. * - * Use this method to change and restructure response - * data as needed after receiving it from the `do()` method. - * @param body - Response body received + * Use this method to unpack response ata as needed after receiving it from the `do()` method. + * @param response - Response body received * @category JSONRequest */ - // eslint-disable-next-line class-methods-use-this - prepare(body: Body): Data { - return body as unknown as Data; - } + abstract prepare(response: HTTPClientResponse): Data; /** - * Execute the request. - * @param headers - Additional headers to send in the request. Optional. - * @returns A promise which resolves to the parsed response data. - * @category JSONRequest + * Execute the request */ - async do(headers: Record = {}): Promise { - const res = await this.c.get({ + protected executeRequest( + headers: Record + ): Promise { + return this.c.get({ relativePath: this.path(), - parseBody: true, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, query: this.query, requestHeaders: headers, }); - return this.prepare(res.body); + } + + /** + * Execute the request and decode the response. + * @param headers - Additional headers to send in the request. Optional. + * @returns A promise which resolves to the parsed response data. + * @category JSONRequest + */ + async do(headers: Record = {}): Promise { + const res = await this.executeRequest(headers); + return this.prepare(res); } /** @@ -65,14 +64,8 @@ export default abstract class JSONRequest< * @returns A promise which resolves to the raw response data, exactly as returned by the server. * @category JSONRequest */ - async doRaw(headers: Record = {}): Promise { - const res = await this.c.get({ - relativePath: this.path(), - parseBody: false, - jsonOptions: { intDecoding: IntDecoding.BIGINT }, - query: this.query, - requestHeaders: headers, - }); + async doRaw(headers: Record = {}): Promise { + const res = await this.executeRequest(headers); return res.body; } } diff --git a/src/encoding/schema/array.ts b/src/encoding/schema/array.ts index 7306f7c26..1a90571b6 100644 --- a/src/encoding/schema/array.ts +++ b/src/encoding/schema/array.ts @@ -40,7 +40,9 @@ export class ArraySchema extends Schema { ) ); } - throw new Error('ArraySchema encoded data must be an array'); + throw new Error( + `ArraySchema encoded data must be an array: ${encoded} (${typeof encoded})` + ); } public prepareJSON( @@ -57,6 +59,8 @@ export class ArraySchema extends Schema { if (Array.isArray(encoded)) { return encoded.map((item) => this.itemSchema.fromPreparedJSON(item)); } - throw new Error('ArraySchema encoded data must be an array'); + throw new Error( + `ArraySchema encoded data must be an array: ${encoded} (${typeof encoded})` + ); } } diff --git a/src/encoding/schema/blockhash.ts b/src/encoding/schema/blockhash.ts new file mode 100644 index 000000000..5c82dd4f4 --- /dev/null +++ b/src/encoding/schema/blockhash.ts @@ -0,0 +1,84 @@ +import base32 from 'hi-base32'; +import { + Schema, + MsgpackEncodingData, + MsgpackRawStringProvider, + JSONEncodingData, + PrepareJSONOptions, +} from '../encoding.js'; + +/** + * Length of a block hash in bytes + */ +const blockHashByteLength = 32; + +/* eslint-disable class-methods-use-this */ + +/** + * Length of a 32-byte encoded in base32 without padding + */ +const base32Length = 52; + +/** + * BlockHashSchema is a schema for block hashes. + * + * In msgapck, these types are encoded as 32-byte binary strings. In JSON, they + * are encoded as strings prefixed with "blk-" followed by the base32 encoding + * of the 32-byte block hash without any padding. + */ +export class BlockHashSchema extends Schema { + public defaultValue(): Uint8Array { + return new Uint8Array(blockHashByteLength); + } + + public isDefaultValue(data: unknown): boolean { + return ( + data instanceof Uint8Array && + data.byteLength === blockHashByteLength && + data.every((byte) => byte === 0) + ); + } + + public prepareMsgpack(data: unknown): MsgpackEncodingData { + if (data instanceof Uint8Array && data.byteLength === blockHashByteLength) { + return data; + } + throw new Error(`Invalid block hash: (${typeof data}) ${data}`); + } + + public fromPreparedMsgpack( + encoded: MsgpackEncodingData, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _rawStringProvider: MsgpackRawStringProvider + ): Uint8Array { + if ( + encoded instanceof Uint8Array && + encoded.byteLength === blockHashByteLength + ) { + return encoded; + } + throw new Error(`Invalid block hash: (${typeof encoded}) ${encoded}`); + } + + public prepareJSON( + data: unknown, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _options: PrepareJSONOptions + ): JSONEncodingData { + if (data instanceof Uint8Array && data.byteLength === blockHashByteLength) { + return `blk-${base32.encode(data).slice(0, base32Length)}`; + } + throw new Error(`Invalid block hash: (${typeof data}) ${data}`); + } + + public fromPreparedJSON(encoded: JSONEncodingData): Uint8Array { + if ( + typeof encoded === 'string' && + encoded.length === base32Length + 4 && + encoded.startsWith('blk-') + ) { + return Uint8Array.from(base32.decode.asBytes(encoded.slice(4))); + } + throw new Error(`Invalid block hash: (${typeof encoded}) ${encoded}`); + } +} diff --git a/src/encoding/schema/index.ts b/src/encoding/schema/index.ts index 208799132..a18203174 100644 --- a/src/encoding/schema/index.ts +++ b/src/encoding/schema/index.ts @@ -5,6 +5,8 @@ export { Uint64Schema } from './uint64.js'; export { AddressSchema } from './address.js'; export { ByteArraySchema, FixedLengthByteArraySchema } from './bytearray.js'; +export { BlockHashSchema } from './blockhash.js'; + export { SpecialCaseBinaryStringSchema } from './binarystring.js'; export { ArraySchema } from './array.js'; @@ -16,6 +18,7 @@ export { convertMap, Uint64MapSchema, StringMapSchema, + ByteArrayMapSchema, SpecialCaseBinaryStringMapSchema, } from './map.js'; export { OptionalSchema } from './optional.js'; diff --git a/src/encoding/schema/map.ts b/src/encoding/schema/map.ts index f0afee3cb..5c57dd6f5 100644 --- a/src/encoding/schema/map.ts +++ b/src/encoding/schema/map.ts @@ -7,7 +7,12 @@ import { PrepareJSONOptions, } from '../encoding.js'; import { ensureUint64, arrayEqual } from '../../utils/utils.js'; -import { bytesToString, coerceToBytes, bytesToBase64 } from '../binarydata.js'; +import { + bytesToString, + coerceToBytes, + bytesToBase64, + base64ToBytes, +} from '../binarydata.js'; /* eslint-disable class-methods-use-this */ @@ -460,6 +465,105 @@ export class StringMapSchema extends Schema { } } +/** + * Schema for a map with a variable number of byte array keys. + */ +export class ByteArrayMapSchema extends Schema { + constructor(public readonly valueSchema: Schema) { + super(); + } + + public defaultValue(): Map { + return new Map(); + } + + public isDefaultValue(data: unknown): boolean { + return data instanceof Map && data.size === 0; + } + + public prepareMsgpack(data: unknown): MsgpackEncodingData { + if (!(data instanceof Map)) { + throw new Error( + `ByteArrayMapSchema data must be a Map. Got (${typeof data}) ${data}` + ); + } + const prepared = new Map(); + for (const [key, value] of data) { + if (!(key instanceof Uint8Array)) { + throw new Error(`Invalid key: ${key} (${typeof key})`); + } + prepared.set(key, this.valueSchema.prepareMsgpack(value)); + } + return prepared; + } + + public fromPreparedMsgpack( + encoded: MsgpackEncodingData, + rawStringProvider: MsgpackRawStringProvider + ): Map { + if (!(encoded instanceof Map)) { + throw new Error('ByteArrayMapSchema data must be a Map'); + } + const map = new Map(); + for (const [key, value] of encoded) { + if (!(key instanceof Uint8Array)) { + throw new Error(`Invalid key: ${key} (${typeof key})`); + } + map.set( + key, + this.valueSchema.fromPreparedMsgpack( + value, + rawStringProvider.withMapValue(key) + ) + ); + } + return map; + } + + public prepareJSON( + data: unknown, + options: PrepareJSONOptions + ): JSONEncodingData { + if (!(data instanceof Map)) { + throw new Error( + `ByteArrayMapSchema data must be a Map. Got (${typeof data}) ${data}` + ); + } + const prepared = new Map(); + for (const [key, value] of data) { + if (!(key instanceof Uint8Array)) { + throw new Error(`Invalid key: ${key} (${typeof key})`); + } + const b64Encoded = bytesToBase64(key); + if (prepared.has(b64Encoded)) { + throw new Error(`Duplicate key (base64): ${b64Encoded}`); + } + prepared.set(b64Encoded, this.valueSchema.prepareJSON(value, options)); + } + // Convert map to object + const obj: { [key: string]: JSONEncodingData } = {}; + for (const [key, value] of prepared) { + obj[key] = value; + } + return obj; + } + + public fromPreparedJSON(encoded: JSONEncodingData): Map { + if ( + encoded == null || + typeof encoded !== 'object' || + Array.isArray(encoded) + ) { + throw new Error('ByteArrayMapSchema data must be an object'); + } + const map = new Map(); + for (const [key, value] of Object.entries(encoded)) { + map.set(base64ToBytes(key), this.valueSchema.fromPreparedJSON(value)); + } + return map; + } +} + /** * Converts any RawBinaryString values to regular strings in a MsgpackEncodingData object. * diff --git a/src/encoding/schema/optional.ts b/src/encoding/schema/optional.ts index b083cebeb..ae6100e14 100644 --- a/src/encoding/schema/optional.ts +++ b/src/encoding/schema/optional.ts @@ -18,6 +18,8 @@ import { * `undefined` value is indistinguishable from the given schema's default value; in this respect, * OptionalSchema does not affect the encoding of NamedMapSchema values, but rather allows the * application to restore omitted values as `undefined` instead of their default value. + * + * Upon decoding, this schema also allows null/undefined values to be acceptable as values. */ export class OptionalSchema extends Schema { constructor(public readonly valueSchema: Schema) { diff --git a/src/main.ts b/src/main.ts index e0d9779b9..f5cfd0bb5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -124,6 +124,7 @@ export { encodeUint64, decodeUint64 } from './encoding/uint64.js'; export { parseJSON, ParseJSONOptions, stringifyJSON } from './utils/utils.js'; export { default as generateAccount } from './account.js'; export * from './types/block.js'; +export * from './types/statedelta.js'; export * from './stateproof.js'; export { UntypedValue } from './client/v2/untypedmodel.js'; export * as modelsv2 from './client/v2/algod/models/types.js'; diff --git a/src/types/block.ts b/src/types/block.ts index d90e207fd..5cc9edc37 100644 --- a/src/types/block.ts +++ b/src/types/block.ts @@ -15,6 +15,7 @@ import { allOmitEmpty, combineMaps, convertMap, + BlockHashSchema, } from '../encoding/schema/index.js'; import { Address } from '../encoding/address.js'; import { SignedTransaction } from '../signedTransaction.js'; @@ -506,7 +507,7 @@ export class BlockHeader implements Encodable { }, { key: 'prev', // branch - valueSchema: new ByteArraySchema(), + valueSchema: new BlockHashSchema(), }, { key: 'seed', // seed diff --git a/src/types/statedelta.ts b/src/types/statedelta.ts new file mode 100644 index 000000000..32be00618 --- /dev/null +++ b/src/types/statedelta.ts @@ -0,0 +1,1717 @@ +import { Encodable, Schema } from '../encoding/encoding.js'; +import { + NamedMapSchema, + Uint64MapSchema, + ByteArrayMapSchema, + SpecialCaseBinaryStringMapSchema, + SpecialCaseBinaryStringSchema, + ArraySchema, + BooleanSchema, + Uint64Schema, + AddressSchema, + ByteArraySchema, + FixedLengthByteArraySchema, + OptionalSchema, + UntypedSchema, + allOmitEmpty, + convertMap, + combineMaps, +} from '../encoding/schema/index.js'; +import { Address } from '../encoding/address.js'; +import { BlockHeader } from './block.js'; +import { UntypedValue } from '../client/v2/untypedmodel.js'; + +// TealValue contains type information and a value, representing a value in a TEAL program +export class TealValue implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'tt', valueSchema: new Uint64Schema() }, // type + { + key: 'tb', // bytes + valueSchema: new OptionalSchema(new SpecialCaseBinaryStringSchema()), + }, + { key: 'ui', valueSchema: new OptionalSchema(new Uint64Schema()) }, // uint + ]) + ); + + /** + * Type determines the type of the value. + * * 1 represents the type of a byte slice in a TEAL program + * * 2 represents the type of an unsigned integer in a TEAL program + */ + public type: number; + public bytes?: Uint8Array; + public uint?: bigint; + + constructor(params: { type: number; bytes?: Uint8Array; uint?: bigint }) { + this.type = params.type; + this.bytes = params.bytes; + this.uint = params.uint; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return TealValue.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['tt', this.type], + ['tb', this.bytes], + ['ui', this.uint], + ]); + } + + public static fromEncodingData(data: unknown): TealValue { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded TealValue: ${data}`); + } + return new TealValue({ + type: Number(data.get('tt')), + bytes: data.get('tb'), + uint: data.get('ui'), + }); + } +} + +/** + * StateSchema sets maximums on the number of each type that may be stored + */ +export class StateSchema implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'nui', // numUints + valueSchema: new Uint64Schema(), + }, + { + key: 'nbs', // numByteSlices + valueSchema: new Uint64Schema(), + }, + ]) + ); + + public numUints: number; + public numByteSlices: number; + + public constructor(params: { numUints: number; numByteSlices: number }) { + this.numUints = params.numUints; + this.numByteSlices = params.numByteSlices; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return StateSchema.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['nui', this.numUints], + ['nbs', this.numByteSlices], + ]); + } + + public static fromEncodingData(data: unknown): StateSchema { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded StateSchema: ${data}`); + } + return new StateSchema({ + numUints: Number(data.get('nui')), + numByteSlices: Number(data.get('nbs')), + }); + } +} + +/** + * AppParams stores the global information associated with an application + */ +export class AppParams implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'approv', valueSchema: new ByteArraySchema() }, // approvalProgram + { key: 'clearp', valueSchema: new ByteArraySchema() }, // alearStateProgram + { + key: 'gs', + valueSchema: new SpecialCaseBinaryStringMapSchema( + TealValue.encodingSchema + ), + }, // globalState + { key: 'lsch', valueSchema: StateSchema.encodingSchema }, // localStateSchema + { key: 'gsch', valueSchema: StateSchema.encodingSchema }, // globalStateSchema + { key: 'epp', valueSchema: new Uint64Schema() }, // extraProgramPages + ]) + ); + + public approvalProgram: Uint8Array; + public clearStateProgram: Uint8Array; + public globalState: Map; + public localStateSchema: StateSchema; + public globalStateSchema: StateSchema; + public extraProgramPages: number; + + constructor(params: { + approvalProgram: Uint8Array; + clearStateProgram: Uint8Array; + globalState: Map; + localStateSchema: StateSchema; + globalStateSchema: StateSchema; + extraProgramPages: number; + }) { + this.approvalProgram = params.approvalProgram; + this.clearStateProgram = params.clearStateProgram; + this.globalState = params.globalState; + this.localStateSchema = params.localStateSchema; + this.globalStateSchema = params.globalStateSchema; + this.extraProgramPages = params.extraProgramPages; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AppParams.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['approv', this.approvalProgram], + ['clearp', this.clearStateProgram], + ['gs', convertMap(this.globalState, (k, v) => [k, v.toEncodingData()])], + ['lsch', this.localStateSchema.toEncodingData()], + ['gsch', this.globalStateSchema.toEncodingData()], + ['epp', this.extraProgramPages], + ]); + } + + public static fromEncodingData(data: unknown) { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AppParams: ${data}`); + } + return new AppParams({ + approvalProgram: data.get('approv'), + clearStateProgram: data.get('clearp'), + globalState: convertMap( + data.get('gs') as Map, + (k, v) => [k, TealValue.fromEncodingData(v)] + ), + localStateSchema: StateSchema.fromEncodingData(data.get('lsch')), + globalStateSchema: StateSchema.fromEncodingData(data.get('gsch')), + extraProgramPages: Number(data.get('epp')), + }); + } +} + +/** + * AppLocalState stores the LocalState associated with an application. + */ +export class AppLocalState implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'hsch', valueSchema: StateSchema.encodingSchema }, // schema + { + key: 'tkv', // keyValue + valueSchema: new SpecialCaseBinaryStringMapSchema( + TealValue.encodingSchema + ), + }, + ]) + ); + + public schema: StateSchema; + public keyValue: Map; + + constructor(params: { + schema: StateSchema; + keyValue: Map; + }) { + this.schema = params.schema; + this.keyValue = params.keyValue; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AppLocalState.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['hsch', this.schema.toEncodingData()], + ['tkv', convertMap(this.keyValue, (k, v) => [k, v.toEncodingData()])], + ]); + } + + public static fromEncodingData(data: unknown): AppLocalState { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AppLocalState: ${data}`); + } + return new AppLocalState({ + schema: StateSchema.fromEncodingData(data.get('hsch')), + keyValue: convertMap( + data.get('tkv') as Map, + (k, v) => [k, TealValue.fromEncodingData(v)] + ), + }); + } +} + +/** + * AppLocalStateDelta tracks a changed AppLocalState, and whether it was deleted + */ +export class AppLocalStateDelta implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'LocalState', // localState + valueSchema: new OptionalSchema(AppLocalState.encodingSchema), + }, + { key: 'Deleted', valueSchema: new BooleanSchema() }, // deleted + ]) + ); + + public localState?: AppLocalState; + public deleted: boolean; + + constructor(params: { localState?: AppLocalState; deleted: boolean }) { + this.localState = params.localState; + this.deleted = params.deleted; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AppLocalStateDelta.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + [ + 'LocalState', + this.localState ? this.localState.toEncodingData() : undefined, + ], + ['Deleted', this.deleted], + ]); + } + + public static fromEncodingData(data: unknown): AppLocalStateDelta { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AppLocalStateDelta: ${data}`); + } + return new AppLocalStateDelta({ + localState: data.get('LocalState') + ? AppLocalState.fromEncodingData(data.get('LocalState')) + : undefined, + deleted: data.get('Deleted'), + }); + } +} + +/** + * AppParamsDelta tracks a changed AppParams, and whether it was deleted + */ +export class AppParamsDelta implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Params', // params + valueSchema: new OptionalSchema(AppParams.encodingSchema), + }, + { key: 'Deleted', valueSchema: new BooleanSchema() }, // deleted + ]) + ); + + public params?: AppParams; + public deleted: boolean; + + constructor(params: { params?: AppParams; deleted: boolean }) { + this.params = params.params; + this.deleted = params.deleted; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AppParamsDelta.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Params', this.params ? this.params.toEncodingData() : undefined], + ['Deleted', this.deleted], + ]); + } + + public static fromEncodingData(data: unknown): AppParamsDelta { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AppParamsDelta: ${data}`); + } + return new AppParamsDelta({ + params: data.get('Params') + ? AppParams.fromEncodingData(data.get('Params')) + : undefined, + deleted: data.get('Deleted'), + }); + } +} + +/** + * AppResourceRecord represents AppParams and AppLocalState in deltas + */ +export class AppResourceRecord implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'Aidx', valueSchema: new Uint64Schema() }, // id + { key: 'Addr', valueSchema: new AddressSchema() }, // address + { + key: 'Params', // params + valueSchema: AppParamsDelta.encodingSchema, + }, + { + key: 'State', // state + valueSchema: AppLocalStateDelta.encodingSchema, + }, + ]) + ); + + public id: bigint; + public address: Address; + public params: AppParamsDelta; + public state: AppLocalStateDelta; + + constructor(params: { + id: bigint; + address: Address; + params: AppParamsDelta; + state: AppLocalStateDelta; + }) { + this.id = params.id; + this.address = params.address; + this.params = params.params; + this.state = params.state; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AppResourceRecord.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Aidx', this.id], + ['Addr', this.address], + ['Params', this.params.toEncodingData()], + ['State', this.state.toEncodingData()], + ]); + } + + public static fromEncodingData(data: unknown): AppResourceRecord { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AppResourceRecord: ${data}`); + } + return new AppResourceRecord({ + id: data.get('Aidx'), + address: data.get('Addr'), + params: AppParamsDelta.fromEncodingData(data.get('Params')), + state: AppLocalStateDelta.fromEncodingData(data.get('State')), + }); + } +} + +/** + * AssetHolding describes an asset held by an account. + */ +export class AssetHolding implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'a', valueSchema: new Uint64Schema() }, // amount + { key: 'f', valueSchema: new BooleanSchema() }, // frozen + ]) + ); + + public amount: bigint; + public frozen: boolean; + + constructor(params: { amount: bigint; frozen: boolean }) { + this.amount = params.amount; + this.frozen = params.frozen; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AssetHolding.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['a', this.amount], + ['f', this.frozen], + ]); + } + + public static fromEncodingData(data: unknown): AssetHolding { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AssetHolding: ${data}`); + } + return new AssetHolding({ + amount: data.get('a'), + frozen: data.get('f'), + }); + } +} + +/** + * AssetHoldingDelta records a changed AssetHolding, and whether it was deleted + */ +export class AssetHoldingDelta implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Holding', // holding + valueSchema: new OptionalSchema(AssetHolding.encodingSchema), + }, + { key: 'Deleted', valueSchema: new BooleanSchema() }, // deleted + ]) + ); + + public holding?: AssetHolding; + public deleted: boolean; + + constructor(params: { holding?: AssetHolding; deleted: boolean }) { + this.holding = params.holding; + this.deleted = params.deleted; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AssetHoldingDelta.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Holding', this.holding ? this.holding.toEncodingData() : undefined], + ['Deleted', this.deleted], + ]); + } + + public static fromEncodingData(data: unknown): AssetHoldingDelta { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AssetHoldingDelta: ${data}`); + } + return new AssetHoldingDelta({ + holding: data.get('Holding') + ? AssetHolding.fromEncodingData(data.get('Holding')) + : undefined, + deleted: data.get('Deleted'), + }); + } +} + +/** + * AssetParams describes the parameters of an asset. + */ +export class AssetParams implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 't', valueSchema: new Uint64Schema() }, // total + { key: 'dc', valueSchema: new Uint64Schema() }, // decimals + { key: 'df', valueSchema: new BooleanSchema() }, // defaultFrozen + { + key: 'un', // unitName + valueSchema: new OptionalSchema(new SpecialCaseBinaryStringSchema()), + }, + { + key: 'an', // assetName + valueSchema: new OptionalSchema(new SpecialCaseBinaryStringSchema()), + }, + { + key: 'au', // url + valueSchema: new OptionalSchema(new SpecialCaseBinaryStringSchema()), + }, + { key: 'am', valueSchema: new FixedLengthByteArraySchema(32) }, // metadataHash + { key: 'm', valueSchema: new OptionalSchema(new AddressSchema()) }, // manager + { key: 'r', valueSchema: new OptionalSchema(new AddressSchema()) }, // reserve + { key: 'f', valueSchema: new OptionalSchema(new AddressSchema()) }, // freeze + { key: 'c', valueSchema: new OptionalSchema(new AddressSchema()) }, // clawback + ]) + ); + + /** + * Total specifies the total number of units of this asset created. + */ + public total: bigint; + + /** + * Decimals specifies the number of digits to display after the decimal place when displaying this asset. + * A value of 0 represents an asset that is not divisible, a value of 1 represents an asset divisible into tenths, and so on. + * This value must be between 0 and 19 (inclusive). + */ + public decimals: number; + + /** + * DefaultFrozen specifies whether slots for this asset in user accounts are frozen by default or not. + */ + public defaultFrozen: boolean; + + /** + * UnitName specifies a hint for the name of a unit of this asset. + */ + public unitName?: Uint8Array; + + /** + * AssetName specifies a hint for the name of the asset. + */ + public assetName?: Uint8Array; + + /** + * URL specifies a URL where more information about the asset can be retrieved. + */ + public url?: Uint8Array; + + /** + * MetadataHash specifies a commitment to some unspecified asset metadata. The format of this + * metadata is up to the application. + */ + public metadataHash?: Uint8Array; + + /** + * Manager specifies an account that is allowed to change the non-zero addresses in this AssetParams. + */ + public manager?: Address; + + /** + * Reserve specifies an account whose holdings of this asset should be reported as "not minted". + */ + public reserve?: Address; + + /** + * Freeze specifies an account that is allowed to change the frozen state of holdings of this asset. + */ + public freeze?: Address; + + /** + * Clawback specifies an account that is allowed to take units of this asset from any account. + */ + public clawback?: Address; + + public constructor(params: { + total: bigint; + decimals: number; + defaultFrozen: boolean; + unitName?: Uint8Array; + assetName?: Uint8Array; + url?: Uint8Array; + metadataHash?: Uint8Array; + manager?: Address; + reserve?: Address; + freeze?: Address; + clawback?: Address; + }) { + this.total = params.total; + this.decimals = params.decimals; + this.defaultFrozen = params.defaultFrozen; + this.unitName = params.unitName; + this.assetName = params.assetName; + this.url = params.url; + this.metadataHash = params.metadataHash; + this.manager = params.manager; + this.reserve = params.reserve; + this.freeze = params.freeze; + this.clawback = params.clawback; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AssetParams.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['t', this.total], + ['dc', this.decimals], + ['df', this.defaultFrozen], + ['un', this.unitName], + ['an', this.assetName], + ['au', this.url], + ['am', this.metadataHash], + ['m', this.manager], + ['r', this.reserve], + ['f', this.freeze], + ['c', this.clawback], + ]); + } + + public static fromEncodingData(data: unknown): AssetParams { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AssetParams: ${data}`); + } + return new AssetParams({ + total: data.get('t'), + decimals: data.get('dc'), + defaultFrozen: data.get('df'), + unitName: data.get('un'), + assetName: data.get('an'), + url: data.get('au'), + metadataHash: data.get('am'), + manager: data.get('m'), + reserve: data.get('r'), + freeze: data.get('f'), + clawback: data.get('c'), + }); + } +} + +/** + * AssetParamsDelta tracks a changed AssetParams, and whether it was deleted + */ +export class AssetParamsDelta implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Params', // params + valueSchema: new OptionalSchema(AssetParams.encodingSchema), + }, + { key: 'Deleted', valueSchema: new BooleanSchema() }, // deleted + ]) + ); + + public params?: AssetParams; + public deleted: boolean; + + constructor(params: { params?: AssetParams; deleted: boolean }) { + this.params = params.params; + this.deleted = params.deleted; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AssetParamsDelta.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Params', this.params ? this.params.toEncodingData() : undefined], + ['Deleted', this.deleted], + ]); + } + + public static fromEncodingData(data: unknown): AssetParamsDelta { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AssetParamsDelta: ${data}`); + } + return new AssetParamsDelta({ + params: data.get('Params') + ? AssetParams.fromEncodingData(data.get('Params')) + : undefined, + deleted: data.get('Deleted'), + }); + } +} + +/** + * AssetResourceRecord represents AssetParams and AssetHolding in deltas + */ +export class AssetResourceRecord implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'Aidx', valueSchema: new Uint64Schema() }, // id + { key: 'Addr', valueSchema: new AddressSchema() }, // address + { + key: 'Params', // params + valueSchema: AssetParamsDelta.encodingSchema, + }, + { + key: 'Holding', // holding + valueSchema: AssetHoldingDelta.encodingSchema, + }, + ]) + ); + + public id: bigint; + public address: Address; + public params: AssetParamsDelta; + public holding: AssetHoldingDelta; + + constructor(params: { + id: bigint; + address: Address; + params: AssetParamsDelta; + holding: AssetHoldingDelta; + }) { + this.id = params.id; + this.address = params.address; + this.params = params.params; + this.holding = params.holding; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AssetResourceRecord.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Aidx', this.id], + ['Addr', this.address], + ['Params', this.params.toEncodingData()], + ['Holding', this.holding.toEncodingData()], + ]); + } + + public static fromEncodingData(data: unknown): AssetResourceRecord { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AssetResourceRecord: ${data}`); + } + return new AssetResourceRecord({ + id: data.get('Aidx'), + address: data.get('Addr'), + params: AssetParamsDelta.fromEncodingData(data.get('Params')), + holding: AssetHoldingDelta.fromEncodingData(data.get('Holding')), + }); + } +} + +/** + * VotingData holds participation information + */ +export class VotingData implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'VoteID', // voteID + valueSchema: new FixedLengthByteArraySchema(32), + }, + { + key: 'SelectionID', // selectionID + valueSchema: new FixedLengthByteArraySchema(32), + }, + { + key: 'StateProofID', // stateProofID + valueSchema: new FixedLengthByteArraySchema(64), + }, + { + key: 'VoteFirstValid', // voteFirstValid + valueSchema: new Uint64Schema(), + }, + { + key: 'VoteLastValid', // voteLastValid + valueSchema: new Uint64Schema(), + }, + { + key: 'VoteKeyDilution', // voteKeyDilution + valueSchema: new Uint64Schema(), + }, + ]) + ); + + public voteID: Uint8Array; + public selectionID: Uint8Array; + public stateProofID: Uint8Array; + + public voteFirstValid: bigint; + public voteLastValid: bigint; + public voteKeyDilution: bigint; + + constructor(params: { + voteID: Uint8Array; + selectionID: Uint8Array; + stateProofID: Uint8Array; + voteFirstValid: bigint; + voteLastValid: bigint; + voteKeyDilution: bigint; + }) { + this.voteID = params.voteID; + this.selectionID = params.selectionID; + this.stateProofID = params.stateProofID; + this.voteFirstValid = params.voteFirstValid; + this.voteLastValid = params.voteLastValid; + this.voteKeyDilution = params.voteKeyDilution; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return VotingData.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['VoteID', this.voteID], + ['SelectionID', this.selectionID], + ['StateProofID', this.stateProofID], + ['VoteFirstValid', this.voteFirstValid], + ['VoteLastValid', this.voteLastValid], + ['VoteKeyDilution', this.voteKeyDilution], + ]); + } + + public static fromEncodingData(data: unknown): VotingData { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded VotingData: ${data}`); + } + return new VotingData({ + voteID: data.get('VoteID'), + selectionID: data.get('SelectionID'), + stateProofID: data.get('StateProofID'), + voteFirstValid: data.get('VoteFirstValid'), + voteLastValid: data.get('VoteLastValid'), + voteKeyDilution: data.get('VoteKeyDilution'), + }); + } +} + +/** + * AccountBaseData contains base account info like balance, status and total number of resources + */ +export class AccountBaseData implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'Status', valueSchema: new Uint64Schema() }, // status + { key: 'MicroAlgos', valueSchema: new Uint64Schema() }, // microAlgos + { key: 'RewardsBase', valueSchema: new Uint64Schema() }, // rewardsBase + { + key: 'RewardedMicroAlgos', // rewardedMicroAlgos + valueSchema: new Uint64Schema(), + }, + { key: 'AuthAddr', valueSchema: new AddressSchema() }, // authAddr + { + key: 'IncentiveEligible', // incentiveEligible + valueSchema: new BooleanSchema(), + }, + { + key: 'TotalAppSchema', // totalAppSchema + valueSchema: StateSchema.encodingSchema, + }, + { + key: 'TotalExtraAppPages', // totalExtraAppPages + valueSchema: new Uint64Schema(), + }, + { + key: 'TotalAppParams', // totalAppParams + valueSchema: new Uint64Schema(), + }, + { + key: 'TotalAppLocalStates', // totalAppLocalStates + valueSchema: new Uint64Schema(), + }, + { + key: 'TotalAssetParams', // totalAssetParams + valueSchema: new Uint64Schema(), + }, + { key: 'TotalAssets', valueSchema: new Uint64Schema() }, // totalAssets + { key: 'TotalBoxes', valueSchema: new Uint64Schema() }, // totalBoxes + { + key: 'TotalBoxBytes', // totalBoxBytes + valueSchema: new Uint64Schema(), + }, + { key: 'LastProposed', valueSchema: new Uint64Schema() }, // lastProposed + { + key: 'LastHeartbeat', // lastHeartbeat + valueSchema: new Uint64Schema(), + }, + ]) + ); + + /** + * Account status. Values are: + * * 0: Offline + * * 1: Online + * * 2: NotParticipating + */ + public status: number; + public microAlgos: bigint; + public rewardsBase: bigint; + public rewardedMicroAlgos: bigint; + public authAddr: Address; + public incentiveEligible: boolean; + + /** + * Totals across created globals, and opted in locals. + */ + public totalAppSchema: StateSchema; + /** + * Total number of extra pages across all created apps + */ + public totalExtraAppPages: number; + /** + * Total number of apps this account has created + */ + public totalAppParams: bigint; + /** + * Total number of apps this account is opted into. + */ + public totalAppLocalStates: bigint; + /** + * Total number of assets created by this account + */ + public totalAssetParams: bigint; + /** + * Total of asset creations and optins (i.e. number of holdings) + */ + public totalAssets: bigint; + /** + * Total number of boxes associated to this account + */ + public totalBoxes: bigint; + /** + * Total bytes for this account's boxes. keys _and_ values count + */ + public totalBoxBytes: bigint; + + /** + * The last round that this account proposed the winning block. + */ + public lastProposed: bigint; + /** + * The last round that this account sent a heartbeat to show it was online. + */ + public lastHeartbeat: bigint; + + public constructor(params: { + status: number; + microAlgos: bigint; + rewardsBase: bigint; + rewardedMicroAlgos: bigint; + authAddr: Address; + incentiveEligible: boolean; + totalAppSchema: StateSchema; + totalExtraAppPages: number; + totalAppParams: bigint; + totalAppLocalStates: bigint; + totalAssetParams: bigint; + totalAssets: bigint; + totalBoxes: bigint; + totalBoxBytes: bigint; + lastProposed: bigint; + lastHeartbeat: bigint; + }) { + this.status = params.status; + this.microAlgos = params.microAlgos; + this.rewardsBase = params.rewardsBase; + this.rewardedMicroAlgos = params.rewardedMicroAlgos; + this.authAddr = params.authAddr; + this.incentiveEligible = params.incentiveEligible; + this.totalAppSchema = params.totalAppSchema; + this.totalExtraAppPages = params.totalExtraAppPages; + this.totalAppParams = params.totalAppParams; + this.totalAppLocalStates = params.totalAppLocalStates; + this.totalAssetParams = params.totalAssetParams; + this.totalAssets = params.totalAssets; + this.totalBoxes = params.totalBoxes; + this.totalBoxBytes = params.totalBoxBytes; + this.lastProposed = params.lastProposed; + this.lastHeartbeat = params.lastHeartbeat; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AccountBaseData.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Status', this.status], + ['MicroAlgos', this.microAlgos], + ['RewardsBase', this.rewardsBase], + ['RewardedMicroAlgos', this.rewardedMicroAlgos], + ['AuthAddr', this.authAddr], + ['IncentiveEligible', this.incentiveEligible], + ['TotalAppSchema', this.totalAppSchema.toEncodingData()], + ['TotalExtraAppPages', this.totalExtraAppPages], + ['TotalAppParams', this.totalAppParams], + ['TotalAppLocalStates', this.totalAppLocalStates], + ['TotalAssetParams', this.totalAssetParams], + ['TotalAssets', this.totalAssets], + ['TotalBoxes', this.totalBoxes], + ['TotalBoxBytes', this.totalBoxBytes], + ['LastProposed', this.lastProposed], + ['LastHeartbeat', this.lastHeartbeat], + ]); + } + + public static fromEncodingData(data: unknown): AccountBaseData { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AccountBaseData: ${data}`); + } + return new AccountBaseData({ + status: Number(data.get('Status')), + microAlgos: data.get('MicroAlgos'), + rewardsBase: data.get('RewardsBase'), + rewardedMicroAlgos: data.get('RewardedMicroAlgos'), + authAddr: data.get('AuthAddr'), + incentiveEligible: data.get('IncentiveEligible'), + totalAppSchema: StateSchema.fromEncodingData(data.get('TotalAppSchema')), + totalExtraAppPages: Number(data.get('TotalExtraAppPages')), + totalAppParams: data.get('TotalAppParams'), + totalAppLocalStates: data.get('TotalAppLocalStates'), + totalAssetParams: data.get('TotalAssetParams'), + totalAssets: data.get('TotalAssets'), + totalBoxes: data.get('TotalBoxes'), + totalBoxBytes: data.get('TotalBoxBytes'), + lastProposed: data.get('LastProposed'), + lastHeartbeat: data.get('LastHeartbeat'), + }); + } +} + +/** + * AccountData provides per-account data + */ +export class AccountData implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: '', + valueSchema: AccountBaseData.encodingSchema, + embedded: true, + }, + { + key: '', + valueSchema: VotingData.encodingSchema, + embedded: true, + }, + ]) + ); + + public accountBaseData: AccountBaseData; + public votingData: VotingData; + + constructor(params: { + accountBaseData: AccountBaseData; + votingData: VotingData; + }) { + this.accountBaseData = params.accountBaseData; + this.votingData = params.votingData; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AccountData.encodingSchema; + } + + public toEncodingData(): Map { + return combineMaps( + this.accountBaseData.toEncodingData(), + this.votingData.toEncodingData() + ); + } + + public static fromEncodingData(data: unknown): AccountData { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AccountData: ${data}`); + } + return new AccountData({ + accountBaseData: AccountBaseData.fromEncodingData(data), + votingData: VotingData.fromEncodingData(data), + }); + } +} + +export class BalanceRecord implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Addr', + valueSchema: new AddressSchema(), + }, + { + key: '', + valueSchema: AccountData.encodingSchema, + embedded: true, + }, + ]) + ); + + public addr: Address; + public accountData: AccountData; + + constructor(params: { addr: Address; accountData: AccountData }) { + this.addr = params.addr; + this.accountData = params.accountData; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return BalanceRecord.encodingSchema; + } + + public toEncodingData(): Map { + return combineMaps( + new Map([['Addr', this.addr]]), + this.accountData.toEncodingData() + ); + } + + public static fromEncodingData(data: unknown): BalanceRecord { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded BalanceRecord: ${data}`); + } + return new BalanceRecord({ + addr: data.get('Addr'), + accountData: AccountData.fromEncodingData(data), + }); + } +} + +export class AccountDeltas implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Accts', // accounts + valueSchema: new ArraySchema(BalanceRecord.encodingSchema), + }, + { + key: 'AppResources', // appResources + valueSchema: new OptionalSchema( + new ArraySchema(AppResourceRecord.encodingSchema) + ), + }, + { + key: 'AssetResources', // assetResources + valueSchema: new OptionalSchema( + new ArraySchema(AssetResourceRecord.encodingSchema) + ), + }, + ]) + ); + + public accounts: BalanceRecord[]; + public appResources: AppResourceRecord[]; + public assetResources: AssetResourceRecord[]; + + constructor(params: { + accounts: BalanceRecord[]; + appResources: AppResourceRecord[]; + assetResources: AssetResourceRecord[]; + }) { + this.accounts = params.accounts; + this.appResources = params.appResources; + this.assetResources = params.assetResources; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AccountDeltas.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Accts', this.accounts.map((account) => account.toEncodingData())], + [ + 'AppResources', + this.appResources.length === 0 + ? undefined + : this.appResources.map((appResource) => + appResource.toEncodingData() + ), + ], + [ + 'AssetResources', + this.assetResources.length === 0 + ? undefined + : this.assetResources.map((assetResource) => + assetResource.toEncodingData() + ), + ], + ]); + } + + public static fromEncodingData(data: unknown): AccountDeltas { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AccountDeltas: ${data}`); + } + return new AccountDeltas({ + accounts: (data.get('Accts') ?? []).map(BalanceRecord.fromEncodingData), + appResources: (data.get('AppResources') ?? []).map( + AppResourceRecord.fromEncodingData + ), + assetResources: (data.get('AssetResources') ?? []).map( + AssetResourceRecord.fromEncodingData + ), + }); + } +} + +/** + * A KvValueDelta shows how the Data associated with a key in the kvstore has changed. + */ +export class KvValueDelta implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Data', + valueSchema: new OptionalSchema(new ByteArraySchema()), + }, + { + key: 'OldData', + valueSchema: new OptionalSchema(new ByteArraySchema()), + }, + ]) + ); + + /** + * Data stores the most recent value (undefined means deleted) + */ + public data?: Uint8Array; + + /** + * OldData stores the previous value (undefined means didn't exist) + */ + public oldData?: Uint8Array; + + constructor(params: { data?: Uint8Array; oldData?: Uint8Array }) { + this.data = params.data; + this.oldData = params.oldData; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return KvValueDelta.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Data', this.data], + ['OldData', this.oldData], + ]); + } + + public static fromEncodingData(data: unknown): KvValueDelta { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded KvValueDelta: ${data}`); + } + return new KvValueDelta({ + data: data.get('Data'), + oldData: data.get('OldData'), + }); + } +} + +/** + * IncludedTransactions defines the transactions included in a block, their index and last valid round. + */ +export class IncludedTransactions implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'LastValid', + valueSchema: new Uint64Schema(), + }, + { + key: 'Intra', + valueSchema: new Uint64Schema(), + }, + ]) + ); + + public lastValid: bigint; + /** + * The index of the transaction in the block + */ + public intra: number; + + constructor(params: { lastValid: bigint; intra: number }) { + this.lastValid = params.lastValid; + this.intra = params.intra; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return IncludedTransactions.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['LastValid', this.lastValid], + ['Intra', this.intra], + ]); + } + + public static fromEncodingData(data: unknown): IncludedTransactions { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded IncludedTransactions: ${data}`); + } + return new IncludedTransactions({ + lastValid: data.get('LastValid'), + intra: Number(data.get('Intra')), + }); + } +} + +/** + * ModifiedCreatable represents a change to a single creatable state + */ +export class ModifiedCreatable implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Ctype', // creatableType + valueSchema: new Uint64Schema(), + }, + { + key: 'Created', // created + valueSchema: new BooleanSchema(), + }, + { + key: 'Creator', // creator + valueSchema: new AddressSchema(), + }, + { + key: 'Ndeltas', // ndeltas + valueSchema: new Uint64Schema(), + }, + ]) + ); + + /** + * Type of the creatable. The values are: + * * 0: Asset + * * 1: Application + */ + public creatableType: number; + + /** + * Created if true, deleted if false + */ + public created: boolean; + + /** + * creator of the app/asset + */ + public creator: Address; + + /** + * Keeps track of how many times this app/asset appears in accountUpdates.creatableDeltas + */ + public ndeltas: number; + + public constructor(params: { + creatableType: number; + created: boolean; + creator: Address; + ndeltas: number; + }) { + this.creatableType = params.creatableType; + this.created = params.created; + this.creator = params.creator; + this.ndeltas = params.ndeltas; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return ModifiedCreatable.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Ctype', this.creatableType], + ['Created', this.created], + ['Creator', this.creator], + ['Ndeltas', this.ndeltas], + ]); + } + + public static fromEncodingData(data: unknown): ModifiedCreatable { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded ModifiedCreatable: ${data}`); + } + return new ModifiedCreatable({ + creatableType: Number(data.get('Ctype')), + created: data.get('Created'), + creator: data.get('Creator'), + ndeltas: Number(data.get('Ndeltas')), + }); + } +} + +/** + * AlgoCount represents a total of algos of a certain class of accounts (split up by their Status value). + */ +export class AlgoCount implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'mon', valueSchema: new Uint64Schema() }, // money + { key: 'rwd', valueSchema: new Uint64Schema() }, // rewardUnits + ]) + ); + + /** + * Sum of algos of all accounts in this class. + */ + public money: bigint; + + /** + * Total number of whole reward units in accounts. + */ + public rewardUnits: bigint; + + constructor(params: { money: bigint; rewardUnits: bigint }) { + this.money = params.money; + this.rewardUnits = params.rewardUnits; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AlgoCount.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['mon', this.money], + ['rwd', this.rewardUnits], + ]); + } + + public static fromEncodingData(data: unknown): AlgoCount { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AlgoCount: ${data}`); + } + return new AlgoCount({ + money: data.get('mon'), + rewardUnits: data.get('rwd'), + }); + } +} + +/** + * AccountTotals represents the totals of algos in the system grouped by different account status values. + */ +export class AccountTotals implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { key: 'online', valueSchema: AlgoCount.encodingSchema }, // online + { key: 'offline', valueSchema: AlgoCount.encodingSchema }, // offline + { key: 'notpart', valueSchema: AlgoCount.encodingSchema }, // notParticipating + { key: 'rwdlvl', valueSchema: new Uint64Schema() }, // rewardsLevel + ]) + ); + + public online: AlgoCount; + public offline: AlgoCount; + public notParticipating: AlgoCount; + + /** + * Total number of algos received per reward unit since genesis + */ + public rewardsLevel: bigint; + + constructor(params: { + online: AlgoCount; + offline: AlgoCount; + notParticipating: AlgoCount; + rewardsLevel: bigint; + }) { + this.online = params.online; + this.offline = params.offline; + this.notParticipating = params.notParticipating; + this.rewardsLevel = params.rewardsLevel; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return AccountTotals.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['online', this.online.toEncodingData()], + ['offline', this.offline.toEncodingData()], + ['notpart', this.notParticipating.toEncodingData()], + ['rwdlvl', this.rewardsLevel], + ]); + } + + public static fromEncodingData(data: unknown): AccountTotals { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded AccountTotals: ${data}`); + } + return new AccountTotals({ + online: AlgoCount.fromEncodingData(data.get('online')), + offline: AlgoCount.fromEncodingData(data.get('offline')), + notParticipating: AlgoCount.fromEncodingData(data.get('notpart')), + rewardsLevel: data.get('rwdlvl'), + }); + } +} + +/** + * LedgerStateDelta describes the delta between a given round to the previous round + */ +export class LedgerStateDelta implements Encodable { + public static readonly encodingSchema = new NamedMapSchema( + allOmitEmpty([ + { + key: 'Accts', // accounts + valueSchema: AccountDeltas.encodingSchema, + }, + { + key: 'KvMods', // kvMods + valueSchema: new OptionalSchema( + new SpecialCaseBinaryStringMapSchema(KvValueDelta.encodingSchema) + ), + }, + { + key: 'Txids', // txids + valueSchema: new ByteArrayMapSchema( + IncludedTransactions.encodingSchema + ), + }, + { + key: 'Txleases', // txleases + // Note: because txleases is currently just an UntypedSchema and we are expected to decode + // null values for this field, we use OptionalSchema to coerce null values to undefined so + // that the values can be properly omitted during encoding. + valueSchema: new OptionalSchema(new UntypedSchema()), + }, + { + key: 'Creatables', // creatables + valueSchema: new OptionalSchema( + new Uint64MapSchema(ModifiedCreatable.encodingSchema) + ), + }, + { + key: 'Hdr', // blockHeader + valueSchema: BlockHeader.encodingSchema, + }, + { + key: 'StateProofNext', // stateProofNext + valueSchema: new Uint64Schema(), + }, + { + key: 'PrevTimestamp', // prevTimestamp + valueSchema: new Uint64Schema(), + }, + { + key: 'Totals', // totals + valueSchema: AccountTotals.encodingSchema, + }, + ]) + ); + + /** + * modified new accounts + */ + public accounts: AccountDeltas; + + /** + * modified kv pairs (nil == delete) + */ + public kvMods: Map; + + /** + * new Txids for the txtail and TxnCounter, mapped to txn.LastValid + */ + public txids: Map; + + // TODO: properly support txleases once we are able to decode msgpack maps with object keys. + /** + * new txleases for the txtail mapped to expiration + */ + public txleases: UntypedValue; + + /** + * new creatables creator lookup table + */ + public creatables: Map; + + /** + * new block header + */ + public blockHeader: BlockHeader; + + /** + * StateProofNext represents modification on StateProofNextRound field in the block header. If the block contains + * a valid state proof transaction, this field will contain the next round for state proof. + * otherwise it will be set to 0. + */ + public stateProofNext: bigint; + + /** + * previous block timestamp + */ + public prevTimestamp: bigint; + + /** + * The account totals reflecting the changes in this StateDelta object. + */ + public totals: AccountTotals; + + public constructor(params: { + accounts: AccountDeltas; + kvMods: Map; + txids: Map; + txleases: UntypedValue; + creatables: Map; + blockHeader: BlockHeader; + stateProofNext: bigint; + prevTimestamp: bigint; + totals: AccountTotals; + }) { + this.accounts = params.accounts; + this.kvMods = params.kvMods; + this.txids = params.txids; + this.txleases = params.txleases; + this.creatables = params.creatables; + this.blockHeader = params.blockHeader; + this.stateProofNext = params.stateProofNext; + this.prevTimestamp = params.prevTimestamp; + this.totals = params.totals; + } + + // eslint-disable-next-line class-methods-use-this + public getEncodingSchema(): Schema { + return LedgerStateDelta.encodingSchema; + } + + public toEncodingData(): Map { + return new Map([ + ['Accts', this.accounts.toEncodingData()], + [ + 'KvMods', + this.kvMods.size === 0 + ? undefined + : convertMap(this.kvMods, (key, value) => [ + key, + value.toEncodingData(), + ]), + ], + [ + 'Txids', + convertMap(this.txids, (key, value) => [key, value.toEncodingData()]), + ], + ['Txleases', this.txleases.toEncodingData()], + [ + 'Creatables', + this.creatables.size === 0 + ? undefined + : convertMap(this.creatables, (key, value) => [ + key, + value.toEncodingData(), + ]), + ], + ['Hdr', this.blockHeader.toEncodingData()], + ['StateProofNext', this.stateProofNext], + ['PrevTimestamp', this.prevTimestamp], + ['Totals', this.totals.toEncodingData()], + ]); + } + + public static fromEncodingData(data: unknown): LedgerStateDelta { + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded LedgerStateDelta: ${data}`); + } + return new LedgerStateDelta({ + accounts: AccountDeltas.fromEncodingData(data.get('Accts')), + kvMods: convertMap( + (data.get('KvMods') ?? new Map()) as Map, + (key, value) => [key, KvValueDelta.fromEncodingData(value)] + ), + txids: convertMap( + data.get('Txids') as Map, + (key, value) => [key, IncludedTransactions.fromEncodingData(value)] + ), + txleases: UntypedValue.fromEncodingData(data.get('Txleases')), + creatables: convertMap( + (data.get('Creatables') ?? new Map()) as Map, + (key, value) => [key, ModifiedCreatable.fromEncodingData(value)] + ), + blockHeader: BlockHeader.fromEncodingData(data.get('Hdr')), + stateProofNext: data.get('StateProofNext'), + prevTimestamp: data.get('PrevTimestamp'), + totals: AccountTotals.fromEncodingData(data.get('Totals')), + }); + } +} diff --git a/tests/2.Encoding.ts b/tests/2.Encoding.ts index c3bdbfc91..3c1fec7a1 100644 --- a/tests/2.Encoding.ts +++ b/tests/2.Encoding.ts @@ -11,12 +11,14 @@ import { AddressSchema, ByteArraySchema, FixedLengthByteArraySchema, + BlockHashSchema, SpecialCaseBinaryStringSchema, ArraySchema, NamedMapSchema, NamedMapEntry, Uint64MapSchema, StringMapSchema, + ByteArrayMapSchema, SpecialCaseBinaryStringMapSchema, UntypedSchema, OptionalSchema, @@ -1048,6 +1050,30 @@ describe('encoding', () => { ], preparedJsonValues: ['AAAAAAA=', 'AQIDBAU='], }, + { + name: 'BlockHashSchema', + schema: new BlockHashSchema(), + values: [ + new Uint8Array(32), + Uint8Array.from([ + 236, 203, 188, 96, 194, 35, 246, 94, 227, 223, 92, 185, 6, 143, + 198, 118, 147, 181, 197, 211, 218, 113, 81, 36, 52, 88, 237, 1, + 109, 72, 120, 38, + ]), + ], + preparedMsgpackValues: [ + new Uint8Array(32), + Uint8Array.from([ + 236, 203, 188, 96, 194, 35, 246, 94, 227, 223, 92, 185, 6, 143, + 198, 118, 147, 181, 197, 211, 218, 113, 81, 36, 52, 88, 237, 1, + 109, 72, 120, 38, + ]), + ], + preparedJsonValues: [ + 'blk-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'blk-5TF3YYGCEP3F5Y67LS4QND6GO2J3LROT3JYVCJBULDWQC3KIPATA', + ], + }, { name: 'UntypedSchema', schema: new UntypedSchema(), @@ -1167,6 +1193,77 @@ describe('encoding', () => { }, ], }, + { + name: 'ByteArrayMapSchema of BooleanSchema', + schema: new ByteArrayMapSchema(new BooleanSchema()), + values: [ + new Map(), + new Map([ + [Uint8Array.from([]), true], + [Uint8Array.from([0]), false], + [Uint8Array.from([1]), true], + [Uint8Array.from([2, 3, 4, 5]), true], + ]), + ], + preparedMsgpackValues: [ + new Map(), + new Map([ + [Uint8Array.from([]), true], + [Uint8Array.from([0]), false], + [Uint8Array.from([1]), true], + [Uint8Array.from([2, 3, 4, 5]), true], + ]), + ], + preparedJsonValues: [ + {}, + { + '': true, + 'AA==': false, + 'AQ==': true, + 'AgMEBQ==': true, + }, + ], + }, + { + name: 'ByteArrayMapSchema of SpecialCaseBinaryStringSchema', + schema: new ByteArrayMapSchema(new SpecialCaseBinaryStringSchema()), + values: [ + new Map(), + new Map([ + [Uint8Array.from([]), Uint8Array.from([])], + [Uint8Array.from([0]), Uint8Array.from([97])], + [Uint8Array.from([1]), Uint8Array.from([98])], + [Uint8Array.from([2, 3, 4, 5]), Uint8Array.from([99])], + ]), + ], + preparedMsgpackValues: [ + new Map(), + new Map([ + [Uint8Array.from([]), new RawBinaryString(Uint8Array.from([]))], + [ + Uint8Array.from([0]), + new RawBinaryString(Uint8Array.from([97])), + ], + [ + Uint8Array.from([1]), + new RawBinaryString(Uint8Array.from([98])), + ], + [ + Uint8Array.from([2, 3, 4, 5]), + new RawBinaryString(Uint8Array.from([99])), + ], + ]), + ], + preparedJsonValues: [ + {}, + { + '': '', + 'AA==': 'a', + 'AQ==': 'b', + 'AgMEBQ==': 'c', + }, + ], + }, { name: 'SpecialCaseBinaryStringMapSchema of BooleanSchema', schema: new SpecialCaseBinaryStringMapSchema(new BooleanSchema()), @@ -1416,6 +1513,15 @@ describe('encoding', () => { emptyValue: new Uint8Array(5), nonemptyValue: Uint8Array.from([1, 2, 3, 4, 5]), }, + { + schema: new BlockHashSchema(), + emptyValue: new Uint8Array(32), + nonemptyValue: Uint8Array.from([ + 236, 203, 188, 96, 194, 35, 246, 94, 227, 223, 92, 185, 6, 143, + 198, 118, 147, 181, 197, 211, 218, 113, 81, 36, 52, 88, 237, 1, + 109, 72, 120, 38, + ]), + }, { schema: new UntypedSchema(), emptyValue: undefined, @@ -1482,6 +1588,16 @@ describe('encoding', () => { ['', true], ]), }, + { + schema: new ByteArrayMapSchema(new BooleanSchema()), + emptyValue: new Map(), + nonemptyValue: new Map([ + [Uint8Array.from([]), true], + [Uint8Array.from([0]), false], + [Uint8Array.from([1]), true], + [Uint8Array.from([2, 3, 4, 5]), true], + ]), + }, { schema: new SpecialCaseBinaryStringMapSchema(new BooleanSchema()), emptyValue: new Map(), @@ -2622,4 +2738,481 @@ describe('encoding', () => { assert.deepStrictEqual(reencoded, encodedEvalDelta); }); }); + describe('LedgerStateDelta', () => { + it('should decode LedgerStateDelta correctly', async () => { + async function loadResource(name: string): Promise { + const res = await fetch( + `http://localhost:8080/tests/resources/${name}` + ); + if (!res.ok) { + throw new Error(`Failed to load resource (${res.status}): ${name}`); + } + return new Uint8Array(await res.arrayBuffer()); + } + + const stateDeltaBytes = await loadResource( + 'groupdelta-betanet_23963123_2.msgp' + ); + const stateDelta = algosdk.decodeMsgpack( + stateDeltaBytes, + algosdk.LedgerStateDelta + ); + + const expectedStateDelta = new algosdk.LedgerStateDelta({ + accounts: new algosdk.AccountDeltas({ + accounts: [ + new algosdk.BalanceRecord({ + addr: algosdk.Address.fromString( + 'TILB4MAJIUF56ZBE7CDOWOXDR57IXZFJUHJARQPW3JDEVKMU56HP3A6A54' + ), + accountData: new algosdk.AccountData({ + accountBaseData: new algosdk.AccountBaseData({ + status: 0, + microAlgos: BigInt(377962010), + rewardsBase: BigInt(12595), + rewardedMicroAlgos: BigInt(0), + authAddr: algosdk.Address.zeroAddress(), + incentiveEligible: false, + totalAppSchema: new algosdk.StateSchema({ + numUints: 2675, + numByteSlices: 2962, + }), + totalExtraAppPages: 740, + totalAppParams: BigInt(459), + totalAppLocalStates: BigInt(37), + totalAssetParams: BigInt(23), + totalAssets: BigInt(110), + totalBoxes: BigInt(0), + totalBoxBytes: BigInt(0), + lastProposed: BigInt(0), + lastHeartbeat: BigInt(0), + }), + votingData: new algosdk.VotingData({ + voteID: new Uint8Array(32), + selectionID: new Uint8Array(32), + stateProofID: new Uint8Array(64), + voteFirstValid: BigInt(0), + voteLastValid: BigInt(0), + voteKeyDilution: BigInt(0), + }), + }), + }), + new algosdk.BalanceRecord({ + addr: algosdk.Address.fromString( + 'A7NMWS3NT3IUDMLVO26ULGXGIIOUQ3ND2TXSER6EBGRZNOBOUIQXHIBGDE' + ), + accountData: new algosdk.AccountData({ + accountBaseData: new algosdk.AccountBaseData({ + status: 2, + microAlgos: BigInt(1529589813809), + rewardsBase: BigInt(0), + rewardedMicroAlgos: BigInt(0), + authAddr: algosdk.Address.zeroAddress(), + incentiveEligible: false, + totalAppSchema: new algosdk.StateSchema({ + numUints: 0, + numByteSlices: 0, + }), + totalExtraAppPages: 0, + totalAppParams: BigInt(0), + totalAppLocalStates: BigInt(0), + totalAssetParams: BigInt(0), + totalAssets: BigInt(0), + totalBoxes: BigInt(0), + totalBoxBytes: BigInt(0), + lastProposed: BigInt(0), + lastHeartbeat: BigInt(0), + }), + votingData: new algosdk.VotingData({ + voteID: new Uint8Array(32), + selectionID: new Uint8Array(32), + stateProofID: new Uint8Array(64), + voteFirstValid: BigInt(0), + voteLastValid: BigInt(0), + voteKeyDilution: BigInt(0), + }), + }), + }), + new algosdk.BalanceRecord({ + addr: algosdk.Address.fromString( + 'DSR7TNPLYXGPINSZOC76OYLXNAH6VITLH7BYO5HWLLWUOUI365LD62IHSA' + ), + accountData: new algosdk.AccountData({ + accountBaseData: new algosdk.AccountBaseData({ + status: 0, + microAlgos: BigInt(100000), + rewardsBase: BigInt(12595), + rewardedMicroAlgos: BigInt(0), + authAddr: algosdk.Address.zeroAddress(), + incentiveEligible: false, + totalAppSchema: new algosdk.StateSchema({ + numUints: 0, + numByteSlices: 0, + }), + totalExtraAppPages: 0, + totalAppParams: BigInt(0), + totalAppLocalStates: BigInt(0), + totalAssetParams: BigInt(0), + totalAssets: BigInt(0), + totalBoxes: BigInt(0), + totalBoxBytes: BigInt(0), + lastProposed: BigInt(0), + lastHeartbeat: BigInt(0), + }), + votingData: new algosdk.VotingData({ + voteID: new Uint8Array(32), + selectionID: new Uint8Array(32), + stateProofID: new Uint8Array(64), + voteFirstValid: BigInt(0), + voteLastValid: BigInt(0), + voteKeyDilution: BigInt(0), + }), + }), + }), + new algosdk.BalanceRecord({ + addr: algosdk.Address.fromString( + '5UA72YDDTT7VLRMVHDRWCUOTWMWBH5XOB4MJRYTMKDNV3GEVYY5JMT5KXM' + ), + accountData: new algosdk.AccountData({ + accountBaseData: new algosdk.AccountBaseData({ + status: 0, + microAlgos: BigInt(243300), + rewardsBase: BigInt(12595), + rewardedMicroAlgos: BigInt(0), + authAddr: algosdk.Address.zeroAddress(), + incentiveEligible: false, + totalAppSchema: new algosdk.StateSchema({ + numUints: 0, + numByteSlices: 0, + }), + totalExtraAppPages: 0, + totalAppParams: BigInt(0), + totalAppLocalStates: BigInt(0), + totalAssetParams: BigInt(0), + totalAssets: BigInt(0), + totalBoxes: BigInt(1), + totalBoxBytes: BigInt(331), + lastProposed: BigInt(0), + lastHeartbeat: BigInt(0), + }), + votingData: new algosdk.VotingData({ + voteID: new Uint8Array(32), + selectionID: new Uint8Array(32), + stateProofID: new Uint8Array(64), + voteFirstValid: BigInt(0), + voteLastValid: BigInt(0), + voteKeyDilution: BigInt(0), + }), + }), + }), + ], + appResources: [ + new algosdk.AppResourceRecord({ + id: BigInt(1508981233), + address: algosdk.Address.fromString( + 'TILB4MAJIUF56ZBE7CDOWOXDR57IXZFJUHJARQPW3JDEVKMU56HP3A6A54' + ), + params: new algosdk.AppParamsDelta({ + deleted: false, + params: new algosdk.AppParams({ + approvalProgram: algosdk.base64ToBytes( + 'CCAEAAgBOCYVCER1cmF0aW9uA1JQVAhSUFRfZnJhYwxUb3RhbFJld2FyZHMOUGVuZGluZ1Jld2FyZHMLVG90YWxTdGFrZWQKTnVtU3Rha2VycwxOZXh0RHVyYXRpb24OUmV3YXJkQXNzZXRJRHMGU3Rha2VkDkFjY3J1ZWRSZXdhcmRzC05leHRSZXdhcmRzBUFkbWluDkNsYWltZWRSZXdhcmRzCVVwZGF0ZWRBdAdVcGRhdGVyxQIIIAQAAQQGJgMLTWFzdGVyQXBwSUQBAQEAMgkxABJEMRlAAPIxGEAARTYaAIAEOIgacRJEKDYaARfAMmexJbIQNhoCF8AyshiABLc1X9GyGiKyAbOxJLIQMgqyFCKyEjYaAxfAMLIRIrIBs0IA1TYaAIAEeIIs8BJAAE42GgCABLdY2NESQAApNhoAgASb5CgbEkAAAQCxI7IQNhoBF8AcsgcisgE2GgJXAgCyBbNCAJKxI7IQMgmyBzIKYDIKeAmyCCKyAbNCAHqxJLIQMgmyFDYaAheyEjYaARfAMLIRIrIBs7ElshAoZLIYgATDFArnshopshoqshopshoqshoyCbIcMgiyMjYaARfAMLIwIrIBs0IALTEZgQUSQAABADIJKGRhFESxJLIQNjAAshEyCbIVIrIBs7EjshAyCbIJIrIBsyNDCEVzY3Jvd0lEAA1TdGFrZWRBc3NldElEDUNPTlRSQUNUX05BTUUxGyISQAGvNhoAgAQtYN77EkABbzYaAIAE/WijvxJAAU42GgCABI8NfY4SQAEtNhoAgASUjPWAEkABDDYaAIAEb+gbmxJAAOE2GgCABGFhym4SQADFNhoAgAQ1noJVEkAAqTYaAIAEoh7bJBJAAIk2GgCABMMUCucSQABJNhoAgARKrqPyEkAAHTYaAIAEGZ27ERJAAAEAMRkiEjEYIhMQRIgL9CRDMRkiEjEYIhMQRDYaASJVNRA2GgI1ETQQNBGICxokQzEZIhIxGCITEEQ2GgEiVTUMNhoCIlU1DTYaAyJVNQ42GgQiVTUPNAw0DTQONA+IB7YkQzEZIhIxGCITEEQ2GgEXiAbVJEMxGSISMRgiExBEiATcJEMxGSISMRgiExBEiAS4JEMxGSISMRgiExBENhoBNQo2GgIXNQs0CjQLiAPVJEMxGSISMRgiExBENhoBIlWIA3QkQzEZIhIxGCITEEQ2GgEiVYgDUCRDMRkiEjEYIhMQRDYaASJViAMsJEMxGSISMRgiEhBENhoBIlU1BjYaAiJVNQc2GgMiVTUINhoEIlU1CTQGNAc0CDQJiAJJJEMxGSQSQAAlMRmBAhJAABMxGYEEEkAAAQAxGCITRIgA+iRDMRgiE0SIAVMkQzEYIhNEiADtJEM1tzW2NbU0tkAAE7EkshA0tbIHNLeyCCKyAbNCABWxgQSyEDS1shQ0t7ISNLayESKyAbOJNSo0KjgQJBJAAAc0KjgSQgAENCo4CIk1IDUiIjUhNCE0IhUjCgxBABg0IjQhIwtbNCASQAAJNCEkCDUhQv/fJIkiiTUuNS01LDUrNCxAACo0KzgQJBI0KzggMgMSEDQrOAkyAxIQNCs4BzQtEhA0KzgANC4SEERCADA0KzgQgQQSNCs4IDIDEhA0KzgVMgMSEDQrOBE0LBIQNCs4FDQtEhA0KzgANC4SEESJJw9kEokxAIj/9kSJMRYkCTUBNAE4EIEGEjQBOBgiEhA0ATgZIhIQNAE4HicQEhA0ATgAMQASEEQxACcJImYxACcRImYxACklr2YxAColr2YxACcNJa9mMQAnCiWvZjEAJxE0ATg9ZokxACcJYiISRDEAJwpiJa8SRDEAJwliNQQnBScFZDQECWc0BCKICZErZDUCMQAnCmI1AyI1BTQFgQcMQQAiNAI0BSMLNAI0BSMLWzQDNAUjC1sJFl01AjQFJAg1BUL/1is0AmckQycPZBKJJwxkEoknDGQSiScMZBKJNaA1nzSfcgA1ojWhNJ9yBzWkNaM0n8AygAtNYXN0ZXJBcHBJRGU1pjWlNKI0oScQEhA0ozSgwBwSEDSmEDSlMggSEDSgwBwnEWI0n8AyEhCJNRc1FjUVNRQiJxRlNRk1GDQZFEQnFIAJUEFDVCBGQVJNZ4AHVkVSU0lPToFkZycMJxJnJw8nEmcnBSJnJw4yB2cnBiJnKCJnJwciZyklr2cqJa9nJwslr2cnBCWvZyslr2cnDSWvZycIJxJnIicTZTUbNRo0GxREJxM0FcAwZycMNBbAHGcnDzQXwBxnsYEGshA0FMAyshiABLc1X9GyGiKyAbOABkVzY3JvdycQv4k1HDEAiP7kRCcPNBzAHGeJNR0xAIj+2UQnDDQdwBxniTUeMQCI/s5EJwhkNR80HxUjCoEHDEQ0HzQewDCI/UsURDQfNB7AMBZQNR8nCDQfZzQewDAiE0EAE7GBBLIQMgqyFDQewDCyESKyAbOJNSQ1IzEAiP6ERCcOZDIHEkQnB2QiEkQ0IyJZRDQkRCcIZDUoIjUlNCU0IyJZDEAAYSWvNSkiNSU0JTQjIlkMQAAgKGQiEkAADScLNClnJwc0JGdCAG4nBDQpZyg0JGdCAGI0IyM0JQuBAghbNSc0KTQnIwsxFjQjIlk1JjQmCTQlCIj8gRZdNSk0JSQINSVC/6Y0IyM0JQuBAghbNScxFjQjIlk1JjQmCTQlCDQoNCcjC1syCicMZIj8jjQlJAg1JUL/Y4knBCcLZGcoJwdkZycLJa9nJwciZ4knBWQiEyhkIhMQQQHXMgcnDmQJNTEoZDQxSg1NNS8nB2Q0MTQvCUoNTTUwJwhkFSMKNTQoZDUyJwVkNTMpZDU1KmQ1NicEZDU3K2Q1OCI1OTQ5NDQMQAEGKChkNC8JZyk0NWcqNDZnJwQ0N2crNDhnKGQiEkEBbScEJwtkZygnB2RnJwslr2cnByJnNDBBAVQnCGQVIwo1QyhkNUEnBWQ1QilkNUQqZDVFJwRkNUYrZDVHIjVINEg0QwxAABsoKGQ0MAlnKTREZyo0RWcnBDRGZys0R2dCAQw0RjRIIwtbNUk0STQwHTRBlzVMNEw0Qgo1TTRMNEIYIjRClzVONEU0SCMLWzROHjVLNU80RDRIIwtbNE0INE8INUo0RDRIIws0ShZdNUQ0RTRIIws0SxZdNUU0RjRIIws0STRMCRZdNUY0RzRIIws0RzRIIwtbNEwIFl01RzRIJAg1SEL/VzQ3NDkjC1s1OjQ6NC8dNDKXNT00PTQzCjU+ND00MxgiNDOXNT80NjQ5IwtbND8eNTw1QDQ1NDkjC1s0Pgg0QAg1OzQ1NDkjCzQ7Fl01NTQ2NDkjCzQ8Fl01NjQ3NDkjCzQ6ND0JFl01NzQ4NDkjCzQ4NDkjC1s0PQgWXTU4NDkkCDU5Qv5sJw4yB2eJNVAnCGQVIwo1UyhkNVEnBWQ1UilkNVQqZDVVJwRkNVYrZDVXIjVYNFg0UwxBAIY0VjRYIwtbNVk0WTRQHTRRlzVcNFw0Ugo1XTRcNFIYIjRSlzVeNFU0WCMLWzReHjVbNV80VDRYIwtbNF0INF8INVo0VDRYIws0WhZdNVQ0VTRYIws0WxZdNVU0VjRYIws0WTRcCRZdNVY0VzRYIws0VzRYIwtbNFwIFl01VzRYJAg1WEL/cigoZDRQCWcpNFRnKjRVZycENFZnKzRXZ4k1YzViNWE1YDRgcgc1ZTVkNGByCDVnNWY0ZUQ0Z0Q0ZjRhwBwSRDRkIicJYzVpNWg0aUQ0YDRiiPrGRCcFZCITKGQiExBAAMcnDjIHZylkNYsqZDWMNGQnCWI1jTRkKWI1jjRkKmI1jzRkJwpiNZAiNZE0kScIZBUjCgxBAmg0izSRIwtbNZI0jDSRIwtbNZM0jjSRIwtbNZQ0jzSRIwtbNZU0lTSTDkAAVIH///////////8BNJUJNJMIJAg1mDSSNJQJJAk1lzRkJwliNJgdNQA1mTRkJwliNJcLNJkINZY0kDSRIws0kDSRIwtbNJYIFl01kDSRJAg1kUL/dDSTNJUJNZg0kjSUCTWXQv+5MgcnDmQJNWwoZDRsSg1NNWonB2Q0bDRqCUoNTTVrJwhkFSMKNW8oZDVtJwVkNW4pZDVwKmQ1cScEZDVyK2Q1cyI1dDR0NG8MQAEGKChkNGoJZyk0cGcqNHFnJwQ0cmcrNHNnKGQiEkH+zycEJwtkZygnB2RnJwslr2cnByJnNGtB/rYnCGQVIwo1fihkNXwnBWQ1fSlkNX8qZDWAJwRkNYErZDWCIjWDNIM0fgxAABsoKGQ0awlnKTR/Zyo0gGcnBDSBZys0gmdC/m40gTSDIwtbNYQ0hDRrHTR8lzWHNIc0fQo1iDSHNH0YIjR9lzWJNIA0gyMLWzSJHjWGNYo0fzSDIwtbNIgINIoINYU0fzSDIws0hRZdNX80gDSDIws0hhZdNYA0gTSDIws0hDSHCRZdNYE0gjSDIws0gjSDIwtbNIcIFl01gjSDJAg1g0L/VzRyNHQjC1s1dTR1NGodNG2XNXg0eDRuCjV5NHg0bhgiNG6XNXo0cTR0IwtbNHoeNXc1ezRwNHQjC1s0eQg0ewg1djRwNHQjCzR2Fl01cDRxNHQjCzR3Fl01cTRyNHQjCzR1NHgJFl01cjRzNHQjCzRzNHQjC1s0eAgWXTVzNHQkCDV0Qv5sNGZzAjWbNZo0ZicTZHAANZ01nDSaMgMSNJwLNZ4nBScFZDSNCTSeCGc0jTSeiAFTNGQnCTSeZjRkKTSLZjRkKjSMZjRkJwo0kGaJNao1qScIZDWsNKwVIwo1qycNZDWtNKnAHCcKYjWuNKnAHCcNYjWvNKoiWTWwIjW0NLQ0sAxBAGs0qiM0tAuBAghbNbE0sTSrDkQ0rDSxIwtbNbM0rjSxIwtbNbI0qcAcNLM0soj1qDSuNLEjCyIWXTWuNK00sSMLNK00sSMLWzSyCBZdNa00rzSxIws0rzSxIwtbNLIIFl01rzS0JAg1tEL/jScNNK1nNKnAHCcKNK5mNKnAHCcNNK9miTEAJwliNbonBScFZDS6CWc0uiKIAJErZDW4MQAnCmI1uSI1uzS7gQcMQQAiNLg0uyMLNLg0uyMLWzS5NLsjC1sJFl01uDS7JAg1u0L/1is0uGeJNRM1EjQSNBMUEEAAFDQSFDQTEEEAEycGJwZkJAhnQgAIJwYnBmQkCWeJNag1pzSnNKgUEEAAFDSnFDSoEEEAEycGJwZkJAhnQgAIJwYnBmQkCWeJNb01vDS8NL0UEEAAFDS8FDS9EEEAEycGJwZkJAhnQgAIJwYnBmQkCWeJ' + ), + clearStateProgram: algosdk.base64ToBytes( + 'CCADAQAIJgMKTnVtU3Rha2VycwtUb3RhbFN0YWtlZAxUb3RhbFJld2FyZHMxGyMSQAABAIgAAiJDMQCABlN0YWtlZGI1AikpZDQCCWc0AiOIAEwqZDUAMQCADkFjY3J1ZWRSZXdhcmRzYjUBIzUDNAOBBwxBACI0ADQDJAs0ADQDJAtbNAE0AyQLWwkWXTUANAMiCDUDQv/WKjQAZyJDNQU1BDQENAUUEEAAEjQEFDQFEEEADygoZCIIZ0IABigoZCIJZ4k=' + ), + globalState: new Map([ + [ + algosdk.coerceToBytes('Admin'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'mhYeMAlFC99kJPiG6zrjj36L5Kmh0gjB9tpGSqmU744=' + ), + }), + ], + [ + algosdk.coerceToBytes('CONTRACT_NAME'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.coerceToBytes('PACT FARM'), + }), + ], + [ + algosdk.coerceToBytes('ClaimedRewards'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' + ), + }), + ], + [ + algosdk.coerceToBytes('Duration'), + new algosdk.TealValue({ + type: 2, + }), + ], + [ + algosdk.coerceToBytes('NextDuration'), + new algosdk.TealValue({ + type: 2, + }), + ], + [ + algosdk.coerceToBytes('NextRewards'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' + ), + }), + ], + [ + algosdk.coerceToBytes('NumStakers'), + new algosdk.TealValue({ + type: 2, + }), + ], + [ + algosdk.coerceToBytes('PendingRewards'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' + ), + }), + ], + [ + algosdk.coerceToBytes('RPT'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' + ), + }), + ], + [ + algosdk.coerceToBytes('RPT_frac'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' + ), + }), + ], + [ + algosdk.coerceToBytes('RewardAssetIDs'), + new algosdk.TealValue({ + type: 1, + }), + ], + [ + algosdk.coerceToBytes('StakedAssetID'), + new algosdk.TealValue({ + type: 2, + uint: BigInt(156390370), + }), + ], + [ + algosdk.coerceToBytes('TotalRewards'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' + ), + }), + ], + [ + algosdk.coerceToBytes('TotalStaked'), + new algosdk.TealValue({ + type: 2, + }), + ], + [ + algosdk.coerceToBytes('UpdatedAt'), + new algosdk.TealValue({ + type: 2, + uint: BigInt(1675257832), + }), + ], + [ + algosdk.coerceToBytes('Updater'), + new algosdk.TealValue({ + type: 1, + bytes: algosdk.base64ToBytes( + 'mhYeMAlFC99kJPiG6zrjj36L5Kmh0gjB9tpGSqmU744=' + ), + }), + ], + [ + algosdk.coerceToBytes('VERSION'), + new algosdk.TealValue({ + type: 2, + uint: BigInt(100), + }), + ], + ]), + localStateSchema: new algosdk.StateSchema({ + numByteSlices: 4, + numUints: 2, + }), + globalStateSchema: new algosdk.StateSchema({ + numByteSlices: 10, + numUints: 7, + }), + extraProgramPages: 2, + }), + }), + state: new algosdk.AppLocalStateDelta({ + deleted: false, + }), + }), + ], + assetResources: [], + }), + kvMods: new Map([ + [ + algosdk.base64ToBytes('Yng6AAAAAFnxOfFFc2Nyb3c='), + new algosdk.KvValueDelta({ + data: algosdk.base64ToBytes( + 'CCAEAAEEBiYDC01hc3RlckFwcElEAQEBADIJMQASRDEZQADyMRhAAEU2GgCABDiIGnESRCg2GgEXwDJnsSWyEDYaAhfAMrIYgAS3NV/RshoisgGzsSSyEDIKshQishI2GgMXwDCyESKyAbNCANU2GgCABHiCLPASQABONhoAgAS3WNjREkAAKTYaAIAEm+QoGxJAAAEAsSOyEDYaARfAHLIHIrIBNhoCVwIAsgWzQgCSsSOyEDIJsgcyCmAyCngJsggisgGzQgB6sSSyEDIJshQ2GgIXshI2GgEXwDCyESKyAbOxJbIQKGSyGIAEwxQK57IaKbIaKrIaKbIaKrIaMgmyHDIIsjI2GgEXwDCyMCKyAbNCAC0xGYEFEkAAAQAyCShkYRREsSSyEDYwALIRMgmyFSKyAbOxI7IQMgmyCSKyAbMjQw==' + ), + oldData: algosdk.base64ToBytes( + 'CCAEAAEEBiYDC01hc3RlckFwcElEAQEBADIJMQASRDEZQADyMRhAAEU2GgCABDiIGnESRCg2GgEXwDJnsSWyEDYaAhfAMrIYgAS3NV/RshoisgGzsSSyEDIKshQishI2GgMXwDCyESKyAbNCANU2GgCABHiCLPASQABONhoAgAS3WNjREkAAKTYaAIAEm+QoGxJAAAEAsSOyEDYaARfAHLIHIrIBNhoCVwIAsgWzQgCSsSOyEDIJsgcyCmAyCngJsggisgGzQgB6sSSyEDIJshQ2GgIXshI2GgEXwDCyESKyAbOxJbIQKGSyGIAEwxQK57IaKbIaKrIaKbIaKrIaMgmyHDIIsjI2GgEXwDCyMCKyAbNCAC0xGYEFEkAAAQAyCShkYRREsSSyEDYwALIRMgmyFSKyAbOxI7IQMgmyCSKyAbMjQw==' + ), + }), + ], + ]), + txids: new Map([ + [ + algosdk.base64ToBytes( + 'g3NWme3GAy5uHfd8BQO06da2MjdGJ9EuuikeSD3Nuqk=' + ), + new algosdk.IncludedTransactions({ + lastValid: BigInt(23964120), + intra: 1, + }), + ], + [ + algosdk.base64ToBytes( + 'j6CIOjVZijXyqqTJA4xJjoA4oSmiM6Il5qsV/O3H3+Q=' + ), + new algosdk.IncludedTransactions({ + lastValid: BigInt(23964120), + intra: 0, + }), + ], + ]), + txleases: new algosdk.UntypedValue(undefined), + creatables: new Map([ + [ + BigInt(1508981233), + new algosdk.ModifiedCreatable({ + creatableType: 1, + created: true, + creator: algosdk.Address.fromString( + 'TILB4MAJIUF56ZBE7CDOWOXDR57IXZFJUHJARQPW3JDEVKMU56HP3A6A54' + ), + ndeltas: 0, + }), + ], + ]), + blockHeader: new algosdk.BlockHeader({ + round: BigInt(23963123), + branch: algosdk.base64ToBytes( + 'NPCkBgM/t8nRvRaaVqSeWHCyYUdxEghgQglgtERCuqE=' + ), + seed: algosdk.base64ToBytes( + 'yxhfocGJCuC+DKVcfgwo0juV9jNEUvMiU1uJl0Y1MNk=' + ), + txnCommitments: new algosdk.TxnCommitments({ + nativeSha512_256Commitment: algosdk.base64ToBytes( + 'FIrR4OYcMHA4fhT2vEScSvbaCkETZd+BPtttEQi8DiI=' + ), + sha256Commitment: algosdk.base64ToBytes( + 'Hj1OQRa1jURkxJkRtXOKTrKSrm/MIrP5wmTnUuNq3ew=' + ), + }), + timestamp: BigInt(1675257836), + genesisID: 'betanet-v1.0', + genesisHash: algosdk.base64ToBytes( + 'mFgazF+2uRS1tMiL9dsj01hJGySEmPN28B/TjjvpVW0=' + ), + proposer: algosdk.Address.zeroAddress(), + feesCollected: BigInt(0), + bonus: BigInt(0), + proposerPayout: BigInt(0), + rewardState: new algosdk.RewardState({ + feeSink: algosdk.Address.fromString( + 'A7NMWS3NT3IUDMLVO26ULGXGIIOUQ3ND2TXSER6EBGRZNOBOUIQXHIBGDE' + ), + rewardsPool: algosdk.Address.fromString( + '7777777777777777777777777777777777777777777777777774MSJUVU' + ), + rewardsLevel: BigInt(12595), + rewardsRate: BigInt(0), + rewardsResidue: BigInt(3846799357), + rewardsRecalculationRound: BigInt(24000000), + }), + upgradeState: new algosdk.UpgradeState({ + currentProtocol: + 'https://github.com/algorandfoundation/specs/tree/44fa607d6051730f5264526bf3c108d51f0eadb6', + nextProtocol: '', + nextProtocolApprovals: BigInt(0), + nextProtocolVoteBefore: BigInt(0), + nextProtocolSwitchOn: BigInt(0), + }), + upgradeVote: new algosdk.UpgradeVote({ + upgradePropose: '', + upgradeDelay: BigInt(0), + upgradeApprove: false, + }), + txnCounter: BigInt(1508981323), + stateproofTracking: new Map([ + [ + 0, + new algosdk.StateProofTrackingData({ + stateProofVotersCommitment: new Uint8Array(), + stateProofOnlineTotalWeight: BigInt(0), + stateProofNextRound: BigInt(23963136), + }), + ], + ]), + participationUpdates: new algosdk.ParticipationUpdates({ + expiredParticipationAccounts: [], + absentParticipationAccounts: [], + }), + }), + stateProofNext: BigInt(0), + prevTimestamp: BigInt(0), + totals: new algosdk.AccountTotals({ + online: new algosdk.AlgoCount({ + money: BigInt(0), + rewardUnits: BigInt(0), + }), + offline: new algosdk.AlgoCount({ + money: BigInt(0), + rewardUnits: BigInt(0), + }), + notParticipating: new algosdk.AlgoCount({ + money: BigInt(0), + rewardUnits: BigInt(0), + }), + rewardsLevel: BigInt(0), + }), + }); + + assert.deepStrictEqual(stateDelta, expectedStateDelta); + + // Avoid comparing reencoded to stateDeltaBytes because this SDK uses omit empty for the fields, + // so the produced encoding will be different. Instead we decode and compare again. + const reencoded = algosdk.encodeMsgpack(stateDelta); + const roundTripDecoded = algosdk.decodeMsgpack( + reencoded, + algosdk.LedgerStateDelta + ); + assert.deepStrictEqual(roundTripDecoded, expectedStateDelta); + }); + }); }); diff --git a/tests/cucumber/browser/test.js b/tests/cucumber/browser/test.js index 45ae7b977..d37fc6991 100644 --- a/tests/cucumber/browser/test.js +++ b/tests/cucumber/browser/test.js @@ -70,6 +70,10 @@ window.makeObject = function makeObject(obj) { return { ...obj }; }; +window.makeMap = function makeMap(m) { + return new Map(m); +}; + window.parseJSON = function parseJSON(json) { return JSON.parse(json); }; diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index a027d4ce5..82b8b59b3 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -74,6 +74,10 @@ function makeObject(obj) { return { ...obj }; } +function makeMap(m) { + return new Map(m); +} + function parseJSON(json) { return JSON.parse(json); } @@ -602,8 +606,7 @@ module.exports = function getSteps(options) { }); Then('the node should be healthy', async function () { - const health = await this.v2Client.healthCheck().do(); - assert.deepStrictEqual(health, makeObject({})); + await this.v2Client.healthCheck().do(); }); Then('I get the ledger supply', async function () { @@ -1636,7 +1639,9 @@ module.exports = function getSteps(options) { ); assert.ok(err.response.body); - this.actualMockResponse = err.response.body; + this.actualMockResponse = err.response.parseBodyAsJSON({ + intDecoding: algosdk.IntDecoding.MIXED, + }); caughtError = true; } if (!caughtError) { @@ -1747,7 +1752,9 @@ module.exports = function getSteps(options) { ); assert.ok(err.response.body); - this.actualMockResponse = err.response.body; + this.actualMockResponse = err.response.parseBodyAsJSON({ + intDecoding: algosdk.IntDecoding.MIXED, + }); caughtError = true; } if (!caughtError) { @@ -1807,6 +1814,60 @@ module.exports = function getSteps(options) { return prunedObject; } + function pruneDefaultValuesFromMap(m) { + function isMap(x) { + // workaround for firefox + const other = makeMap([]); + return x instanceof other.constructor; + } + + function isUint8Array(x) { + // workaround for firefox + const other = makeUint8Array(); + return x instanceof other.constructor; + } + + if (!isMap(m)) { + throw new Error('pruneDefaultValuesFromMap expects a map.'); + } + const prunedMap = makeMap(m); + for (const [key, value] of Array.from(prunedMap.entries())) { + if ( + value === undefined || + value === null || + value === 0 || + value === BigInt(0) || + value === '' || + value === false || + (Array.isArray(value) && value.length === 0) || + (isMap(value) && value.size === 0) || + (isUint8Array(value) && + (value.byteLength === 0 || value.every((byte) => byte === 0))) + ) { + prunedMap.delete(key); + continue; + } + if (Array.isArray(value)) { + prunedMap.set( + key, + value.map((element) => + isMap(element) ? pruneDefaultValuesFromMap(element) : element + ) + ); + continue; + } + if (isMap(value)) { + const prunedValue = pruneDefaultValuesFromMap(value); + if (prunedValue.size === 0) { + prunedMap.delete(key); + } else { + prunedMap.set(key, prunedValue); + } + } + } + return prunedMap; + } + Then('the parsed response should equal the mock response.', function () { let expectedJsonNeedsPruning = true; @@ -1814,17 +1875,9 @@ module.exports = function getSteps(options) { if (this.expectedMockResponseCode === 200) { if (responseFormat === 'json') { 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" + // Handles responses which don't implement Encodable encodedResponseObject = algosdk.stringifyJSON( this.actualMockResponse ); @@ -1856,8 +1909,15 @@ module.exports = function getSteps(options) { ); } } else { - actualResponseObject = algosdk.decodeObj(encodedResponseObject); - parsedExpectedMockResponse = algosdk.decodeObj(expectedMockResponse); + actualResponseObject = algosdk.msgpackRawDecodeAsMap( + encodedResponseObject + ); + parsedExpectedMockResponse = + algosdk.msgpackRawDecodeAsMap(expectedMockResponse); + + parsedExpectedMockResponse = pruneDefaultValuesFromMap( + parsedExpectedMockResponse + ); } assert.deepStrictEqual(actualResponseObject, parsedExpectedMockResponse); diff --git a/tests/cucumber/unit.tags b/tests/cucumber/unit.tags index 1dbf5bd09..48232f779 100644 --- a/tests/cucumber/unit.tags +++ b/tests/cucumber/unit.tags @@ -23,11 +23,11 @@ @unit.responses.unlimited_assets @unit.responses.blocksummary @unit.responses.minbalance -@unit.responses.statedelta.json +@unit.responses.statedelta @unit.responses.sync @unit.responses.timestamp @unit.responses.txid.json -@unit.responses.txngroupdeltas.json +@unit.responses.txngroupdeltas @unit.sourcemapv2 @unit.stateproof.paths @unit.stateproof.responses diff --git a/tests/resources/groupdelta-betanet_23963123_2.msgp b/tests/resources/groupdelta-betanet_23963123_2.msgp new file mode 100644 index 000000000..ddb18cebe Binary files /dev/null and b/tests/resources/groupdelta-betanet_23963123_2.msgp differ diff --git a/v2_TO_v3_MIGRATION_GUIDE.md b/v2_TO_v3_MIGRATION_GUIDE.md index 259a6c09f..e1ffe2b5c 100644 --- a/v2_TO_v3_MIGRATION_GUIDE.md +++ b/v2_TO_v3_MIGRATION_GUIDE.md @@ -125,7 +125,7 @@ Every transaction type has a base `make*` function whose single parameter object These interfaces differ slightly from the v2 types. Some field names have changed in order to be more consistent with their usage in other contexts, and some types have changed as well. The table below covers all name changes and cases where types become more restrictive. Fields where the only change was a type becoming less restrictive (e.g. `string` to `string | Address`) are not covered here. -| Transaction Type | v2 Parameter | v2 Parameter Type | v2 Parameter | v3 Parameter Type | Notes | +| Transaction Type | v2 Parameter | v2 Parameter Type | v3 Parameter | v3 Parameter Type | Notes | | ---------------- | ------------------- | ---------------------- | ------------------- | ------------------- | ------------------------------------------ | | All | `from` | `string` | `sender` | `string \| Address` | | | Payment | `to` | `string` | `receiver` | `string \| Address` | |