From ce1cd06d7cc99694ec50a138c87c3dcff68a20d1 Mon Sep 17 00:00:00 2001 From: ehanoc Date: Mon, 10 Feb 2025 19:22:44 +0000 Subject: [PATCH 1/6] BIGINTs and Schema validation updates Signed-off-by: ehanoc --- lib/algorand.encoder.spec.ts | 15 +++++---- lib/algorand.transaction.acfg.ts | 4 +-- lib/algorand.transaction.afrz.ts | 4 +-- lib/algorand.transaction.axfer.ts | 4 +-- lib/algorand.transaction.crafter.spec.ts | 43 ++++++++++++++---------- lib/algorand.transaction.crafter.ts | 2 +- lib/algorand.transaction.header.ts | 4 +-- lib/algorand.transaction.keyreg.ts | 4 +-- lib/algorand.transaction.pay.ts | 10 +++--- lib/schemas/pay.transaction.json | 2 +- lib/schemas/transaction.header.json | 2 +- package.json | 10 ++++-- 12 files changed, 58 insertions(+), 46 deletions(-) diff --git a/lib/algorand.encoder.spec.ts b/lib/algorand.encoder.spec.ts index de6d39a..6745e59 100644 --- a/lib/algorand.encoder.spec.ts +++ b/lib/algorand.encoder.spec.ts @@ -10,6 +10,7 @@ import {AssetParamsBuilder} from "./algorand.asset.params"; import {AssetConfigTransaction} from "./algorand.transaction.acfg"; import {AssetFreezeTransaction} from "./algorand.transaction.afrz"; import {AssetTransferTransaction} from "./algorand.transaction.axfer"; +import algosdk from 'algosdk' export function concatArrays(...arrs: ArrayLike[]) { const size = arrs.reduce((sum, arr) => sum + arr.length, 0) @@ -42,7 +43,7 @@ describe("Algorand Encoding", () => { // to algorand address const to: string = algoEncoder.encodeAddress(Buffer.from(randomBytes(32))) - const encodedTransaction: Uint8Array = algorandCrafter.pay(1000, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() + const encodedTransaction: Uint8Array = algorandCrafter.pay(1000n, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() const signature: Uint8Array = new Uint8Array(Buffer.from(randomBytes(64))) const signedTransaction: Uint8Array = algorandCrafter.addSignature(encodedTransaction, signature) @@ -79,7 +80,7 @@ describe("Algorand Encoding", () => { txn: { rcv: algoEncoder.decodeAddress(to), snd: algoEncoder.decodeAddress(from), - amt: 1000, + amt: 1000n, fv: 1000, lv: 2000, fee: 1000, @@ -105,7 +106,7 @@ describe("Algorand Encoding", () => { const note: string = Buffer.from(randomBytes(32)).toString("base64") // create pay transaction - const txn: PayTransaction = algorandCrafter.pay(1000, from, to).addFirstValidRound(1000).addLastValidRound(2000).addNote(note).addFee(1000).get() + const txn: PayTransaction = algorandCrafter.pay(1000n, from, to).addFirstValidRound(1000).addLastValidRound(2000).addNote(note).addFee(1000n).get() const encoded: Uint8Array = txn.encode() expect(encoded).toEqual(algoEncoder.encodeTransaction(txn)) @@ -133,7 +134,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note) - .addFee(1000) + .addFee(1000n) .get() const encoded: Uint8Array = txn.encode() @@ -166,7 +167,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note, "base64") - .addFee(1000) + .addFee(1000n) .addGroup(grp) .addRekey(from) .addLease(lx) @@ -191,7 +192,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note, "base64") - .addFee(1000) + .addFee(1000n) .addGroup(grp) .addRekey(from) .addLease(lx) @@ -218,7 +219,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note, "base64") - .addFee(1000) + .addFee(1000n) .addGroup(grp) .addRekey(from) .addLease(lx) diff --git a/lib/algorand.transaction.acfg.ts b/lib/algorand.transaction.acfg.ts index e518d77..dd17007 100644 --- a/lib/algorand.transaction.acfg.ts +++ b/lib/algorand.transaction.acfg.ts @@ -78,7 +78,7 @@ export class AssetConfigTxBuilder implements IAssetConfigTxBuilder { this.tx.gen = genesisId this.tx.gh = new Uint8Array(Buffer.from(genesisHash, "base64")) this.tx.type = "acfg" - this.tx.fee = 1000 + this.tx.fee = 1000n } addAssetId(caid: number | bigint): IAssetConfigTxBuilder { this.tx.caid = caid @@ -92,7 +92,7 @@ export class AssetConfigTxBuilder implements IAssetConfigTxBuilder { this.tx.snd = this.encoder.decodeAddress(sender) return this } - addFee(fee: number): IAssetConfigTxBuilder { + addFee(fee: bigint): IAssetConfigTxBuilder { this.tx.fee = fee return this } diff --git a/lib/algorand.transaction.afrz.ts b/lib/algorand.transaction.afrz.ts index 33e8ea7..7177277 100644 --- a/lib/algorand.transaction.afrz.ts +++ b/lib/algorand.transaction.afrz.ts @@ -75,7 +75,7 @@ export class AssetFreezeTxBuilder implements IAssetFreezeTxBuilder { this.tx.gen = genesisId this.tx.gh = new Uint8Array(Buffer.from(genesisHash, "base64")) this.tx.type = "afrz" - this.tx.fee = 1000 + this.tx.fee = 1000n } addFreezeAccount(fadd: string): IAssetFreezeTxBuilder { this.tx.fadd = this.encoder.decodeAddress(fadd) @@ -93,7 +93,7 @@ export class AssetFreezeTxBuilder implements IAssetFreezeTxBuilder { this.tx.snd = this.encoder.decodeAddress(sender) return this } - addFee(fee: number): IAssetFreezeTxBuilder { + addFee(fee: bigint): IAssetFreezeTxBuilder { this.tx.fee = fee return this } diff --git a/lib/algorand.transaction.axfer.ts b/lib/algorand.transaction.axfer.ts index af315d7..265183b 100644 --- a/lib/algorand.transaction.axfer.ts +++ b/lib/algorand.transaction.axfer.ts @@ -103,7 +103,7 @@ export class AssetTransferTxBuilder implements IAssetTransferTxBuilder { this.tx.gen = genesisId this.tx.gh = new Uint8Array(Buffer.from(genesisHash, "base64")) this.tx.type = "axfer" - this.tx.fee = 1000 + this.tx.fee = 1000n } addAssetId(xaid: number | bigint): IAssetTransferTxBuilder { this.tx.xaid = xaid @@ -129,7 +129,7 @@ export class AssetTransferTxBuilder implements IAssetTransferTxBuilder { this.tx.snd = this.encoder.decodeAddress(sender) return this } - addFee(fee: number): IAssetTransferTxBuilder { + addFee(fee: bigint): IAssetTransferTxBuilder { this.tx.fee = fee return this } diff --git a/lib/algorand.transaction.crafter.spec.ts b/lib/algorand.transaction.crafter.spec.ts index 579b385..25fae45 100644 --- a/lib/algorand.transaction.crafter.spec.ts +++ b/lib/algorand.transaction.crafter.spec.ts @@ -5,6 +5,8 @@ import { AlgorandTransactionCrafter } from "./algorand.transaction.crafter" import { PayTransaction } from "./algorand.transaction.pay" import { KeyregTransaction } from "./algorand.transaction.keyreg" import Ajv, {type JSONSchemaType} from "ajv" +import addFormat from 'ajv-formats' +import addKeywords from 'ajv-keywords' import path from "path" import fs from 'fs' import {AssetConfigTransaction} from "./algorand.transaction.acfg"; @@ -16,26 +18,30 @@ import {ITransactionHeaderBuilder, TransactionHeader} from "./algorand.transacti // Setup Validator const ajv = new Ajv() +addFormat(ajv) + +// Define the custom keyword 'typeof' +ajv.addKeyword({ + keyword: 'typeof', + validate: function(schema: string, data: any) { + if (schema === 'bigint') { + return typeof data === 'bigint'; + } + + console.log("Unknown type: ", schema) + + // Add more types as needed + return false; + }, + errors: false + }); + ajv.addSchema(JSON.parse(fs.readFileSync(path.resolve(__dirname, "schemas/bytes32.json"), "utf8"))) ajv.addSchema(JSON.parse(fs.readFileSync(path.resolve(__dirname, "schemas/bytes64.json"), "utf8"))) ajv.addSchema(JSON.parse(fs.readFileSync(path.resolve(__dirname, "schemas/transaction.header.json"), "utf8"))) ajv.addSchema(JSON.parse(fs.readFileSync(path.resolve(__dirname, "schemas/asset.params.json"), "utf8"))) -type TestTransactionHeader = { - snd: Uint8Array, - note: Uint8Array, - grp: Uint8Array, - lx: Uint8Array, - - gen: string - gh: Uint8Array - fee: number - fv: number - lv: number - rekey: Uint8Array -} - describe("Algorand Transaction Crafter", () => { let algorandCrafter: AlgorandTransactionCrafter let algoEncoder: AlgorandEncoder @@ -59,6 +65,7 @@ describe("Algorand Transaction Crafter", () => { .addRekey(algoEncoder.encodeAddress(Buffer.from(snd))) .addLease(lx!!) } + beforeEach(async () => { algorandCrafter = new AlgorandTransactionCrafter(genesisId, genesisHash) algoEncoder = new AlgorandEncoder() @@ -71,7 +78,7 @@ describe("Algorand Transaction Crafter", () => { lx: randomBytes(32), gen: genesisId, gh: new Uint8Array(Buffer.from(genesisHash, "base64")), - fee: 1000, + fee: 1000n, fv: 1000, lv: 2000, rekey: sender @@ -90,7 +97,7 @@ describe("Algorand Transaction Crafter", () => { // to algorand address const to: string = algoEncoder.encodeAddress(Buffer.from(randomBytes(32))) - const encodedTransaction: Uint8Array = algorandCrafter.pay(1000, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() + const encodedTransaction: Uint8Array = algorandCrafter.pay(1000n, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() const signature: Uint8Array = Buffer.from(randomBytes(64)) const result: Uint8Array = algorandCrafter.addSignature(encodedTransaction, signature) expect(result).toBeDefined() @@ -115,7 +122,7 @@ describe("Algorand Transaction Crafter", () => { // create pay transaction const txn: PayTransaction = withTestTransactionHeader( algorandCrafter - .pay(1000, from, to) + .pay(1000n, from, to) .addCloseTo(from) ).get() @@ -124,7 +131,7 @@ describe("Algorand Transaction Crafter", () => { expect(txn).toEqual({ rcv: algoEncoder.decodeAddress(to), type: "pay", - amt: 1000, + amt: 1000n, close: algoEncoder.decodeAddress(from), ...transactionHeader, }) diff --git a/lib/algorand.transaction.crafter.ts b/lib/algorand.transaction.crafter.ts index a14cb2f..007de40 100644 --- a/lib/algorand.transaction.crafter.ts +++ b/lib/algorand.transaction.crafter.ts @@ -22,7 +22,7 @@ export class AlgorandTransactionCrafter extends Crafter { * @param from The address of the account that pays the fee and amount. * @param to The address of the account that receives the amount. */ - pay(amount: number, from: string, to: string): IPayTxBuilder { + pay(amount: bigint, from: string, to: string): IPayTxBuilder { return new PayTxBuilder(this.genesisId, this.genesisHash).addAmount(amount).addSender(from).addReceiver(to) } /** diff --git a/lib/algorand.transaction.header.ts b/lib/algorand.transaction.header.ts index 75405b0..c21a875 100644 --- a/lib/algorand.transaction.header.ts +++ b/lib/algorand.transaction.header.ts @@ -30,7 +30,7 @@ export abstract class TransactionHeader { * * Paid by the sender to the FeeSink to prevent denial-of-service. The minimum fee on Algorand is currently 1000 microAlgos. */ - fee: number + fee: bigint /** * First Valid * @@ -108,7 +108,7 @@ export interface ITransactionHeaderBuilder { * * @param fee Paid by the sender to the FeeSink to prevent denial-of-service. The minimum fee on Algorand is currently 1000 microAlgos. */ - addFee(fee: number): T + addFee(fee: bigint): T /** * Add First Valid Round diff --git a/lib/algorand.transaction.keyreg.ts b/lib/algorand.transaction.keyreg.ts index e18aa0c..57267cb 100644 --- a/lib/algorand.transaction.keyreg.ts +++ b/lib/algorand.transaction.keyreg.ts @@ -132,7 +132,7 @@ export class KeyregTxBuilder implements IKeyregTxBuilder { this.tx.gen = genesisId this.tx.gh = new Uint8Array(Buffer.from(genesisHash, "base64")) this.tx.type = "keyreg" - this.tx.fee = 1000 + this.tx.fee = 1000n } addVoteKey(voteKey: string, encoding: BufferEncoding = "base64"): IKeyregTxBuilder { this.tx.votekey = new Uint8Array(Buffer.from(voteKey, encoding)) @@ -166,7 +166,7 @@ export class KeyregTxBuilder implements IKeyregTxBuilder { this.tx.snd = this.encoder.decodeAddress(sender) return this } - addFee(fee: number): IKeyregTxBuilder { + addFee(fee: bigint): IKeyregTxBuilder { this.tx.fee = fee return this } diff --git a/lib/algorand.transaction.pay.ts b/lib/algorand.transaction.pay.ts index b751f54..366dea0 100644 --- a/lib/algorand.transaction.pay.ts +++ b/lib/algorand.transaction.pay.ts @@ -21,7 +21,7 @@ export class PayTransaction extends TransactionHeader { * * The total amount to be sent in microAlgos. */ - amt: number + amt: bigint /** * Close Remainder To @@ -54,7 +54,7 @@ export interface IPayTxBuilder extends ITransactionHeaderBuilder{ * * @param amount The total amount to be sent in microAlgos. */ - addAmount(amount: number): IPayTxBuilder + addAmount(amount: bigint): IPayTxBuilder /** * Add Close Remainder To * @@ -77,13 +77,13 @@ export class PayTxBuilder implements IPayTxBuilder { this.tx.gen = genesisId this.tx.gh = new Uint8Array(Buffer.from(genesisHash, "base64")) this.tx.type = "pay" - this.tx.fee = 1000 + this.tx.fee = 1000n } addReceiver(receiver: string): IPayTxBuilder { this.tx.rcv = this.encoder.decodeAddress(receiver) return this } - addAmount(amount: number): IPayTxBuilder { + addAmount(amount: bigint): IPayTxBuilder { this.tx.amt = amount return this } @@ -95,7 +95,7 @@ export class PayTxBuilder implements IPayTxBuilder { this.tx.snd = this.encoder.decodeAddress(sender) return this } - addFee(fee: number): IPayTxBuilder { + addFee(fee: bigint): IPayTxBuilder { this.tx.fee = fee return this } diff --git a/lib/schemas/pay.transaction.json b/lib/schemas/pay.transaction.json index 2043cd1..19d762f 100644 --- a/lib/schemas/pay.transaction.json +++ b/lib/schemas/pay.transaction.json @@ -12,7 +12,7 @@ "const": "pay" }, "amt": { - "type": "integer", + "typeof": "bigint", "description": "The total amount to be sent in microAlgos." }, "rcv": { diff --git a/lib/schemas/transaction.header.json b/lib/schemas/transaction.header.json index 19c4335..1c8860d 100644 --- a/lib/schemas/transaction.header.json +++ b/lib/schemas/transaction.header.json @@ -5,7 +5,7 @@ "type": "object", "properties": { "fee": { - "type": "integer", + "typeof": "bigint", "description": "Paid by the sender to the FeeSink to prevent denial-of-service. The minimum fee on Algorand is currently 1000 microAlgos." }, "fv": { diff --git a/package.json b/package.json index 56d86e7..8e10303 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ }, "scripts": { "build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json", - "test": "jest lib --verbose", - "test:cov": "jest lib --coverage", + "test": "jest lib --verbose --maxWorkers=1", + "test:cov": "jest lib --coverage --maxWorkers=1", "typedoc": "typedoc" }, "author": "", @@ -23,6 +23,8 @@ "devDependencies": { "@types/jest": "^29.5.5", "@types/node": "^20.7.1", + "algosdk": "^3.1.0", + "jest": "^29.7.0", "ts-jest": "^29.1.1", "tweetnacl": "^1.0.3", "typedoc": "^0.26.7", @@ -30,6 +32,8 @@ }, "dependencies": { "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "ajv-keywords": "^5.1.0", "algo-msgpack-with-bigint": "^2.1.1", "hi-base32": "^0.5.1", "js-sha512": "^0.8.0" @@ -61,7 +65,7 @@ ], "testEnvironment": "node", "moduleNameMapper": { - "(.+)\\.js": "$1" + "^((?!bignumber).+)\\.js$": "$1" }, "extensionsToTreatAsEsm": [ ".ts" From 5780d5984862a6f21a29b989d2354427d9e72329 Mon Sep 17 00:00:00 2001 From: ehanoc Date: Mon, 10 Feb 2025 19:25:16 +0000 Subject: [PATCH 2/6] Unnessary algosdk Signed-off-by: ehanoc --- lib/algorand.encoder.spec.ts | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/algorand.encoder.spec.ts b/lib/algorand.encoder.spec.ts index 6745e59..3ab41b8 100644 --- a/lib/algorand.encoder.spec.ts +++ b/lib/algorand.encoder.spec.ts @@ -10,7 +10,6 @@ import {AssetParamsBuilder} from "./algorand.asset.params"; import {AssetConfigTransaction} from "./algorand.transaction.acfg"; import {AssetFreezeTransaction} from "./algorand.transaction.afrz"; import {AssetTransferTransaction} from "./algorand.transaction.axfer"; -import algosdk from 'algosdk' export function concatArrays(...arrs: ArrayLike[]) { const size = arrs.reduce((sum, arr) => sum + arr.length, 0) diff --git a/package.json b/package.json index 8e10303..5c83b32 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "devDependencies": { "@types/jest": "^29.5.5", "@types/node": "^20.7.1", - "algosdk": "^3.1.0", "jest": "^29.7.0", "ts-jest": "^29.1.1", "tweetnacl": "^1.0.3", From 7b7b2eaba47b7f776538b9a33d59b2a83655e705 Mon Sep 17 00:00:00 2001 From: ehanoc Date: Mon, 10 Feb 2025 19:34:07 +0000 Subject: [PATCH 3/6] Update CJS target to ES2020 Signed-off-by: ehanoc --- tsconfig.cjs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index c0554c9..2c582e0 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -2,7 +2,7 @@ "compilerOptions": { "outDir": "./dist/cjs", "module": "commonjs", - "target": "es5", + "target": "ES2020", "declaration": false }, "extends": "./tsconfig.json" From 542aa4072f7a0dcadf886cb2b8e2abb98dfdde15 Mon Sep 17 00:00:00 2001 From: ehanoc Date: Mon, 10 Feb 2025 20:34:20 +0000 Subject: [PATCH 4/6] Update all models to use bigint, update schemas, cast to safe range of integers Signed-off-by: ehanoc --- lib/algorand.asset.params.ts | 16 +++++----- lib/algorand.encoder.spec.ts | 12 ++++---- lib/algorand.encoder.ts | 14 +++++++++ lib/algorand.transaction.acfg.ts | 18 +++++------ lib/algorand.transaction.afrz.ts | 20 ++++++------ lib/algorand.transaction.axfer.ts | 18 +++++------ lib/algorand.transaction.crafter.spec.ts | 22 ++++++------- lib/algorand.transaction.crafter.ts | 8 ++--- lib/algorand.transaction.header.ts | 10 +++--- lib/algorand.transaction.keyreg.ts | 36 +++++++++++----------- lib/algorand.transaction.pay.ts | 18 +++++------ lib/schemas/acfg.transaction.json | 2 +- lib/schemas/afrz.transaction.json | 2 +- lib/schemas/asset.params.json | 4 +-- lib/schemas/axfer.transaction.json | 4 +-- lib/schemas/keyreg.transaction.online.json | 6 ++-- lib/schemas/transaction.header.json | 6 ++-- 17 files changed, 114 insertions(+), 102 deletions(-) diff --git a/lib/algorand.asset.params.ts b/lib/algorand.asset.params.ts index 184d1ff..2f0c76a 100644 --- a/lib/algorand.asset.params.ts +++ b/lib/algorand.asset.params.ts @@ -9,14 +9,14 @@ export class AssetParams { * * The total number of base units of the asset to create. This number cannot be changed. */ - t?: number | bigint | undefined + t?: bigint | undefined /** * Decimals * * The number of digits to use after the decimal point when displaying the asset. If 0, the asset is not divisible. If 1, the base unit of the asset is in tenths. * If 2, the base unit of the asset is in hundredths, if 3, the base unit of the asset is in thousandths, and so on up to 19 decimal places */ - dc?: number | bigint + dc?: bigint /** * Default Frozen * @@ -79,8 +79,8 @@ export class AssetParams { * @internal */ export interface IAssetParamsBuilder { - addTotal(total: number): IAssetParamsBuilder - addDecimals(decimals: number): IAssetParamsBuilder + addTotal(total: number | bigint): IAssetParamsBuilder + addDecimals(decimals: number | bigint | bigint): IAssetParamsBuilder addDefaultFrozen(frozen: boolean): IAssetParamsBuilder addUnitName(unitName: string): IAssetParamsBuilder addAssetName(assetName: string): IAssetParamsBuilder @@ -104,12 +104,12 @@ export class AssetParamsBuilder implements IAssetParamsBuilder { this.encoder = new AlgorandEncoder() } - addTotal(total: number): IAssetParamsBuilder { - this.params.t = total + addTotal(total: number | bigint): IAssetParamsBuilder { + this.params.t = AlgorandEncoder.safeCastBigInt(total) return this } - addDecimals(decimals: number): IAssetParamsBuilder { - this.params.dc = decimals + addDecimals(decimals: number | bigint): IAssetParamsBuilder { + this.params.dc = AlgorandEncoder.safeCastBigInt(decimals) return this } addDefaultFrozen(frozen: boolean): IAssetParamsBuilder { diff --git a/lib/algorand.encoder.spec.ts b/lib/algorand.encoder.spec.ts index 3ab41b8..83bf4b0 100644 --- a/lib/algorand.encoder.spec.ts +++ b/lib/algorand.encoder.spec.ts @@ -42,7 +42,7 @@ describe("Algorand Encoding", () => { // to algorand address const to: string = algoEncoder.encodeAddress(Buffer.from(randomBytes(32))) - const encodedTransaction: Uint8Array = algorandCrafter.pay(1000n, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() + const encodedTransaction: Uint8Array = algorandCrafter.pay(1000, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() const signature: Uint8Array = new Uint8Array(Buffer.from(randomBytes(64))) const signedTransaction: Uint8Array = algorandCrafter.addSignature(encodedTransaction, signature) @@ -105,7 +105,7 @@ describe("Algorand Encoding", () => { const note: string = Buffer.from(randomBytes(32)).toString("base64") // create pay transaction - const txn: PayTransaction = algorandCrafter.pay(1000n, from, to).addFirstValidRound(1000).addLastValidRound(2000).addNote(note).addFee(1000n).get() + const txn: PayTransaction = algorandCrafter.pay(1000, from, to).addFirstValidRound(1000).addLastValidRound(2000).addNote(note).addFee(1000).get() const encoded: Uint8Array = txn.encode() expect(encoded).toEqual(algoEncoder.encodeTransaction(txn)) @@ -133,7 +133,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note) - .addFee(1000n) + .addFee(1000) .get() const encoded: Uint8Array = txn.encode() @@ -166,7 +166,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note, "base64") - .addFee(1000n) + .addFee(1000) .addGroup(grp) .addRekey(from) .addLease(lx) @@ -191,7 +191,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note, "base64") - .addFee(1000n) + .addFee(1000) .addGroup(grp) .addRekey(from) .addLease(lx) @@ -218,7 +218,7 @@ describe("Algorand Encoding", () => { .addFirstValidRound(1000) .addLastValidRound(2000) .addNote(note, "base64") - .addFee(1000n) + .addFee(1000) .addGroup(grp) .addRekey(from) .addLease(lx) diff --git a/lib/algorand.encoder.ts b/lib/algorand.encoder.ts index 7d51d17..b409977 100644 --- a/lib/algorand.encoder.ts +++ b/lib/algorand.encoder.ts @@ -110,4 +110,18 @@ export class AlgorandEncoder extends Encoder{ const decoded: object = msgpack.decode(encoded) as object return decoded as object } + + /** + * Casts a number or bigint to BigInt and checks if it's within the safe integer range. + * @param value - The number or bigint to be casted. + * @returns The value as a BigInt. + * @throws Error if the value is not within the safe integer range. + */ + static safeCastBigInt(value: number | bigint): bigint { + const bigIntValue = BigInt(value) + if (bigIntValue < Number.MIN_SAFE_INTEGER || bigIntValue > Number.MAX_SAFE_INTEGER) { + throw new Error("Value is not within the safe integer range") + } + return bigIntValue + } } diff --git a/lib/algorand.transaction.acfg.ts b/lib/algorand.transaction.acfg.ts index dd17007..de1fa3f 100644 --- a/lib/algorand.transaction.acfg.ts +++ b/lib/algorand.transaction.acfg.ts @@ -15,9 +15,9 @@ export class AssetConfigTransaction extends TransactionHeader { declare type: "acfg" /** * For re-configure or destroy transactions, this is the unique asset ID. On asset creation, the ID is set to zero. - * @type {number | bigint} + * @type {bigint} */ - caid?: number | bigint + caid?: bigint /** * See {@link AssetParams} for all available fields. * @type {AssetParams} @@ -81,7 +81,7 @@ export class AssetConfigTxBuilder implements IAssetConfigTxBuilder { this.tx.fee = 1000n } addAssetId(caid: number | bigint): IAssetConfigTxBuilder { - this.tx.caid = caid + this.tx.caid = AlgorandEncoder.safeCastBigInt(caid) return this } addAssetParams(params: AssetParams): IAssetConfigTxBuilder{ @@ -92,16 +92,16 @@ export class AssetConfigTxBuilder implements IAssetConfigTxBuilder { this.tx.snd = this.encoder.decodeAddress(sender) return this } - addFee(fee: bigint): IAssetConfigTxBuilder { - this.tx.fee = fee + addFee(fee: number | bigint): IAssetConfigTxBuilder { + this.tx.fee = AlgorandEncoder.safeCastBigInt(fee) return this } - addFirstValidRound(fv: number): IAssetConfigTxBuilder { - this.tx.fv = fv + addFirstValidRound(fv: number | bigint): IAssetConfigTxBuilder { + this.tx.fv = AlgorandEncoder.safeCastBigInt(fv) return this } - addLastValidRound(lv: number): IAssetConfigTxBuilder { - this.tx.lv = lv + addLastValidRound(lv: number | bigint): IAssetConfigTxBuilder { + this.tx.lv = AlgorandEncoder.safeCastBigInt(lv) return this } addNote(note: string, encoding: BufferEncoding = "utf8"): IAssetConfigTxBuilder { diff --git a/lib/algorand.transaction.afrz.ts b/lib/algorand.transaction.afrz.ts index 7177277..bde3421 100644 --- a/lib/algorand.transaction.afrz.ts +++ b/lib/algorand.transaction.afrz.ts @@ -19,7 +19,7 @@ export class AssetFreezeTransaction extends TransactionHeader { /** * The asset ID being frozen or unfrozen. */ - faid: number | bigint + faid: bigint /** * True to freeze the asset. */ @@ -48,7 +48,7 @@ export interface IAssetFreezeTxBuilder extends ITransactionHeaderBuilder { gen: genesisId, gh: new Uint8Array(Buffer.from(genesisHash, "base64")), fee: 1000n, - fv: 1000, - lv: 2000, + fv: 1000n, + lv: 2000n, rekey: sender } }) @@ -97,7 +97,7 @@ describe("Algorand Transaction Crafter", () => { // to algorand address const to: string = algoEncoder.encodeAddress(Buffer.from(randomBytes(32))) - const encodedTransaction: Uint8Array = algorandCrafter.pay(1000n, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() + const encodedTransaction: Uint8Array = algorandCrafter.pay(1000, from, to).addFirstValidRound(1000).addLastValidRound(2000).get().encode() const signature: Uint8Array = Buffer.from(randomBytes(64)) const result: Uint8Array = algorandCrafter.addSignature(encodedTransaction, signature) expect(result).toBeDefined() @@ -122,7 +122,7 @@ describe("Algorand Transaction Crafter", () => { // create pay transaction const txn: PayTransaction = withTestTransactionHeader( algorandCrafter - .pay(1000n, from, to) + .pay(1000, from, to) .addCloseTo(from) ).get() @@ -173,9 +173,9 @@ describe("Algorand Transaction Crafter", () => { votekey: new Uint8Array(Buffer.from(voteKey, "base64")), selkey: new Uint8Array(Buffer.from(selectionKey, "base64")), sprfkey: new Uint8Array(Buffer.from(stateProofKey, "base64")), - votefst: 1000, - votelst: 2000, - votekd: 32, + votefst: 1000n, + votelst: 2000n, + votekd: 32n, type: "keyreg", ...transactionHeader, }) @@ -296,7 +296,7 @@ describe("Algorand Transaction Crafter", () => { expect(txn).toEqual({ type: "acfg", apar: undefined, - caid: 1, + caid: 1n, ...transactionHeader, }) @@ -325,7 +325,7 @@ describe("Algorand Transaction Crafter", () => { expect(txn).toEqual({ type: "afrz", fadd: algoEncoder.decodeAddress(from), - faid: 1, + faid: 1n, afrz: true, ...transactionHeader, }) @@ -356,8 +356,8 @@ describe("Algorand Transaction Crafter", () => { expect(txn).toBeInstanceOf(AssetTransferTransaction) expect(txn).toEqual({ type: "axfer", - xaid: 1, - aamt: 1, + xaid: 1n, + aamt: 1n, arcv: algoEncoder.decodeAddress(from), aclose: algoEncoder.decodeAddress(from), asnd: algoEncoder.decodeAddress(from), diff --git a/lib/algorand.transaction.crafter.ts b/lib/algorand.transaction.crafter.ts index 007de40..c7d5140 100644 --- a/lib/algorand.transaction.crafter.ts +++ b/lib/algorand.transaction.crafter.ts @@ -22,7 +22,7 @@ export class AlgorandTransactionCrafter extends Crafter { * @param from The address of the account that pays the fee and amount. * @param to The address of the account that receives the amount. */ - pay(amount: bigint, from: string, to: string): IPayTxBuilder { + pay(amount: number | bigint, from: string, to: string): IPayTxBuilder { return new PayTxBuilder(this.genesisId, this.genesisHash).addAmount(amount).addSender(from).addReceiver(to) } /** @@ -36,7 +36,7 @@ export class AlgorandTransactionCrafter extends Crafter { * @param voteLast The last round that the participation key is valid. * @param voteKeyDilution This is the dilution for the 2-level participation key. */ - changeOnline(from: string, voteKey: string, selectionKey: string, stateProofKey: string, voteFirst: number, voteLast: number, voteKeyDilution: number): IKeyregTxBuilder { + changeOnline(from: string, voteKey: string, selectionKey: string, stateProofKey: string, voteFirst: number | bigint, voteLast: number | bigint, voteKeyDilution: number | bigint): IKeyregTxBuilder { return new KeyregTxBuilder(this.genesisId, this.genesisHash) .addSender(from) .addVoteKey(voteKey) @@ -94,7 +94,7 @@ export class AlgorandTransactionCrafter extends Crafter { * @param faid The asset ID being frozen or unfrozen. * @param afrz True to freeze the asset. */ - freezeAsset(fadd: string, faid: number, afrz: boolean): IAssetFreezeTxBuilder { + freezeAsset(fadd: string, faid: number | bigint, afrz: boolean): IAssetFreezeTxBuilder { return new AssetFreezeTxBuilder(this.genesisId, this.genesisHash) .addFreezeAccount(fadd) .addFreezeAsset(faid) @@ -108,7 +108,7 @@ export class AlgorandTransactionCrafter extends Crafter { * @param arcv The recipient of the asset transfer. * @param aamt The amount of the asset to be transferred. */ - transferAsset(from: string, xaid: number, arcv: string, aamt: number | bigint): IAssetTransferTxBuilder { + transferAsset(from: string, xaid: number | bigint, arcv: string, aamt: number | bigint): IAssetTransferTxBuilder { return new AssetTransferTxBuilder(this.genesisId, this.genesisHash) .addSender(from) .addAssetId(xaid) diff --git a/lib/algorand.transaction.header.ts b/lib/algorand.transaction.header.ts index c21a875..f9103ab 100644 --- a/lib/algorand.transaction.header.ts +++ b/lib/algorand.transaction.header.ts @@ -36,13 +36,13 @@ export abstract class TransactionHeader { * * The first round for when the transaction is valid. If the transaction is sent prior to this round it will be rejected by the network. */ - fv: number + fv: bigint /** * Last Valid * * The ending round for which the transaction is valid. After this round, the transaction will be rejected by the network. */ - lv: number + lv: bigint /** * Genesis Hash * @@ -108,21 +108,21 @@ export interface ITransactionHeaderBuilder { * * @param fee Paid by the sender to the FeeSink to prevent denial-of-service. The minimum fee on Algorand is currently 1000 microAlgos. */ - addFee(fee: bigint): T + addFee(fee: number | bigint): T /** * Add First Valid Round * * @param fv The first round for when the transaction is valid. If the transaction is sent prior to this round it will be rejected by the network. */ - addFirstValidRound(fv: number): T + addFirstValidRound(fv: number | bigint): T /** * Add Last Valid Round * * @param lv The ending round for which the transaction is valid. After this round, the transaction will be rejected by the network. */ - addLastValidRound(lv: number): T + addLastValidRound(lv: number | bigint): T /** * Add Note diff --git a/lib/algorand.transaction.keyreg.ts b/lib/algorand.transaction.keyreg.ts index 57267cb..c06750d 100644 --- a/lib/algorand.transaction.keyreg.ts +++ b/lib/algorand.transaction.keyreg.ts @@ -33,21 +33,21 @@ export class KeyregTransaction extends TransactionHeader { * * The first round that the participation key is valid. Not to be confused with the FirstValid round of the keyreg transaction. */ - votefst?: number + votefst?: bigint /** * Vote Last * * The last round that the participation key is valid. * Not to be confused with the LastValid round of the keyreg transaction. */ - votelst?: number + votelst?: bigint /** * Vote Key Dilution * * This is the dilution for the 2-level participation key. * It determines the interval (number of rounds) for generating new ephemeral keys. */ - votekd?: number + votekd?: bigint /** * Nonparticipating * @@ -95,19 +95,19 @@ export interface IKeyregTxBuilder extends ITransactionHeaderBuilder{ * * @param amount The total amount to be sent in microAlgos. */ - addAmount(amount: bigint): IPayTxBuilder + addAmount(amount: number | bigint): IPayTxBuilder /** * Add Close Remainder To * @@ -83,8 +83,8 @@ export class PayTxBuilder implements IPayTxBuilder { this.tx.rcv = this.encoder.decodeAddress(receiver) return this } - addAmount(amount: bigint): IPayTxBuilder { - this.tx.amt = amount + addAmount(amount: number | bigint): IPayTxBuilder { + this.tx.amt = AlgorandEncoder.safeCastBigInt(amount) return this } addCloseTo(close: string): IPayTxBuilder { @@ -95,16 +95,16 @@ export class PayTxBuilder implements IPayTxBuilder { this.tx.snd = this.encoder.decodeAddress(sender) return this } - addFee(fee: bigint): IPayTxBuilder { - this.tx.fee = fee + addFee(fee: number | bigint): IPayTxBuilder { + this.tx.fee = AlgorandEncoder.safeCastBigInt(fee) return this } - addFirstValidRound(fv: number): IPayTxBuilder { - this.tx.fv = fv + addFirstValidRound(fv: number | bigint): IPayTxBuilder { + this.tx.fv = AlgorandEncoder.safeCastBigInt(fv) return this } - addLastValidRound(lv: number): IPayTxBuilder { - this.tx.lv = lv + addLastValidRound(lv: number | bigint): IPayTxBuilder { + this.tx.lv = AlgorandEncoder.safeCastBigInt(lv) return this } addNote(note: string, encoding: BufferEncoding = "utf8"): IPayTxBuilder { diff --git a/lib/schemas/acfg.transaction.json b/lib/schemas/acfg.transaction.json index 09a552e..6dcb6f6 100644 --- a/lib/schemas/acfg.transaction.json +++ b/lib/schemas/acfg.transaction.json @@ -12,7 +12,7 @@ "const": "acfg" }, "caid": { - "type": "integer", + "typeof": "bigint", "description": "For re-configure or destroy transactions, this is the unique asset ID. On asset creation, the ID is set to zero." }, "apar": { diff --git a/lib/schemas/afrz.transaction.json b/lib/schemas/afrz.transaction.json index 3d733e4..f067f44 100644 --- a/lib/schemas/afrz.transaction.json +++ b/lib/schemas/afrz.transaction.json @@ -16,7 +16,7 @@ "description": "The address of the account whose asset is being frozen or unfrozen." }, "faid": { - "type": "integer", + "typeof": "bigint", "description": "The asset ID being frozen or unfrozen." }, "afrz": { diff --git a/lib/schemas/asset.params.json b/lib/schemas/asset.params.json index b5a3751..4be51e3 100644 --- a/lib/schemas/asset.params.json +++ b/lib/schemas/asset.params.json @@ -5,11 +5,11 @@ "type": "object", "properties": { "t": { - "type": "integer", + "typeof": "bigint", "description": "The total number of base units of the asset to create. This number cannot be changed." }, "dc": { - "type": "integer", + "typeof": "bigint", "description": "The number of digits to use after the decimal point when displaying the asset. If 0, the asset is not divisible. If 1, the base unit of the asset is in tenths. If 2, the base unit of the asset is in hundredths, if 3, the base unit of the asset is in thousandths, and so on up to 19 decimal places" }, "df": { diff --git a/lib/schemas/axfer.transaction.json b/lib/schemas/axfer.transaction.json index 6f98588..db96215 100644 --- a/lib/schemas/axfer.transaction.json +++ b/lib/schemas/axfer.transaction.json @@ -12,11 +12,11 @@ "const": "axfer" }, "xaid": { - "type": "integer", + "typeof": "bigint", "description": "The unique ID of the asset to be transferred." }, "aamt": { - "type": "integer", + "typeof": "bigint", "description": "The amount of the asset to be transferred. A zero amount transferred to self allocates that asset in the account's Asset map." }, "asnd": { diff --git a/lib/schemas/keyreg.transaction.online.json b/lib/schemas/keyreg.transaction.online.json index 4a98c46..4c73e7d 100644 --- a/lib/schemas/keyreg.transaction.online.json +++ b/lib/schemas/keyreg.transaction.online.json @@ -29,15 +29,15 @@ "description": "The 64 byte state proof public key commitment." }, "votefst": { - "type": "integer", + "typeof": "bigint", "description": "The first round that the participation key is valid. Not to be confused with the FirstValid round of the keyreg transaction." }, "votelst": { - "type": "integer", + "typeof": "bigint", "description": "The last round that the participation key is valid. Not to be confused with the LastValid round of the keyreg transaction." }, "votekd": { - "type": "integer", + "typeof": "bigint", "description": "This is the dilution for the 2-level participation key. It determines the interval (number of rounds) for generating new ephemeral keys." } }, diff --git a/lib/schemas/transaction.header.json b/lib/schemas/transaction.header.json index 1c8860d..e9f6922 100644 --- a/lib/schemas/transaction.header.json +++ b/lib/schemas/transaction.header.json @@ -9,8 +9,7 @@ "description": "Paid by the sender to the FeeSink to prevent denial-of-service. The minimum fee on Algorand is currently 1000 microAlgos." }, "fv": { - "type": "integer", - "minimum": 0, + "typeof": "bigint", "description": "The first round for when the transaction is valid. If the transaction is sent prior to this round it will be rejected by the network." }, "gh": { @@ -18,8 +17,7 @@ "description": "The hash of the genesis block of the network for which the transaction is valid. See the genesis hash for MainNet, TestNet, and BetaNet." }, "lv": { - "type": "integer", - "minimum": 0, + "typeof": "bigint", "description": "The ending round for which the transaction is valid. After this round, the transaction will be rejected by the network." }, "snd": { From e3e17e2f6840f2e3f59ea417513d9e9171eee9a9 Mon Sep 17 00:00:00 2001 From: ehanoc Date: Mon, 10 Feb 2025 20:35:39 +0000 Subject: [PATCH 5/6] Fix repeating type param Signed-off-by: ehanoc --- lib/algorand.asset.params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/algorand.asset.params.ts b/lib/algorand.asset.params.ts index 2f0c76a..d6f4200 100644 --- a/lib/algorand.asset.params.ts +++ b/lib/algorand.asset.params.ts @@ -80,7 +80,7 @@ export class AssetParams { */ export interface IAssetParamsBuilder { addTotal(total: number | bigint): IAssetParamsBuilder - addDecimals(decimals: number | bigint | bigint): IAssetParamsBuilder + addDecimals(decimals: number | bigint): IAssetParamsBuilder addDefaultFrozen(frozen: boolean): IAssetParamsBuilder addUnitName(unitName: string): IAssetParamsBuilder addAssetName(assetName: string): IAssetParamsBuilder From ab048d41d1b57ff34012308eb8d0864c13c344eb Mon Sep 17 00:00:00 2001 From: ehanoc Date: Mon, 10 Feb 2025 20:52:56 +0000 Subject: [PATCH 6/6] Update lib/algorand.encoder.ts Co-authored-by: Michael J Feher --- lib/algorand.encoder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/algorand.encoder.ts b/lib/algorand.encoder.ts index b409977..89cc26a 100644 --- a/lib/algorand.encoder.ts +++ b/lib/algorand.encoder.ts @@ -119,7 +119,7 @@ export class AlgorandEncoder extends Encoder{ */ static safeCastBigInt(value: number | bigint): bigint { const bigIntValue = BigInt(value) - if (bigIntValue < Number.MIN_SAFE_INTEGER || bigIntValue > Number.MAX_SAFE_INTEGER) { +if (typeof value === "number" && (value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER)) { throw new Error("Value is not within the safe integer range") } return bigIntValue