From 476902cebe25eeb670b3c77a2c1cc7b4cfe37898 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 25 Jan 2024 10:48:28 -0500 Subject: [PATCH 01/17] Refactor transaction class --- examples/app.ts | 16 +- examples/asa.ts | 2 +- examples/participation.ts | 17 +- src/bid.ts | 93 - src/boxStorage.ts | 37 +- src/client/kmd.ts | 18 +- src/client/v2/algod/algod.ts | 4 +- src/client/v2/algod/getApplicationByID.ts | 2 +- src/client/v2/algod/getAssetByID.ts | 2 +- src/client/v2/algod/suggestedParams.ts | 24 +- src/composer.ts | 2 +- src/dryrun.ts | 42 +- src/group.ts | 90 +- src/logicsig.ts | 11 +- src/main.ts | 33 +- src/makeTxn.ts | 1932 ++++++----------- src/multisig.ts | 202 +- src/transaction.ts | 1908 +++++++---------- src/types/transactions/application.ts | 111 - src/types/transactions/asset.ts | 108 - src/types/transactions/base.ts | 329 +-- src/types/transactions/builder.ts | 68 - src/types/transactions/encoded.ts | 48 +- src/types/transactions/index.ts | 72 +- src/types/transactions/keyreg.ts | 23 - src/types/transactions/payment.ts | 14 - src/types/transactions/stateproof.ts | 17 - src/utils/utils.ts | 54 + tests/10.ABI.ts | 38 +- tests/3.Address.ts | 9 + tests/5.Transaction.ts | 2369 ++++++++++++--------- tests/6.Multisig.ts | 240 +-- tests/7.AlgoSDK.ts | 542 +++-- tests/8.LogicSig.ts | 45 +- tests/cucumber/integration.tags | 1 - tests/cucumber/steps/steps.js | 690 +++--- 36 files changed, 3868 insertions(+), 5345 deletions(-) delete mode 100644 src/bid.ts delete mode 100644 src/types/transactions/application.ts delete mode 100644 src/types/transactions/asset.ts delete mode 100644 src/types/transactions/builder.ts delete mode 100644 src/types/transactions/keyreg.ts delete mode 100644 src/types/transactions/payment.ts delete mode 100644 src/types/transactions/stateproof.ts diff --git a/examples/app.ts b/examples/app.ts index 14999f0ef..3db7fe3de 100644 --- a/examples/app.ts +++ b/examples/app.ts @@ -79,7 +79,7 @@ async function main() { // example: APP_OPTIN const appOptInTxn = algosdk.makeApplicationOptInTxnFromObject({ sender: caller.addr, - appIndex: appId, + appId, suggestedParams, }); @@ -92,7 +92,7 @@ async function main() { // example: APP_NOOP const appNoOpTxn = algosdk.makeApplicationNoOpTxnFromObject({ sender: caller.addr, - appIndex: appId, + appId, suggestedParams, }); @@ -106,7 +106,7 @@ async function main() { const anotherAppOptInTxn = algosdk.makeApplicationOptInTxnFromObject({ sender: anotherCaller.addr, - appIndex: appId, + appId, suggestedParams, }); @@ -120,7 +120,7 @@ async function main() { const simpleAddTxn = algosdk.makeApplicationNoOpTxnFromObject({ sender: caller.addr, suggestedParams, - appIndex: appId, + appId, appArgs: [new TextEncoder().encode(now)], }); @@ -160,7 +160,7 @@ async function main() { // example: APP_CLOSEOUT const appCloseOutTxn = algosdk.makeApplicationCloseOutTxnFromObject({ sender: caller.addr, - appIndex: appId, + appId, suggestedParams, }); @@ -180,7 +180,7 @@ async function main() { const appUpdateTxn = algosdk.makeApplicationUpdateTxnFromObject({ sender: creator.addr, suggestedParams, - appIndex: appId, + appId, // updates must define both approval and clear programs, even if unchanged approvalProgram: new Uint8Array(compiledNewProgram), clearProgram: new Uint8Array(compiledClearProgram), @@ -196,7 +196,7 @@ async function main() { const appClearTxn = algosdk.makeApplicationClearStateTxnFromObject({ sender: anotherCaller.addr, suggestedParams, - appIndex: appId, + appId, }); await algodClient @@ -209,7 +209,7 @@ async function main() { const appDeleteTxn = algosdk.makeApplicationDeleteTxnFromObject({ sender: creator.addr, suggestedParams, - appIndex: appId, + appId, }); await algodClient diff --git a/examples/asa.ts b/examples/asa.ts index 93855ac45..9eaa64966 100644 --- a/examples/asa.ts +++ b/examples/asa.ts @@ -65,7 +65,7 @@ async function main() { suggestedParams, assetIndex, // don't throw error if freeze, clawback, or manager are empty - strictEmptyAddressChecking: false, + allowRoleRemoval: true, }); const signedConfigTxn = configTxn.signTxn(creator.privateKey); diff --git a/examples/participation.ts b/examples/participation.ts index c0ad4466b..6bf20143e 100644 --- a/examples/participation.ts +++ b/examples/participation.ts @@ -11,18 +11,23 @@ async function main() { // Parent addr const addr = 'MWAPNXBDFFD2V5KWXAHWKBO7FO4JN36VR4CIBDKDDE7WAUAGZIXM3QPJW4'; // VRF public key - const selectionKey = 'LrpLhvzr+QpN/bivh6IPpOaKGbGzTTB5lJtVfixmmgk='; + const selectionKey = algosdk.base64ToBytes( + 'LrpLhvzr+QpN/bivh6IPpOaKGbGzTTB5lJtVfixmmgk=' + ); // Voting pub key - const voteKey = 'G/lqTV6MKspW6J8wH2d8ZliZ5XZVZsruqSBJMwLwlmo='; + const voteKey = algosdk.base64ToBytes( + 'G/lqTV6MKspW6J8wH2d8ZliZ5XZVZsruqSBJMwLwlmo=' + ); // State proof key - const stateProofKey = - 'RpUpNWfZMjZ1zOOjv3MF2tjO714jsBt0GKnNsw0ihJ4HSZwci+d9zvUi3i67LwFUJgjQ5Dz4zZgHgGduElnmSA=='; + const stateProofKey = algosdk.base64ToBytes( + 'RpUpNWfZMjZ1zOOjv3MF2tjO714jsBt0GKnNsw0ihJ4HSZwci+d9zvUi3i67LwFUJgjQ5Dz4zZgHgGduElnmSA==' + ); // sets up keys for 100000 rounds - const numRounds = 1e5; + const numRounds = BigInt(100000); // dilution default is sqrt num rounds - const keyDilution = numRounds ** 0.5; + const keyDilution = BigInt(Math.floor(Math.sqrt(Number(numRounds)))); // create transaction const onlineKeyreg = diff --git a/src/bid.ts b/src/bid.ts deleted file mode 100644 index 82c2c6ac7..000000000 --- a/src/bid.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as address from './encoding/address.js'; -import * as encoding from './encoding/encoding.js'; -import * as nacl from './nacl/naclWrappers.js'; -import * as utils from './utils/utils.js'; -import { Address } from './types/address.js'; - -interface BidStorageStructure { - bidderKey: Address; - bidAmount: number; - bidID: number; - auctionKey: Address; - auctionID: number; - maxPrice: number; -} - -export type BidOptions = Omit< - BidStorageStructure, - 'bidderKey' | 'auctionKey' -> & { - bidderKey: string; - auctionKey: string; -}; - -/** - * Bid enables construction of Algorand Auctions Bids - * */ -export default class Bid implements BidStorageStructure { - name = 'Bid'; - tag = Uint8Array.from([97, 66]); // "aB" - - bidderKey: Address; - bidAmount: number; - bidID: number; - auctionKey: Address; - auctionID: number; - maxPrice: number; - - constructor({ - bidderKey, - bidAmount, - bidID, - auctionKey, - auctionID, - maxPrice, - }: BidOptions) { - const decodedBidderKey = address.decodeAddress(bidderKey); - const decodedAuctionKey = address.decodeAddress(auctionKey); - - if (!Number.isSafeInteger(bidAmount) || bidAmount < 0) - throw Error('Bid amount must be positive and 2^53-1'); - if (!Number.isSafeInteger(bidID) || bidID < 0) - throw Error('BidID must be positive and 2^53-1'); - if (!Number.isSafeInteger(auctionID) || auctionID < 0) - throw Error('auctionID must be positive'); - - this.bidderKey = decodedBidderKey; - this.bidAmount = bidAmount; - this.bidID = bidID; - this.auctionKey = decodedAuctionKey; - this.auctionID = auctionID; - this.maxPrice = maxPrice; - } - - // eslint-disable-next-line camelcase - get_obj_for_encoding() { - return { - bidder: this.bidderKey.publicKey, - cur: this.bidAmount, - price: this.maxPrice, - id: this.bidID, - auc: this.auctionKey.publicKey, - aid: this.auctionID, - }; - } - - signBid(sk: Uint8Array) { - const encodedMsg = encoding.encode(this.get_obj_for_encoding()); - const toBeSigned = utils.concatArrays(this.tag, encodedMsg); - const sig = nacl.sign(toBeSigned, sk); - - // construct signed message - const sBid = { - sig, - bid: this.get_obj_for_encoding(), - }; - - const note = { - t: 'b', - b: sBid, - }; - return new Uint8Array(encoding.encode(note)); - } -} diff --git a/src/boxStorage.ts b/src/boxStorage.ts index 6961286b5..14f65c49c 100644 --- a/src/boxStorage.ts +++ b/src/boxStorage.ts @@ -3,18 +3,16 @@ import { BoxReference } from './types/transactions/base.js'; function translateBoxReference( reference: BoxReference, - foreignApps: number[], - appIndex: number + foreignApps: bigint[], + appIndex: bigint ): EncodedBoxReference { - const referenceId = reference.appIndex; + const referenceId = BigInt(reference.appIndex); const referenceName = reference.name; - const isOwnReference = referenceId === 0 || referenceId === appIndex; - let index = 0; + const isOwnReference = referenceId === BigInt(0) || referenceId === appIndex; + + // Foreign apps start from index 1; index 0 is its own app ID. + const index = foreignApps.indexOf(referenceId) + 1; - if (foreignApps != null) { - // Foreign apps start from index 1; index 0 is its own app ID. - index = foreignApps.indexOf(referenceId) + 1; - } // Check if the app referenced is itself after checking the foreign apps array. // If index is zero, then the app ID was not found in the foreign apps array // or the foreign apps array was null. @@ -23,7 +21,15 @@ function translateBoxReference( // its own foreign apps array. throw new Error(`Box ref with appId ${referenceId} not in foreign-apps`); } - return { i: index, n: referenceName }; + + const encodedReference: EncodedBoxReference = {}; + if (index !== 0) { + encodedReference.i = index; + } + if (referenceName.length) { + encodedReference.n = referenceName; + } + return encodedReference; } /** @@ -31,12 +37,13 @@ function translateBoxReference( * into an array of EncodedBoxReferences with foreign indices. */ export function translateBoxReferences( - references: BoxReference[] | undefined, - foreignApps: number[], - appIndex: number + references: ReadonlyArray, + foreignApps: ReadonlyArray, + appIndex: number | bigint ): EncodedBoxReference[] { - if (references == null) return []; + const appIndexBigInt = BigInt(appIndex); + const foreignAppsBigInt = foreignApps.map(BigInt); return references.map((bx) => - translateBoxReference(bx, foreignApps, appIndex) + translateBoxReference(bx, foreignAppsBigInt, appIndexBigInt) ); } diff --git a/src/client/kmd.ts b/src/client/kmd.ts index 72c3135b3..472f6a696 100644 --- a/src/client/kmd.ts +++ b/src/client/kmd.ts @@ -3,7 +3,7 @@ import { bytesToBase64, coerceToBytes, } from '../encoding/binarydata.js'; -import * as txn from '../transaction.js'; +import { Transaction } from '../transaction.js'; import { CustomTokenHeader, KMDTokenHeader } from './urlTokenBaseHTTPClient.js'; import ServiceClient from './v2/serviceClient.js'; @@ -260,14 +260,12 @@ export class KmdClient extends ServiceClient { async signTransaction( walletHandle: string, walletPassword: string, - transaction: txn.TransactionLike + transaction: Transaction ) { - const tx = txn.instantiateTxnIfNeeded(transaction); - const req = { wallet_handle_token: walletHandle, wallet_password: walletPassword, - transaction: bytesToBase64(tx.toByte()), + transaction: bytesToBase64(transaction.toByte()), }; const res = await this.c.post('/v1/transaction/sign', req); @@ -290,16 +288,15 @@ export class KmdClient extends ServiceClient { async signTransactionWithSpecificPublicKey( walletHandle: string, walletPassword: string, - transaction: txn.TransactionLike, + transaction: Transaction, publicKey: Uint8Array | string ) { - const tx = txn.instantiateTxnIfNeeded(transaction); const pk = coerceToBytes(publicKey); const req = { wallet_handle_token: walletHandle, wallet_password: walletPassword, - transaction: bytesToBase64(tx.toByte()), + transaction: bytesToBase64(transaction.toByte()), public_key: bytesToBase64(pk), }; const res = await this.c.post('/v1/transaction/sign', req); @@ -386,15 +383,14 @@ export class KmdClient extends ServiceClient { async signMultisigTransaction( walletHandle: string, pw: string, - transaction: txn.TransactionLike, + transaction: Transaction, pk: Uint8Array | string, partial: string ) { - const tx = txn.instantiateTxnIfNeeded(transaction); const pubkey = coerceToBytes(pk); const req = { wallet_handle_token: walletHandle, - transaction: bytesToBase64(tx.toByte()), + transaction: bytesToBase64(transaction.toByte()), public_key: bytesToBase64(pubkey), partial_multisig: partial, wallet_password: pw, diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index 03a267f2a..5b1306b62 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -476,7 +476,7 @@ export class AlgodClient extends ServiceClient { * @param index - The asset ID to look up. * @category GET */ - getAssetByID(index: number) { + getAssetByID(index: number | bigint) { return new GetAssetByID(this.c, this.intDecoding, index); } @@ -494,7 +494,7 @@ export class AlgodClient extends ServiceClient { * @param index - The application ID to look up. * @category GET */ - getApplicationByID(index: number) { + getApplicationByID(index: number | bigint) { return new GetApplicationByID(this.c, this.intDecoding, index); } diff --git a/src/client/v2/algod/getApplicationByID.ts b/src/client/v2/algod/getApplicationByID.ts index 7c3d45d07..858719120 100644 --- a/src/client/v2/algod/getApplicationByID.ts +++ b/src/client/v2/algod/getApplicationByID.ts @@ -10,7 +10,7 @@ export default class GetApplicationByID extends JSONRequest< constructor( c: HTTPClient, intDecoding: IntDecoding, - private index: number + private index: number | bigint ) { super(c, intDecoding); this.index = index; diff --git a/src/client/v2/algod/getAssetByID.ts b/src/client/v2/algod/getAssetByID.ts index fd764048c..ed4f1b3df 100644 --- a/src/client/v2/algod/getAssetByID.ts +++ b/src/client/v2/algod/getAssetByID.ts @@ -10,7 +10,7 @@ export default class GetAssetByID extends JSONRequest< constructor( c: HTTPClient, intDecoding: IntDecoding, - private index: number + private index: number | bigint ) { super(c, intDecoding); this.index = index; diff --git a/src/client/v2/algod/suggestedParams.ts b/src/client/v2/algod/suggestedParams.ts index 0c989f3e7..6c0485015 100644 --- a/src/client/v2/algod/suggestedParams.ts +++ b/src/client/v2/algod/suggestedParams.ts @@ -1,11 +1,21 @@ import JSONRequest from '../jsonrequest.js'; -import { SuggestedParamsWithMinFee } from '../../../types/transactions/base.js'; +import { SuggestedParams } from '../../../types/transactions/base.js'; + +export interface SuggestedParamsFromAlgod extends SuggestedParams { + flatFee: boolean; + fee: bigint; + minFee: bigint; + firstValid: bigint; + lastValid: bigint; + genesisID: string; + genesisHash: string; +} /** * Returns the common needed parameters for a new transaction, in a format the transaction builder expects */ export default class SuggestedParamsRequest extends JSONRequest< - SuggestedParamsWithMinFee, + SuggestedParamsFromAlgod, Record > { /* eslint-disable class-methods-use-this */ @@ -13,15 +23,15 @@ export default class SuggestedParamsRequest extends JSONRequest< return '/v2/transactions/params'; } - prepare(body: Record): SuggestedParamsWithMinFee { + prepare(body: Record): SuggestedParamsFromAlgod { return { flatFee: false, - fee: body.fee, - firstValid: body['last-round'], - lastValid: body['last-round'] + 1000, + fee: BigInt(body.fee), + firstValid: BigInt(body['last-round']), + lastValid: BigInt(body['last-round']) + BigInt(1000), genesisID: body['genesis-id'], genesisHash: body['genesis-hash'], - minFee: body['min-fee'], + minFee: BigInt(body['min-fee']), }; } /* eslint-enable class-methods-use-this */ diff --git a/src/composer.ts b/src/composer.ts index 616a6c5d3..e18433b73 100644 --- a/src/composer.ts +++ b/src/composer.ts @@ -459,7 +459,7 @@ export class AtomicTransactionComposer { const appCall = { txn: makeApplicationCallTxnFromObject({ sender, - appIndex: appID, + appId: appID, appArgs: appArgsEncoded, accounts: foreignAccounts, foreignApps, diff --git a/src/dryrun.ts b/src/dryrun.ts index d9a6b317d..92425f6f0 100644 --- a/src/dryrun.ts +++ b/src/dryrun.ts @@ -47,49 +47,51 @@ export async function createDryrun({ const appInfos: Application[] = []; const acctInfos: Account[] = []; - const apps: number[] = []; - const assets: number[] = []; + const apps: bigint[] = []; + const assets: bigint[] = []; const accts: string[] = []; for (const t of txns) { if (t.txn.type === TransactionType.appl) { accts.push(encodeAddress(t.txn.sender.publicKey)); - if (t.txn.appAccounts) - accts.push(...t.txn.appAccounts.map((a) => encodeAddress(a.publicKey))); + accts.push( + ...t.txn.applicationCall!.appAccounts.map((a) => + encodeAddress(a.publicKey) + ) + ); - if (t.txn.appForeignApps) { - apps.push(...t.txn.appForeignApps); - accts.push( - ...t.txn.appForeignApps.map((aidx) => getApplicationAddress(aidx)) - ); - } + apps.push(...t.txn.applicationCall!.appForeignApps); + accts.push( + ...t.txn.applicationCall!.appForeignApps.map(getApplicationAddress) + ); - if (t.txn.appForeignAssets) assets.push(...t.txn.appForeignAssets); + assets.push(...t.txn.applicationCall!.appForeignAssets); // Create application, - if (t.txn.appIndex === undefined || t.txn.appIndex === 0) { + if (t.txn.applicationCall!.appId === BigInt(0)) { appInfos.push( new Application({ id: defaultAppId, params: new ApplicationParams({ creator: encodeAddress(t.txn.sender.publicKey), - approvalProgram: t.txn.appApprovalProgram, - clearStateProgram: t.txn.appClearProgram, + approvalProgram: t.txn.applicationCall!.appApprovalProgram, + clearStateProgram: t.txn.applicationCall!.appClearProgram, localStateSchema: new ApplicationStateSchema({ - numUint: t.txn.appLocalInts, - numByteSlice: t.txn.appLocalByteSlices, + numUint: t.txn.applicationCall!.appLocalInts, + numByteSlice: t.txn.applicationCall!.appLocalByteSlices, }), globalStateSchema: new ApplicationStateSchema({ - numUint: t.txn.appGlobalInts, - numByteSlice: t.txn.appGlobalByteSlices, + numUint: t.txn.applicationCall!.appGlobalInts, + numByteSlice: t.txn.applicationCall!.appGlobalByteSlices, }), }), }) ); } else { - apps.push(t.txn.appIndex); - accts.push(getApplicationAddress(t.txn.appIndex)); + const { appId } = t.txn.applicationCall!; + apps.push(appId); + accts.push(getApplicationAddress(appId)); } } } diff --git a/src/group.ts b/src/group.ts index bcbbded89..16c8d50c0 100644 --- a/src/group.ts +++ b/src/group.ts @@ -1,97 +1,47 @@ -import * as txnBuilder from './transaction.js'; +import { Transaction } from './transaction.js'; import * as nacl from './nacl/naclWrappers.js'; import * as encoding from './encoding/encoding.js'; -import * as address from './encoding/address.js'; import * as utils from './utils/utils.js'; const ALGORAND_MAX_TX_GROUP_SIZE = 16; +const TX_GROUP_TAG = new TextEncoder().encode('TG'); -interface EncodedTxGroup { - txlist: Uint8Array[]; -} - -/** - * Aux class for group id calculation of a group of transactions - */ -export class TxGroup { - name = 'Transaction group'; - tag = new TextEncoder().encode('TG'); - txGroupHashes: Uint8Array[]; - - constructor(hashes: Uint8Array[]) { - if (hashes.length > ALGORAND_MAX_TX_GROUP_SIZE) { - const errorMsg = `${hashes.length.toString()} transactions grouped together but max group size is ${ALGORAND_MAX_TX_GROUP_SIZE.toString()}`; - throw Error(errorMsg); - } - - this.txGroupHashes = hashes; - } - - // eslint-disable-next-line camelcase - get_obj_for_encoding() { - const txgroup: EncodedTxGroup = { - txlist: this.txGroupHashes, - }; - return txgroup; - } - - // eslint-disable-next-line camelcase - static from_obj_for_encoding(txgroupForEnc: EncodedTxGroup) { - const txn = Object.create(this.prototype); - txn.name = 'Transaction group'; - txn.tag = new TextEncoder().encode('TG'); - txn.txGroupHashes = []; - for (const hash of txgroupForEnc.txlist) { - txn.txGroupHashes.push(hash); - } - return txn; - } - - toByte() { - return encoding.encode(this.get_obj_for_encoding()); +function txGroupPreimage(txnHashes: Uint8Array[]): Uint8Array { + if (txnHashes.length > ALGORAND_MAX_TX_GROUP_SIZE) { + throw Error( + `${txnHashes.length} transactions grouped together but max group size is ${ALGORAND_MAX_TX_GROUP_SIZE}` + ); } + const bytes = encoding.encode({ + txlist: txnHashes, + }); + return utils.concatArrays(TX_GROUP_TAG, bytes); } /** * computeGroupID returns group ID for a group of transactions - * @param txns - array of transactions (every element is a dict or Transaction) + * @param txns - array of transactions * @returns Uint8Array */ -export function computeGroupID(txns: txnBuilder.TransactionLike[]) { - const hashes = []; +export function computeGroupID(txns: ReadonlyArray) { + const hashes: Uint8Array[] = []; for (const txn of txns) { - const tx = txnBuilder.instantiateTxnIfNeeded(txn); - hashes.push(tx.rawTxID()); + hashes.push(txn.rawTxID()); } - const txgroup = new TxGroup(hashes); - - const bytes = txgroup.toByte(); - const toBeHashed = utils.concatArrays(txgroup.tag, bytes); + const toBeHashed = txGroupPreimage(hashes); const gid = nacl.genericHash(toBeHashed); return Uint8Array.from(gid); } /** * assignGroupID assigns group id to a given list of unsigned transactions - * @param txns - array of transactions (every element is a dict or Transaction) - * @param sender - optional sender address specifying which transaction return - * @returns possible list of matching transactions + * @param txns - array of transactions. The array elements will be modified with the group id */ -export function assignGroupID( - txns: txnBuilder.TransactionLike[], - sender?: string -) { +export function assignGroupID(txns: Transaction[]) { const gid = computeGroupID(txns); - const result: txnBuilder.Transaction[] = []; for (const txn of txns) { - const tx = txnBuilder.instantiateTxnIfNeeded(txn); - if (!sender || address.encodeAddress(tx.sender.publicKey) === sender) { - tx.group = gid; - result.push(tx); - } + txn.group = gid; } - return result; + return txns; } - -export default TxGroup; diff --git a/src/logicsig.ts b/src/logicsig.ts index 11a65f78c..1e02a4c6e 100644 --- a/src/logicsig.ts +++ b/src/logicsig.ts @@ -3,7 +3,7 @@ import * as address from './encoding/address.js'; import * as encoding from './encoding/encoding.js'; import { verifyMultisig } from './multisig.js'; import * as utils from './utils/utils.js'; -import * as txnBuilder from './transaction.js'; +import { Transaction } from './transaction.js'; import { isValidAddress } from './encoding/address.js'; import { EncodedLogicSig, @@ -377,7 +377,7 @@ export class LogicSigAccount { } function signLogicSigTransactionWithAddress( - txn: txnBuilder.Transaction, + txn: Transaction, lsig: LogicSig, lsigAddress: Uint8Array ) { @@ -412,7 +412,7 @@ function signLogicSigTransactionWithAddress( * @returns Object containing txID and blob representing signed transaction. */ export function signLogicSigTransactionObject( - txn: txnBuilder.Transaction, + txn: Transaction, lsigObject: LogicSig | LogicSigAccount ) { let lsig: LogicSig; @@ -456,11 +456,10 @@ export function signLogicSigTransactionObject( * @throws error on failure */ export function signLogicSigTransaction( - txn: txnBuilder.TransactionLike, + txn: Transaction, lsigObject: LogicSig | LogicSigAccount ) { - const algoTxn = txnBuilder.instantiateTxnIfNeeded(txn); - return signLogicSigTransactionObject(algoTxn, lsigObject); + return signLogicSigTransactionObject(txn, lsigObject); } /** diff --git a/src/main.ts b/src/main.ts index fe030f8b0..5783d37b7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,7 @@ import * as nacl from './nacl/naclWrappers.js'; import * as address from './encoding/address.js'; import * as encoding from './encoding/encoding.js'; -import * as txnBuilder from './transaction.js'; -import Bid, { BidOptions } from './bid.js'; +import { Transaction } from './transaction.js'; import * as convert from './convert.js'; import * as utils from './utils/utils.js'; @@ -27,36 +26,13 @@ export const MULTISIG_BAD_SENDER_ERROR_MSG = * @param sk - Algorand Secret Key * @returns object contains the binary signed transaction and its txID */ -export function signTransaction( - txn: txnBuilder.TransactionLike, - sk: Uint8Array -) { - if (typeof txn.sender === 'undefined') { - // Get pk from sk if no sender specified - const key = nacl.keyPairFromSecretKey(sk); - // eslint-disable-next-line no-param-reassign - txn.sender = address.encodeAddress(key.publicKey); - } - const algoTxn = txnBuilder.instantiateTxnIfNeeded(txn); - +export function signTransaction(txn: Transaction, sk: Uint8Array) { return { - txID: algoTxn.txID().toString(), - blob: algoTxn.signTxn(sk), + txID: txn.txID(), + blob: txn.signTxn(sk), }; } -/** - * signBid takes an object with the following fields: bidder key, bid amount, max price, bid ID, auctionKey, auction ID, - * and a secret key and returns a signed blob to be inserted into a transaction Algorand note field. - * @param bid - Algorand Bid - * @param sk - Algorand secret key - * @returns Uint8Array binary signed bid - */ -export function signBid(bid: BidOptions, sk: Uint8Array) { - const signedBid = new Bid(bid); - return signedBid.signBid(sk); -} - /** * signBytes takes arbitrary bytes and a secret key, prepends the bytes with "MX" for domain separation, signs the bytes * with the private key, and returns the signature. @@ -137,6 +113,7 @@ export { encodeAddress, decodeAddress, getApplicationAddress, + ALGORAND_ZERO_ADDRESS_STRING, } from './encoding/address.js'; export { bytesToBigInt, bigIntToBytes } from './encoding/bigint.js'; export { diff --git a/src/makeTxn.ts b/src/makeTxn.ts index 9cff6d487..eacdab0b5 100644 --- a/src/makeTxn.ts +++ b/src/makeTxn.ts @@ -1,1448 +1,774 @@ -import * as txnBuilder from './transaction.js'; -import { OnApplicationComplete } from './types/transactions/base.js'; +import { Transaction } from './transaction.js'; import { - // Transaction types - PaymentTxn, - KeyRegistrationTxn, - - // Utilities + OnApplicationComplete, TransactionType, - MustHaveSuggestedParams, - AssetCreateTxn, - AssetConfigTxn, - AssetDestroyTxn, - AssetFreezeTxn, - AssetTransferTxn, - AppCreateTxn, - AppUpdateTxn, - AppDeleteTxn, - AppOptInTxn, - AppCloseOutTxn, - AppClearStateTxn, - AppNoOpTxn, -} from './types/transactions/index.js'; -import { RenameProperties, RenameProperty, Expand } from './types/utils.js'; + SuggestedParams, + PaymentTransactionParams, + KeyRegistrationTransactionParams, + AssetConfigurationTransactionParams, + AssetTransferTransactionParams, + AssetFreezeTransactionParams, + ApplicationCallTransactionParams, +} from './types/transactions/base.js'; +import { Address } from './types/address.js'; + +/** Contains parameters common to every transaction type */ +export interface CommonTransactionParams { + /** Algorand address of sender */ + sender: string | Address; + /** Suggested parameters relevant to the network that will accept this transaction */ + suggestedParams: SuggestedParams; + /** Optional, arbitrary data to be stored in the transaction's note field */ + note?: Uint8Array; + /** + * Optional, 32-byte lease to associate with this transaction. + * + * The sender cannot send another transaction with the same lease until the last round of original + * transaction has passed. + */ + lease?: Uint8Array; + /** The Algorand address that will be used to authorize all future transactions from the sender, if provided. */ + rekeyTo?: string | Address; +} /** - * makePaymentTxnWithSuggestedParams takes payment arguments and returns a Transaction object - * @param sender - string representation of Algorand address of sender - * @param receiver - string representation of Algorand address of recipient - * @param amount - integer amount to send, in microAlgos - * @param closeRemainderTo - optionally close out remaining account balance to this account, represented as string rep of Algorand address - * @param note - uint8array of arbitrary data for sender to store - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param rekeyTo - rekeyTo address, optional + * Create a new payment transaction + * + * @param options - Payment transaction parameters */ -export function makePaymentTxnWithSuggestedParams( - sender: PaymentTxn['sender'], - receiver: PaymentTxn['receiver'], - amount: PaymentTxn['amount'], - closeRemainderTo: PaymentTxn['closeRemainderTo'], - note: PaymentTxn['note'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: PaymentTxn['rekeyTo'] -) { - const o: PaymentTxn = { +export function makePaymentTxnWithSuggestedParamsFromObject({ + sender, + receiver, + amount, + closeRemainderTo, + suggestedParams, + note, + lease, + rekeyTo, +}: PaymentTransactionParams & CommonTransactionParams): Transaction { + return new Transaction({ + type: TransactionType.pay, sender, - receiver, - amount, - closeRemainderTo, note, - suggestedParams, - type: TransactionType.pay, + lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makePaymentTxnWithSuggestedParams, instead accepting an arguments object -export function makePaymentTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperty, 'rekeyTo', 'rekeyTo'>, - | 'sender' - | 'receiver' - | 'amount' - | 'closeRemainderTo' - | 'note' - | 'suggestedParams' - | 'rekeyTo' - > - > -) { - return makePaymentTxnWithSuggestedParams( - o.sender, - o.receiver, - o.amount, - o.closeRemainderTo, - o.note, - o.suggestedParams, - o.rekeyTo - ); + suggestedParams, + paymentParams: { + receiver, + amount, + closeRemainderTo, + }, + }); } /** - * makeKeyRegistrationTxnWithSuggestedParams takes key registration arguments and returns a Transaction object for - * that key registration operation + * Create a new key registration transaction * - * @param sender - string representation of Algorand address of sender - * @param note - uint8array of arbitrary data for sender to store - * @param voteKey - voting key. for key deregistration, leave undefined - * @param selectionKey - selection key. for key deregistration, leave undefined - * @param voteFirst - first round on which voteKey is valid - * @param voteLast - last round on which voteKey is valid - * @param voteKeyDilution - integer - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param rekeyTo - rekeyTo address, optional - * @param nonParticipation - configure whether the address wants to stop participating. If true, - * voteKey, selectionKey, voteFirst, voteLast, and voteKeyDilution must be undefined. - * @param stateProofKey - state proof key. for key deregistration, leave undefined + * @param options - Key registration transaction parameters */ -export function makeKeyRegistrationTxnWithSuggestedParams( - sender: KeyRegistrationTxn['sender'], - note: KeyRegistrationTxn['note'], - voteKey: KeyRegistrationTxn['voteKey'], - selectionKey: KeyRegistrationTxn['selectionKey'], - voteFirst: KeyRegistrationTxn['voteFirst'], - voteLast: KeyRegistrationTxn['voteLast'], - voteKeyDilution: KeyRegistrationTxn['voteKeyDilution'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: KeyRegistrationTxn['rekeyTo'], - nonParticipation?: false, - stateProofKey?: KeyRegistrationTxn['stateProofKey'] -): txnBuilder.Transaction; -export function makeKeyRegistrationTxnWithSuggestedParams( - sender: KeyRegistrationTxn['sender'], - note: KeyRegistrationTxn['note'], - voteKey: undefined, - selectionKey: undefined, - voteFirst: undefined, - voteLast: undefined, - voteKeyDilution: undefined, - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: KeyRegistrationTxn['rekeyTo'], - nonParticipation?: true, - stateProofKey?: undefined -): txnBuilder.Transaction; -export function makeKeyRegistrationTxnWithSuggestedParams( - sender: any, - note: any, - voteKey: any, - selectionKey: any, - voteFirst: any, - voteLast: any, - voteKeyDilution: any, - suggestedParams: any, - rekeyTo?: any, - nonParticipation = false, - stateProofKey: any = undefined -) { - const o: KeyRegistrationTxn = { +export function makeKeyRegistrationTxnWithSuggestedParamsFromObject({ + sender, + voteKey, + selectionKey, + stateProofKey, + voteFirst, + voteLast, + voteKeyDilution, + nonParticipation, + suggestedParams, + note, + lease, + rekeyTo, +}: KeyRegistrationTransactionParams & CommonTransactionParams): Transaction { + return new Transaction({ + type: TransactionType.keyreg, sender, note, - voteKey, - selectionKey, - voteFirst, - voteLast, - voteKeyDilution, - suggestedParams, - type: TransactionType.keyreg, + lease, rekeyTo, - nonParticipation, - stateProofKey, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeKeyRegistrationTxnWithSuggestedParams, instead accepting an arguments object -export function makeKeyRegistrationTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperty< - MustHaveSuggestedParams, - 'rekeyTo', - 'rekeyTo' - >, - | 'sender' - | 'note' - | 'voteKey' - | 'selectionKey' - | 'stateProofKey' - | 'voteFirst' - | 'voteLast' - | 'voteKeyDilution' - | 'suggestedParams' - | 'rekeyTo' - > & { - nonParticipation?: false; - } - > -): txnBuilder.Transaction; -export function makeKeyRegistrationTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperty< - MustHaveSuggestedParams, - 'rekeyTo', - 'rekeyTo' - >, - 'sender' | 'note' | 'suggestedParams' | 'rekeyTo' | 'nonParticipation' - > - > -): txnBuilder.Transaction; -export function makeKeyRegistrationTxnWithSuggestedParamsFromObject(o: any) { - return makeKeyRegistrationTxnWithSuggestedParams( - o.sender, - o.note, - o.voteKey, - o.selectionKey, - o.voteFirst, - o.voteLast, - o.voteKeyDilution, - o.suggestedParams, - o.rekeyTo, - o.nonParticipation, - o.stateProofKey - ); + suggestedParams, + keyregParams: { + voteKey, + selectionKey, + stateProofKey, + voteFirst, + voteLast, + voteKeyDilution, + nonParticipation, + }, + }); } -/** makeAssetCreateTxnWithSuggestedParams takes asset creation arguments and returns a Transaction object - * for creating that asset +/** + * Base function for creating any type of asset config transaction. * - * @param sender - string representation of Algorand address of sender - * @param note - uint8array of arbitrary data for sender to store - * @param total - integer total supply of the asset - * @param decimals - integer number of decimals for asset unit calculation - * @param defaultFrozen - boolean whether asset accounts should default to being frozen - * @param manager - string representation of Algorand address in charge of reserve, freeze, clawback, destruction, etc - * @param reserve - string representation of Algorand address representing asset reserve - * @param freeze - string representation of Algorand address with power to freeze/unfreeze asset holdings - * @param clawback - string representation of Algorand address with power to revoke asset holdings - * @param unitName - string units name for this asset - * @param assetName - string name for this asset - * @param assetURL - string URL relating to this asset - * @param assetMetadataHash - Uint8Array or UTF-8 string representation of a hash commitment with respect to the asset. Must be exactly 32 bytes long. - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param rekeyTo - rekeyTo address, optional + * @param options - Asset config transaction parameters */ -export function makeAssetCreateTxnWithSuggestedParams( - sender: AssetCreateTxn['sender'], - note: AssetCreateTxn['note'], - total: AssetCreateTxn['assetTotal'], - decimals: AssetCreateTxn['assetDecimals'], - defaultFrozen: AssetCreateTxn['assetDefaultFrozen'], - manager: AssetCreateTxn['assetManager'], - reserve: AssetCreateTxn['assetReserve'], - freeze: AssetCreateTxn['assetFreeze'], - clawback: AssetCreateTxn['assetClawback'], - unitName: AssetCreateTxn['assetUnitName'], - assetName: AssetCreateTxn['assetName'], - assetURL: AssetCreateTxn['assetURL'], - assetMetadataHash: AssetCreateTxn['assetMetadataHash'] | undefined, - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: AssetCreateTxn['rekeyTo'] -) { - const o: AssetCreateTxn = { +export function makeBaseAssetConfigTxn({ + sender, + assetIndex, + total, + decimals, + defaultFrozen, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + note, + lease, + rekeyTo, + suggestedParams, +}: AssetConfigurationTransactionParams & CommonTransactionParams): Transaction { + return new Transaction({ + type: TransactionType.acfg, sender, note, + lease, + rekeyTo, suggestedParams, - assetTotal: total, - assetDecimals: decimals, - assetDefaultFrozen: defaultFrozen, - assetUnitName: unitName, + assetConfigParams: { + assetIndex, + total, + decimals, + defaultFrozen, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + }, + }); +} + +/** + * Create a new asset creation transaction + * + * @param options - Asset creation transaction parameters + */ +export function makeAssetCreateTxnWithSuggestedParamsFromObject({ + sender, + total, + decimals, + defaultFrozen, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit & + CommonTransactionParams): Transaction { + return makeBaseAssetConfigTxn({ + sender, + total, + decimals, + defaultFrozen, + manager, + reserve, + freeze, + clawback, + unitName, assetName, assetURL, assetMetadataHash, - assetManager: manager, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - type: TransactionType.acfg, + note, + lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); + suggestedParams, + }); } -// helper for above makeAssetCreateTxnWithSuggestedParams, instead accepting an arguments object -export function makeAssetCreateTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - rekeyTo: 'rekeyTo'; - assetTotal: 'total'; - assetDecimals: 'decimals'; - assetDefaultFrozen: 'defaultFrozen'; - assetManager: 'manager'; - assetReserve: 'reserve'; - assetFreeze: 'freeze'; - assetClawback: 'clawback'; - assetUnitName: 'unitName'; - } - >, - | 'sender' - | 'note' - | 'total' - | 'decimals' - | 'defaultFrozen' - | 'manager' - | 'reserve' - | 'freeze' - | 'clawback' - | 'unitName' - | 'assetName' - | 'assetURL' - | 'assetMetadataHash' - | 'suggestedParams' - | 'rekeyTo' - > - > -) { - return makeAssetCreateTxnWithSuggestedParams( - o.sender, - o.note, - o.total, - o.decimals, - o.defaultFrozen, - o.manager, - o.reserve, - o.freeze, - o.clawback, - o.unitName, - o.assetName, - o.assetURL, - o.assetMetadataHash, - o.suggestedParams, - o.rekeyTo - ); +/** Contains asset modification transaction parameters */ +export interface AssetModificationTransactionParams { + /** + * The unique ID of the asset to be modified + */ + assetIndex: number | bigint; + + /** + * The Algorand address in charge of reserve, freeze, clawback, destruction, etc. + * + * If empty, this role will be irrevocably removed from this asset. + */ + manager?: string | Address; + + /** + * The Algorand address representing asset reserve. + * + * If empty, this role will be irrevocably removed from this asset. + */ + reserve?: string | Address; + + /** + * The Algorand address with power to freeze/unfreeze asset holdings. + * + * If empty, this role will be irrevocably removed from this asset. + */ + freeze?: string | Address; + + /** + * The Algorand address with power to revoke asset holdings. + * + * If empty, this role will be irrevocably removed from this asset. + */ + clawback?: string | Address; + + /** + * This is a safety flag to prevent unintentionally removing a role from an asset. If false or + * undefined, an error will be thrown if any of assetManager, assetReserve, assetFreeze, or + * assetClawback are empty. + * + * Set this to true to allow removing roles by leaving the corresponding address empty. + */ + allowRoleRemoval?: boolean; } -/** makeAssetConfigTxnWithSuggestedParams can be issued by the asset manager to change the manager, reserve, freeze, or clawback - * you must respecify existing addresses to keep them the same; leaving a field blank is the same as turning - * that feature off for this asset +/** + * Create a new asset config transaction. This transaction can be issued by the asset manager to + * change the manager, reserve, freeze, or clawback address. + * + * You must respecify existing addresses to keep them the same; leaving a field blank is the same as + * turning that feature off for this asset. * - * @param sender - string representation of Algorand address of sender - * @param note - uint8array of arbitrary data for sender to store - * @param assetIndex - int asset index uniquely specifying the asset - * @param manager - string representation of new asset manager Algorand address - * @param reserve - string representation of new reserve Algorand address - * @param freeze - string representation of new freeze manager Algorand address - * @param clawback - string representation of new revocation manager Algorand address - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param rekeyTo - rekeyTo address, optional - * @param strictEmptyAddressChecking - boolean - throw an error if any of manager, reserve, freeze, or clawback are undefined. optional, defaults to true. + * @param options - Asset modification transaction parameters */ -export function makeAssetConfigTxnWithSuggestedParams( - sender: AssetConfigTxn['sender'], - note: AssetConfigTxn['note'], - assetIndex: AssetConfigTxn['assetIndex'], - manager: AssetConfigTxn['assetManager'], - reserve: AssetConfigTxn['assetReserve'], - freeze: AssetConfigTxn['assetFreeze'], - clawback: AssetConfigTxn['assetClawback'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: AssetConfigTxn['rekeyTo'], - strictEmptyAddressChecking = true -) { +export function makeAssetConfigTxnWithSuggestedParamsFromObject({ + sender, + assetIndex, + manager, + reserve, + freeze, + clawback, + allowRoleRemoval, + note, + lease, + rekeyTo, + suggestedParams, +}: AssetModificationTransactionParams & CommonTransactionParams): Transaction { + if (!assetIndex) { + throw Error('assetIndex must be provided'); + } if ( - strictEmptyAddressChecking && - (manager === undefined || - reserve === undefined || - freeze === undefined || - clawback === undefined) + !allowRoleRemoval && + (manager == null || reserve == null || freeze == null || clawback == null) ) { throw Error( - 'strict empty address checking was turned on, but at least one empty address was provided' + 'allowRoleRemoval is not enabled, but an address is empty. If this is intentional, set allowRoleRemoval to true.' ); } - const o: AssetConfigTxn = { + return makeBaseAssetConfigTxn({ sender, - suggestedParams, assetIndex, - assetManager: manager, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - type: TransactionType.acfg, + manager, + reserve, + freeze, + clawback, note, + lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeAssetConfigTxnWithSuggestedParams, instead accepting an arguments object -export function makeAssetConfigTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - rekeyTo: 'rekeyTo'; - assetManager: 'manager'; - assetReserve: 'reserve'; - assetFreeze: 'freeze'; - assetClawback: 'clawback'; - } - >, - | 'sender' - | 'note' - | 'assetIndex' - | 'manager' - | 'reserve' - | 'freeze' - | 'clawback' - | 'suggestedParams' - | 'rekeyTo' - > & { - strictEmptyAddressChecking: boolean; - } - > -) { - return makeAssetConfigTxnWithSuggestedParams( - o.sender, - o.note, - o.assetIndex, - o.manager, - o.reserve, - o.freeze, - o.clawback, - o.suggestedParams, - o.rekeyTo, - o.strictEmptyAddressChecking - ); + suggestedParams, + }); } -/** makeAssetDestroyTxnWithSuggestedParams will allow the asset's manager to remove this asset from the ledger, so long - * as all outstanding assets are held by the creator. +/** + * Create a new asset destroy transaction. This will allow the asset's manager to remove this asset + * from the ledger, provided all outstanding assets are held by the creator. * - * @param sender - string representation of Algorand address of sender - * @param note - uint8array of arbitrary data for sender to store - * @param assetIndex - int asset index uniquely specifying the asset - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param rekeyTo - rekeyTo address, optional + * @param options - Asset destroy transaction parameters */ -export function makeAssetDestroyTxnWithSuggestedParams( - sender: AssetDestroyTxn['sender'], - note: AssetDestroyTxn['note'], - assetIndex: AssetDestroyTxn['assetIndex'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: AssetDestroyTxn['rekeyTo'] -) { - const o: AssetDestroyTxn = { +export function makeAssetDestroyTxnWithSuggestedParamsFromObject({ + sender, + assetIndex, + note, + lease, + rekeyTo, + suggestedParams, +}: Required> & + CommonTransactionParams): Transaction { + if (!assetIndex) { + throw Error('assetIndex must be provided'); + } + return makeBaseAssetConfigTxn({ sender, - suggestedParams, assetIndex, - type: TransactionType.acfg, note, + lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeAssetDestroyTxnWithSuggestedParams, instead accepting an arguments object -export function makeAssetDestroyTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperty< - MustHaveSuggestedParams, - 'rekeyTo', - 'rekeyTo' - >, - 'sender' | 'note' | 'assetIndex' | 'suggestedParams' | 'rekeyTo' - > - > -) { - return makeAssetDestroyTxnWithSuggestedParams( - o.sender, - o.note, - o.assetIndex, - o.suggestedParams, - o.rekeyTo - ); + suggestedParams, + }); } -/** makeAssetFreezeTxnWithSuggestedParams will allow the asset's freeze manager to freeze or un-freeze an account, - * blocking or allowing asset transfers to and from the targeted account. +/** + * Create a new asset freeze transaction. This transaction allows the asset's freeze manager to + * freeze or un-freeze an account, blocking or allowing asset transfers to and from the targeted + * account. * - * @param sender - string representation of Algorand address of sender - * @param note - uint8array of arbitrary data for sender to store - * @param assetIndex - int asset index uniquely specifying the asset - * @param freezeTarget - string representation of Algorand address being frozen or unfrozen - * @param assetFrozen - true if freezeTarget should be frozen, false if freezeTarget should be allowed to transact - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param rekeyTo - rekeyTo address, optional + * @param options - Asset freeze transaction parameters */ -export function makeAssetFreezeTxnWithSuggestedParams( - sender: AssetFreezeTxn['sender'], - note: AssetFreezeTxn['note'], - assetIndex: AssetFreezeTxn['assetIndex'], - freezeTarget: AssetFreezeTxn['freezeAccount'], - assetFrozen: AssetFreezeTxn['assetFrozen'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: AssetFreezeTxn['rekeyTo'] -) { - const o: AssetFreezeTxn = { - sender, +export function makeAssetFreezeTxnWithSuggestedParamsFromObject({ + sender, + assetIndex, + freezeTarget, + assetFrozen, + suggestedParams, + note, + lease, + rekeyTo, +}: AssetFreezeTransactionParams & CommonTransactionParams): Transaction { + return new Transaction({ type: TransactionType.afrz, - freezeAccount: freezeTarget, - assetIndex, - assetFrozen, + sender, note, - suggestedParams, + lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeAssetFreezeTxnWithSuggestedParams, instead accepting an arguments object -export function makeAssetFreezeTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - freezeAccount: 'freezeTarget'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'note' - | 'assetIndex' - | 'freezeTarget' - | 'assetFrozen' - | 'suggestedParams' - | 'rekeyTo' - > - > -) { - return makeAssetFreezeTxnWithSuggestedParams( - o.sender, - o.note, - o.assetIndex, - o.freezeTarget, - o.assetFrozen, - o.suggestedParams, - o.rekeyTo - ); + suggestedParams, + assetFreezeParams: { + assetIndex, + freezeTarget, + assetFrozen, + }, + }); } -/** makeAssetTransferTxnWithSuggestedParams allows for the creation of an asset transfer transaction. - * Special case: to begin accepting assets, set amount=0 and sender=receiver. +/** + * Create a new asset transfer transaction. * - * @param sender - string representation of Algorand address of sender - * @param receiver - string representation of Algorand address of asset recipient - * @param closeRemainderTo - optional - string representation of Algorand address - if provided, - * send all remaining assets after transfer to the "closeRemainderTo" address and close "sender"'s asset holdings - * @param assetSender - optional - string representation of Algorand address - if provided, - * and if "sender" is the asset's revocation manager, then deduct from "assetSender" rather than "sender" - * @param amount - integer amount of assets to send - * @param note - uint8array of arbitrary data for sender to store - * @param assetIndex - int asset index uniquely specifying the asset - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param rekeyTo - rekeyTo address, optional + * Special case: to opt into an assets, set amount=0 and sender=receiver. + * + * @param options - Asset transfer transaction parameters */ -export function makeAssetTransferTxnWithSuggestedParams( - sender: AssetTransferTxn['sender'], - receiver: AssetTransferTxn['receiver'], - closeRemainderTo: AssetTransferTxn['closeRemainderTo'], - assetSender: AssetTransferTxn['assetSender'], - amount: AssetTransferTxn['amount'], - note: AssetTransferTxn['note'], - assetIndex: AssetTransferTxn['assetIndex'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - rekeyTo?: AssetTransferTxn['rekeyTo'] -) { - const o: AssetTransferTxn = { +export function makeAssetTransferTxnWithSuggestedParamsFromObject({ + sender, + receiver, + amount, + closeRemainderTo, + assetSender, + note, + assetIndex, + suggestedParams, + rekeyTo, + lease, +}: AssetTransferTransactionParams & CommonTransactionParams): Transaction { + if (!assetIndex) { + throw Error('assetIndex must be provided'); + } + return new Transaction({ type: TransactionType.axfer, sender, - receiver, - amount, - suggestedParams, - assetIndex, note, - assetSender, - closeRemainderTo, + lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); + suggestedParams, + assetTransferParams: { + assetIndex, + receiver, + amount, + assetSender, + closeRemainderTo, + }, + }); } -// helper for above makeAssetTransferTxnWithSuggestedParams, instead accepting an arguments object -export function makeAssetTransferTxnWithSuggestedParamsFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - assetSender: 'assetSender'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'receiver' - | 'closeRemainderTo' - | 'assetSender' - | 'amount' - | 'note' - | 'assetIndex' - | 'suggestedParams' - | 'rekeyTo' - > - > -) { - return makeAssetTransferTxnWithSuggestedParams( - o.sender, - o.receiver, - o.closeRemainderTo, - o.assetSender, - o.amount, - o.note, - o.assetIndex, - o.suggestedParams, - o.rekeyTo - ); +/** + * Base function for creating any application call transaction. + * + * @param options - Application call transaction parameters + */ +export function makeApplicationCallTxnFromObject({ + sender, + appId, + onComplete, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + approvalProgram, + clearProgram, + numLocalInts, + numLocalByteSlices, + numGlobalInts, + numGlobalByteSlices, + extraPages, + note, + lease, + rekeyTo, + suggestedParams, +}: ApplicationCallTransactionParams & CommonTransactionParams): Transaction { + return new Transaction({ + type: TransactionType.appl, + sender, + note, + lease, + rekeyTo, + suggestedParams, + appCallParams: { + appId, + onComplete, + appArgs, + accounts, + foreignAssets, + foreignApps, + boxes, + approvalProgram, + clearProgram, + numLocalInts, + numLocalByteSlices, + numGlobalInts, + numGlobalByteSlices, + extraPages, + }, + }); } /** * Make a transaction that will create an application. - * @param sender - address of sender - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param onComplete - algosdk.OnApplicationComplete, what application should do once the program is done being run - * @param approvalProgram - Uint8Array, the compiled TEAL that approves a transaction - * @param clearProgram - Uint8Array, the compiled TEAL that runs when clearing state - * @param numLocalInts - restricts number of ints in per-user local state - * @param numLocalByteSlices - restricts number of byte slices in per-user local state - * @param numGlobalInts - restricts number of ints in global state - * @param numGlobalByteSlices - restricts number of byte slices in global state - * @param appArgs - Array of Uint8Array, any additional arguments to the application - * @param accounts - Array of Address strings, any additional accounts to supply to the application - * @param foreignApps - Array of int, any other apps used by the application, identified by index - * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param note - Arbitrary data for sender to store - * @param lease - Lease a transaction - * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions - * @param extraPages - integer extra pages of memory to rent on creation of application - * @param boxes - Array of BoxReference, app ID and name of box to be accessed + * + * @param options - Application creation transaction parameters */ -export function makeApplicationCreateTxn( - sender: AppCreateTxn['sender'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - onComplete: AppCreateTxn['appOnComplete'], - approvalProgram: AppCreateTxn['appApprovalProgram'], - clearProgram: AppCreateTxn['appClearProgram'], - numLocalInts: AppCreateTxn['appLocalInts'], - numLocalByteSlices: AppCreateTxn['appLocalByteSlices'], - numGlobalInts: AppCreateTxn['appGlobalInts'], - numGlobalByteSlices: AppCreateTxn['appGlobalByteSlices'], - appArgs?: AppCreateTxn['appArgs'], - accounts?: AppCreateTxn['appAccounts'], - foreignApps?: AppCreateTxn['appForeignApps'], - foreignAssets?: AppCreateTxn['appForeignAssets'], - note?: AppCreateTxn['note'], - lease?: AppCreateTxn['lease'], - rekeyTo?: AppCreateTxn['rekeyTo'], - extraPages?: AppCreateTxn['extraPages'], - boxes?: AppCreateTxn['boxes'] -) { - const o: AppCreateTxn = { - type: TransactionType.appl, +export function makeApplicationCreateTxnFromObject({ + sender, + onComplete, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + approvalProgram, + clearProgram, + numLocalInts, + numLocalByteSlices, + numGlobalInts, + numGlobalByteSlices, + extraPages, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit & // TODO: make programs required + CommonTransactionParams): Transaction { + return makeApplicationCallTxnFromObject({ sender, - suggestedParams, - appIndex: 0, - appOnComplete: onComplete, - appLocalInts: numLocalInts, - appLocalByteSlices: numLocalByteSlices, - appGlobalInts: numGlobalInts, - appGlobalByteSlices: numGlobalByteSlices, - appApprovalProgram: approvalProgram, - appClearProgram: clearProgram, + appId: 0, + onComplete, appArgs, - appAccounts: accounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts, + foreignApps, + foreignAssets, boxes, + approvalProgram, + clearProgram, + numLocalInts, + numLocalByteSlices, + numGlobalInts, + numGlobalByteSlices, + extraPages, note, lease, rekeyTo, - extraPages, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeApplicationCreateTxn, instead accepting an arguments object -export function makeApplicationCreateTxnFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appOnComplete: 'onComplete'; - appApprovalProgram: 'approvalProgram'; - appClearProgram: 'clearProgram'; - appLocalInts: 'numLocalInts'; - appLocalByteSlices: 'numLocalByteSlices'; - appGlobalInts: 'numGlobalInts'; - appGlobalByteSlices: 'numGlobalByteSlices'; - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'onComplete' - | 'approvalProgram' - | 'clearProgram' - | 'numLocalInts' - | 'numLocalByteSlices' - | 'numGlobalInts' - | 'numGlobalByteSlices' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - | 'extraPages' - > - > -) { - return makeApplicationCreateTxn( - o.sender, - o.suggestedParams, - o.onComplete, - o.approvalProgram, - o.clearProgram, - o.numLocalInts, - o.numLocalByteSlices, - o.numGlobalInts, - o.numGlobalByteSlices, - o.appArgs, - o.accounts, - o.foreignApps, - o.foreignAssets, - o.note, - o.lease, - o.rekeyTo, - o.extraPages, - o.boxes - ); + suggestedParams, + }); } /** * Make a transaction that changes an application's approval and clear programs - * @param sender - address of sender - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param appIndex - the ID of the app to be updated - * @param approvalProgram - Uint8Array, the compiled TEAL that approves a transaction - * @param clearProgram - Uint8Array, the compiled TEAL that runs when clearing state - * @param appArgs - Array of Uint8Array, any additional arguments to the application - * @param accounts - Array of Address strings, any additional accounts to supply to the application - * @param foreignApps - Array of int, any other apps used by the application, identified by index - * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param note - Arbitrary data for sender to store - * @param lease - Lease a transaction - * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions - * @param boxes - Array of BoxReference, app ID and name of box to be accessed + * + * @param options - Application update transaction parameters */ -export function makeApplicationUpdateTxn( - sender: AppUpdateTxn['sender'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - appIndex: AppUpdateTxn['appIndex'], - approvalProgram: AppUpdateTxn['appApprovalProgram'], - clearProgram: AppUpdateTxn['appClearProgram'], - appArgs?: AppUpdateTxn['appArgs'], - accounts?: AppUpdateTxn['appAccounts'], - foreignApps?: AppUpdateTxn['appForeignApps'], - foreignAssets?: AppUpdateTxn['appForeignAssets'], - note?: AppUpdateTxn['note'], - lease?: AppUpdateTxn['lease'], - rekeyTo?: AppUpdateTxn['rekeyTo'], - boxes?: AppUpdateTxn['boxes'] -) { - const o: AppUpdateTxn = { - type: TransactionType.appl, +export function makeApplicationUpdateTxnFromObject({ + sender, + appId, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + approvalProgram, + clearProgram, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit< + ApplicationCallTransactionParams, + | 'onComplete' + | 'numLocalInts' + | 'numLocalByteSlices' + | 'numGlobalInts' + | 'numGlobalByteSlices' + | 'extraPages' +> & // TODO: make programs required + CommonTransactionParams): Transaction { + if (!appId) { + throw Error('appId must be provided'); + } + return makeApplicationCallTxnFromObject({ sender, - suggestedParams, - appIndex, - appApprovalProgram: approvalProgram, - appOnComplete: OnApplicationComplete.UpdateApplicationOC, - appClearProgram: clearProgram, + appId, + onComplete: OnApplicationComplete.UpdateApplicationOC, appArgs, - appAccounts: accounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts, + foreignApps, + foreignAssets, boxes, + approvalProgram, + clearProgram, note, lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeApplicationUpdateTxn, instead accepting an arguments object -export function makeApplicationUpdateTxnFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appApprovalProgram: 'approvalProgram'; - appClearProgram: 'clearProgram'; - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'appIndex' - | 'approvalProgram' - | 'clearProgram' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - > - > -) { - return makeApplicationUpdateTxn( - o.sender, - o.suggestedParams, - o.appIndex, - o.approvalProgram, - o.clearProgram, - o.appArgs, - o.accounts, - o.foreignApps, - o.foreignAssets, - o.note, - o.lease, - o.rekeyTo, - o.boxes - ); + suggestedParams, + }); } /** * Make a transaction that deletes an application - * @param sender - address of sender - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param appIndex - the ID of the app to be deleted - * @param appArgs - Array of Uint8Array, any additional arguments to the application - * @param accounts - Array of Address strings, any additional accounts to supply to the application - * @param foreignApps - Array of int, any other apps used by the application, identified by index - * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param note - Arbitrary data for sender to store - * @param lease - Lease a transaction - * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions - * @param boxes - Array of BoxReference, app ID and name of box to be accessed + * + * @param options - Application deletion transaction parameters */ -export function makeApplicationDeleteTxn( - sender: AppDeleteTxn['sender'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - appIndex: AppDeleteTxn['appIndex'], - appArgs?: AppDeleteTxn['appArgs'], - accounts?: AppDeleteTxn['appAccounts'], - foreignApps?: AppDeleteTxn['appForeignApps'], - foreignAssets?: AppDeleteTxn['appForeignAssets'], - note?: AppDeleteTxn['note'], - lease?: AppDeleteTxn['lease'], - rekeyTo?: AppDeleteTxn['rekeyTo'], - boxes?: AppDeleteTxn['boxes'] -) { - const o: AppDeleteTxn = { - type: TransactionType.appl, +export function makeApplicationDeleteTxnFromObject({ + sender, + appId, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit< + ApplicationCallTransactionParams, + | 'onComplete' + | 'numLocalInts' + | 'numLocalByteSlices' + | 'numGlobalInts' + | 'numGlobalByteSlices' + | 'extraPages' + | 'approvalProgram' + | 'clearProgram' +> & + CommonTransactionParams): Transaction { + if (!appId) { + throw Error('appId must be provided'); + } + return makeApplicationCallTxnFromObject({ sender, - suggestedParams, - appIndex, - appOnComplete: OnApplicationComplete.DeleteApplicationOC, + appId, + onComplete: OnApplicationComplete.DeleteApplicationOC, appArgs, - appAccounts: accounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts, + foreignApps, + foreignAssets, boxes, note, lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeApplicationDeleteTxn, instead accepting an arguments object -export function makeApplicationDeleteTxnFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'appIndex' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - > - > -) { - return makeApplicationDeleteTxn( - o.sender, - o.suggestedParams, - o.appIndex, - o.appArgs, - o.accounts, - o.foreignApps, - o.foreignAssets, - o.note, - o.lease, - o.rekeyTo, - o.boxes - ); + suggestedParams, + }); } /** * Make a transaction that opts in to use an application - * @param sender - address of sender - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param appIndex - the ID of the app to join - * @param appArgs - Array of Uint8Array, any additional arguments to the application - * @param accounts - Array of Address strings, any additional accounts to supply to the application - * @param foreignApps - Array of int, any other apps used by the application, identified by index - * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param note - Arbitrary data for sender to store - * @param lease - Lease a transaction - * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions - * @param boxes - Array of BoxReference, app ID and name of box to be accessed + * + * @param options - Application opt-in transaction parameters */ -export function makeApplicationOptInTxn( - sender: AppOptInTxn['sender'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - appIndex: AppOptInTxn['appIndex'], - appArgs?: AppOptInTxn['appArgs'], - accounts?: AppOptInTxn['appAccounts'], - foreignApps?: AppOptInTxn['appForeignApps'], - foreignAssets?: AppOptInTxn['appForeignAssets'], - note?: AppOptInTxn['note'], - lease?: AppOptInTxn['lease'], - rekeyTo?: AppOptInTxn['rekeyTo'], - boxes?: AppOptInTxn['boxes'] -) { - const o: AppOptInTxn = { - type: TransactionType.appl, +export function makeApplicationOptInTxnFromObject({ + sender, + appId, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit< + ApplicationCallTransactionParams, + | 'onComplete' + | 'numLocalInts' + | 'numLocalByteSlices' + | 'numGlobalInts' + | 'numGlobalByteSlices' + | 'extraPages' + | 'approvalProgram' + | 'clearProgram' +> & + CommonTransactionParams): Transaction { + if (!appId) { + throw Error('appId must be provided'); + } + return makeApplicationCallTxnFromObject({ sender, - suggestedParams, - appIndex, - appOnComplete: OnApplicationComplete.OptInOC, + appId, + onComplete: OnApplicationComplete.OptInOC, appArgs, - appAccounts: accounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts, + foreignApps, + foreignAssets, boxes, note, lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeApplicationOptInTxn, instead accepting an argument object -export function makeApplicationOptInTxnFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'appIndex' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - > - > -) { - return makeApplicationOptInTxn( - o.sender, - o.suggestedParams, - o.appIndex, - o.appArgs, - o.accounts, - o.foreignApps, - o.foreignAssets, - o.note, - o.lease, - o.rekeyTo, - o.boxes - ); + suggestedParams, + }); } /** * Make a transaction that closes out a user's state in an application - * @param sender - address of sender - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param appIndex - the ID of the app to use - * @param appArgs - Array of Uint8Array, any additional arguments to the application - * @param accounts - Array of Address strings, any additional accounts to supply to the application - * @param foreignApps - Array of int, any other apps used by the application, identified by index - * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param note - Arbitrary data for sender to store - * @param lease - Lease a transaction - * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions - * @param boxes - Array of BoxReference, app ID and name of box to be accessed + * + * @param options - Application close-out transaction parameters */ -export function makeApplicationCloseOutTxn( - sender: AppCloseOutTxn['sender'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - appIndex: AppCloseOutTxn['appIndex'], - appArgs?: AppCloseOutTxn['appArgs'], - accounts?: AppCloseOutTxn['appAccounts'], - foreignApps?: AppCloseOutTxn['appForeignApps'], - foreignAssets?: AppCloseOutTxn['appForeignAssets'], - note?: AppCloseOutTxn['note'], - lease?: AppCloseOutTxn['lease'], - rekeyTo?: AppCloseOutTxn['rekeyTo'], - boxes?: AppCloseOutTxn['boxes'] -) { - const o: AppCloseOutTxn = { - type: TransactionType.appl, +export function makeApplicationCloseOutTxnFromObject({ + sender, + appId, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit< + ApplicationCallTransactionParams, + | 'onComplete' + | 'numLocalInts' + | 'numLocalByteSlices' + | 'numGlobalInts' + | 'numGlobalByteSlices' + | 'extraPages' + | 'approvalProgram' + | 'clearProgram' +> & + CommonTransactionParams): Transaction { + if (!appId) { + throw Error('appId must be provided'); + } + return makeApplicationCallTxnFromObject({ sender, - suggestedParams, - appIndex, - appOnComplete: OnApplicationComplete.CloseOutOC, + appId, + onComplete: OnApplicationComplete.CloseOutOC, appArgs, - appAccounts: accounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts, + foreignApps, + foreignAssets, boxes, note, lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeApplicationCloseOutTxn, instead accepting an argument object -export function makeApplicationCloseOutTxnFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'appIndex' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - > - > -) { - return makeApplicationCloseOutTxn( - o.sender, - o.suggestedParams, - o.appIndex, - o.appArgs, - o.accounts, - o.foreignApps, - o.foreignAssets, - o.note, - o.lease, - o.rekeyTo, - o.boxes - ); + suggestedParams, + }); } /** * Make a transaction that clears a user's state in an application - * @param sender - address of sender - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param appIndex - the ID of the app to use - * @param appArgs - Array of Uint8Array, any additional arguments to the application - * @param accounts - Array of Address strings, any additional accounts to supply to the application - * @param foreignApps - Array of int, any other apps used by the application, identified by index - * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param note - Arbitrary data for sender to store - * @param lease - Lease a transaction - * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions - * @param boxes - Array of BoxReference, app ID and name of box to be accessed + * + * @param options - Application clear state transaction parameters */ -export function makeApplicationClearStateTxn( - sender: AppClearStateTxn['sender'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - appIndex: AppClearStateTxn['appIndex'], - appArgs?: AppClearStateTxn['appArgs'], - accounts?: AppClearStateTxn['appAccounts'], - foreignApps?: AppClearStateTxn['appForeignApps'], - foreignAssets?: AppClearStateTxn['appForeignAssets'], - note?: AppClearStateTxn['note'], - lease?: AppClearStateTxn['lease'], - rekeyTo?: AppClearStateTxn['rekeyTo'], - boxes?: AppClearStateTxn['boxes'] -) { - const o: AppClearStateTxn = { - type: TransactionType.appl, +export function makeApplicationClearStateTxnFromObject({ + sender, + appId, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit< + ApplicationCallTransactionParams, + | 'onComplete' + | 'numLocalInts' + | 'numLocalByteSlices' + | 'numGlobalInts' + | 'numGlobalByteSlices' + | 'extraPages' + | 'approvalProgram' + | 'clearProgram' +> & + CommonTransactionParams): Transaction { + if (!appId) { + throw Error('appId must be provided'); + } + return makeApplicationCallTxnFromObject({ sender, - suggestedParams, - appIndex, - appOnComplete: OnApplicationComplete.ClearStateOC, + appId, + onComplete: OnApplicationComplete.ClearStateOC, appArgs, - appAccounts: accounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts, + foreignApps, + foreignAssets, boxes, note, lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeApplicationClearStateTxn, instead accepting an argument object -export function makeApplicationClearStateTxnFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'appIndex' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - > - > -) { - return makeApplicationClearStateTxn( - o.sender, - o.suggestedParams, - o.appIndex, - o.appArgs, - o.accounts, - o.foreignApps, - o.foreignAssets, - o.note, - o.lease, - o.rekeyTo, - o.boxes - ); + suggestedParams, + }); } /** * Make a transaction that just calls an application, doing nothing on completion - * @param sender - address of sender - * @param suggestedParams - a dict holding common-to-all-txns args: - * fee - integer fee per byte, in microAlgos. for a flat fee, set flatFee to true - * flatFee - bool optionally set this to true to specify fee as microalgos-per-txn - * If true, txn fee may fall below the ALGORAND_MIN_TX_FEE - * firstValid - integer first protocol round on which this txn is valid - * lastValid - integer last protocol round on which this txn is valid - * genesisHash - string specifies hash genesis block of network in use - * genesisID - string specifies genesis ID of network in use - * @param appIndex - the ID of the app to use - * @param appArgs - Array of Uint8Array, any additional arguments to the application - * @param accounts - Array of Address strings, any additional accounts to supply to the application - * @param foreignApps - Array of int, any other apps used by the application, identified by index - * @param foreignAssets - Array of int, any assets used by the application, identified by index - * @param note - Arbitrary data for sender to store - * @param lease - Lease a transaction - * @param rekeyTo - String representation of the Algorand address that will be used to authorize all future transactions - * @param boxes - Array of BoxReference, app ID and name of box to be accessed + * + * @param options - Application no-op transaction parameters */ -export function makeApplicationNoOpTxn( - sender: AppNoOpTxn['sender'], - suggestedParams: MustHaveSuggestedParams['suggestedParams'], - appIndex: AppNoOpTxn['appIndex'], - appArgs?: AppNoOpTxn['appArgs'], - accounts?: AppNoOpTxn['appAccounts'], - foreignApps?: AppNoOpTxn['appForeignApps'], - foreignAssets?: AppNoOpTxn['appForeignAssets'], - note?: AppNoOpTxn['note'], - lease?: AppNoOpTxn['lease'], - rekeyTo?: AppNoOpTxn['rekeyTo'], - boxes?: AppNoOpTxn['boxes'] -) { - const o: AppNoOpTxn = { - type: TransactionType.appl, +export function makeApplicationNoOpTxnFromObject({ + sender, + appId, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + note, + lease, + rekeyTo, + suggestedParams, +}: Omit< + ApplicationCallTransactionParams, + | 'onComplete' + | 'numLocalInts' + | 'numLocalByteSlices' + | 'numGlobalInts' + | 'numGlobalByteSlices' + | 'extraPages' + | 'approvalProgram' + | 'clearProgram' +> & + CommonTransactionParams): Transaction { + if (!appId) { + throw Error('appId must be provided'); + } + return makeApplicationCallTxnFromObject({ sender, - suggestedParams, - appIndex, - appOnComplete: OnApplicationComplete.NoOpOC, + appId, + onComplete: OnApplicationComplete.NoOpOC, appArgs, - appAccounts: accounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts, + foreignApps, + foreignAssets, boxes, note, lease, rekeyTo, - }; - return new txnBuilder.Transaction(o); -} - -// helper for above makeApplicationNoOpTxn, instead accepting an argument object -export function makeApplicationNoOpTxnFromObject( - o: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'appIndex' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - > - > -) { - return makeApplicationNoOpTxn( - o.sender, - o.suggestedParams, - o.appIndex, - o.appArgs, - o.accounts, - o.foreignApps, - o.foreignAssets, - o.note, - o.lease, - o.rekeyTo, - o.boxes - ); -} - -export { OnApplicationComplete } from './types/transactions/base.js'; - -/** - * Generic function for creating any application call transaction. - */ -export function makeApplicationCallTxnFromObject( - options: Expand< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appOnComplete: 'onComplete'; - appAccounts: 'accounts'; - appForeignApps: 'foreignApps'; - appForeignAssets: 'foreignAssets'; - rekeyTo: 'rekeyTo'; - } - >, - | 'sender' - | 'suggestedParams' - | 'appIndex' - | 'onComplete' - | 'appArgs' - | 'accounts' - | 'foreignApps' - | 'foreignAssets' - | 'boxes' - | 'note' - | 'lease' - | 'rekeyTo' - | 'extraPages' - > & - Partial< - Pick< - RenameProperties< - MustHaveSuggestedParams, - { - appApprovalProgram: 'approvalProgram'; - appClearProgram: 'clearProgram'; - appLocalInts: 'numLocalInts'; - appLocalByteSlices: 'numLocalByteSlices'; - appGlobalInts: 'numGlobalInts'; - appGlobalByteSlices: 'numGlobalByteSlices'; - } - >, - | 'approvalProgram' - | 'clearProgram' - | 'numLocalInts' - | 'numLocalByteSlices' - | 'numGlobalInts' - | 'numGlobalByteSlices' - > - > - > -) { - const o: AppCreateTxn = { - type: TransactionType.appl, - sender: options.sender, - suggestedParams: options.suggestedParams, - appIndex: options.appIndex, - appOnComplete: options.onComplete, - appLocalInts: options.numLocalInts, - appLocalByteSlices: options.numLocalByteSlices, - appGlobalInts: options.numGlobalInts, - appGlobalByteSlices: options.numGlobalByteSlices, - appApprovalProgram: options.approvalProgram, - appClearProgram: options.clearProgram, - appArgs: options.appArgs, - appAccounts: options.accounts, - appForeignApps: options.foreignApps, - appForeignAssets: options.foreignAssets, - boxes: options.boxes, - note: options.note, - lease: options.lease, - rekeyTo: options.rekeyTo, - extraPages: options.extraPages, - }; - return new txnBuilder.Transaction(o); + suggestedParams, + }); } diff --git a/src/multisig.ts b/src/multisig.ts index 5079cb156..9ff0deb76 100644 --- a/src/multisig.ts +++ b/src/multisig.ts @@ -1,11 +1,8 @@ import * as nacl from './nacl/naclWrappers.js'; import * as address from './encoding/address.js'; import * as encoding from './encoding/encoding.js'; -import * as txnBuilder from './transaction.js'; +import { Transaction } from './transaction.js'; import * as utils from './utils/utils.js'; -import AnyTransaction, { - EncodedTransaction, -} from './types/transactions/index.js'; import { MultisigMetadata } from './types/multisig.js'; import { EncodedMultisig, @@ -52,7 +49,7 @@ interface MultisigMetadataWithPks extends Omit { * @returns encoded multisig blob */ export function createMultisigTransaction( - txn: txnBuilder.Transaction, + txn: Transaction, { version, threshold, addrs }: MultisigMetadata ) { // construct the appendable multisigned transaction format @@ -77,10 +74,10 @@ export function createMultisigTransaction( threshold, pks, }); - if ( - address.encodeAddress(txnForEncoding.snd) !== - address.encodeAddress(msigAddr) - ) { + const senderAddr = txnForEncoding.snd + ? address.encodeAddress(txnForEncoding.snd) + : address.ALGORAND_ZERO_ADDRESS_STRING; + if (senderAddr !== address.encodeAddress(msigAddr)) { signedTxn.sgnr = msigAddr; } @@ -98,10 +95,10 @@ export function createMultisigTransaction( * @returns encoded multisig blob */ function createMultisigTransactionWithSignature( - txn: txnBuilder.Transaction, + txn: Transaction, { rawSig, myPk }: MultisigOptions, { version, threshold, pks }: MultisigMetadataWithPks -) { +): Uint8Array { // Create an empty encoded multisig transaction const encodedMsig = createMultisigTransaction(txn, { version, @@ -119,7 +116,7 @@ function createMultisigTransactionWithSignature( signedTxn.msig!.subsig[i].s = rawSig; } }); - if (keyExist === false) { + if (!keyExist) { throw new Error(MULTISIG_KEY_NOT_EXIST_ERROR_MSG); } @@ -130,9 +127,10 @@ function createMultisigTransactionWithSignature( threshold, pks, }); - if ( - address.encodeAddress(signedTxn.txn.snd) !== address.encodeAddress(msigAddr) - ) { + const senderAddr = signedTxn.txn.snd + ? address.encodeAddress(signedTxn.txn.snd) + : address.ALGORAND_ZERO_ADDRESS_STRING; + if (senderAddr !== address.encodeAddress(msigAddr)) { signedTxn.sgnr = msigAddr; } @@ -140,87 +138,55 @@ function createMultisigTransactionWithSignature( } /** - * MultisigTransaction is a Transaction that also supports creating partially-signed multisig transactions. + * partialSignTxn partially signs this transaction and returns a partially-signed multisig transaction, + * encoded with msgpack as a typed array. + * @param transaction - The transaction to sign + * @param version - multisig version + * @param threshold - multisig threshold + * @param pks - multisig public key list, order is important. + * @param sk - an Algorand secret key to sign with. + * @returns an encoded, partially signed multisig transaction. */ -export class MultisigTransaction extends txnBuilder.Transaction { - /* eslint-disable class-methods-use-this,@typescript-eslint/no-unused-vars,no-dupe-class-members */ - /** - * Override inherited method to throw an error, as mutating transactions are prohibited in this context - */ - addLease() { - throw new Error(MULTISIG_NO_MUTATE_ERROR_MSG); - } - - /** - * Override inherited method to throw an error, as mutating transactions are prohibited in this context - */ - addRekey() { - throw new Error(MULTISIG_NO_MUTATE_ERROR_MSG); - } - - /** - * Override inherited method to throw an error, as traditional signing is not allowed - */ - signTxn(sk: Uint8Array): Uint8Array; // This overload ensures that the override has a compatible type definition with the parent method - signTxn(sk: any): any { - throw new Error(MULTISIG_USE_PARTIAL_SIGN_ERROR_MSG); - } - /* eslint-enable class-methods-use-this,@typescript-eslint/no-unused-vars,no-dupe-class-members */ - - /** - * partialSignTxn partially signs this transaction and returns a partially-signed multisig transaction, - * encoded with msgpack as a typed array. - * @param version - multisig version - * @param threshold - multisig threshold - * @param pks - multisig public key list, order is important. - * @param sk - an Algorand secret key to sign with. - * @returns an encoded, partially signed multisig transaction. - */ - partialSignTxn( - { version, threshold, pks }: MultisigMetadataWithPks, - sk: Uint8Array - ) { - // get signature verifier - const myPk = nacl.keyPairFromSecretKey(sk).publicKey; - return createMultisigTransactionWithSignature( - this, - { rawSig: this.rawSignTxn(sk), myPk }, - { version, threshold, pks } - ); - } - - /** - * partialSignWithMultisigSignature partially signs this transaction with an external raw multisig signature and returns - * a partially-signed multisig transaction, encoded with msgpack as a typed array. - * @param metadata - multisig metadata - * @param signerAddr - address of the signer - * @param signature - raw multisig signature - * @returns an encoded, partially signed multisig transaction. - */ - partialSignWithMultisigSignature( - metadata: MultisigMetadataWithPks, - signerAddr: string, - signature: Uint8Array - ) { - if (!nacl.isValidSignatureLength(signature.length)) { - throw new Error(MULTISIG_SIGNATURE_LENGTH_ERROR_MSG); - } - return createMultisigTransactionWithSignature( - this, - { - rawSig: signature, - myPk: address.decodeAddress(signerAddr).publicKey, - }, - metadata - ); - } +function partialSignTxn( + transaction: Transaction, + { version, threshold, pks }: MultisigMetadataWithPks, + sk: Uint8Array +) { + // get signature verifier + const myPk = nacl.keyPairFromSecretKey(sk).publicKey; + return createMultisigTransactionWithSignature( + transaction, + { rawSig: transaction.rawSignTxn(sk), myPk }, + { version, threshold, pks } + ); +} - // eslint-disable-next-line camelcase - static from_obj_for_encoding( - txnForEnc: EncodedTransaction - ): MultisigTransaction { - return super.from_obj_for_encoding(txnForEnc) as MultisigTransaction; +/** + * partialSignWithMultisigSignature partially signs this transaction with an external raw multisig signature and returns + * a partially-signed multisig transaction, encoded with msgpack as a typed array. + * @param transaction - The transaction to sign + * @param metadata - multisig metadata + * @param signerAddr - address of the signer + * @param signature - raw multisig signature + * @returns an encoded, partially signed multisig transaction. + */ +function partialSignWithMultisigSignature( + transaction: Transaction, + metadata: MultisigMetadataWithPks, + signerAddr: string, + signature: Uint8Array +) { + if (!nacl.isValidSignatureLength(signature.length)) { + throw new Error(MULTISIG_SIGNATURE_LENGTH_ERROR_MSG); } + return createMultisigTransactionWithSignature( + transaction, + { + rawSig: signature, + myPk: address.decodeAddress(signerAddr).publicKey, + }, + metadata + ); } /** @@ -240,9 +206,7 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { 'Invalid multisig transaction, multisig structure missing at index 0' ); } - const refTxID = MultisigTransaction.from_obj_for_encoding( - refSigTx.txn - ).txID(); + const refTxID = Transaction.from_obj_for_encoding(refSigTx.txn).txID(); const refAuthAddr = refSigTx.sgnr ? address.encodeAddress(refSigTx.sgnr) : undefined; @@ -266,7 +230,7 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { ); } - const unisigAlgoTxn = MultisigTransaction.from_obj_for_encoding(unisig.txn); + const unisigAlgoTxn = Transaction.from_obj_for_encoding(unisig.txn); if (unisigAlgoTxn.txID() !== refTxID) { throw new Error(MULTISIG_MERGE_MISMATCH_ERROR_MSG); } @@ -382,39 +346,15 @@ export function verifyMultisig( * If the final calculated fee is lower than the protocol minimum fee, the fee will be increased to match the minimum. */ export function signMultisigTransaction( - txn: txnBuilder.TransactionLike, + txn: Transaction, { version, threshold, addrs }: MultisigMetadata, sk: Uint8Array ) { - // check that the from field matches the mSigPreImage. If from field is not populated, fill it in. - const expectedFromRaw = address.fromMultisigPreImgAddrs({ - version, - threshold, - addrs, - }); - if (!Object.prototype.hasOwnProperty.call(txn, 'sender')) { - // eslint-disable-next-line no-param-reassign - txn.sender = expectedFromRaw; - } // build pks for partialSign const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey); - // `txn` needs to be handled differently if it's a constructed `Transaction` vs a dict of constructor args - const txnAlreadyBuilt = txn instanceof txnBuilder.Transaction; - let algoTxn: MultisigTransaction; - let blob: Uint8Array; - if (txnAlreadyBuilt) { - algoTxn = txn as unknown as MultisigTransaction; - blob = MultisigTransaction.prototype.partialSignTxn.call( - algoTxn, - { version, threshold, pks }, - sk - ); - } else { - algoTxn = new MultisigTransaction(txn as AnyTransaction); - blob = algoTxn.partialSignTxn({ version, threshold, pks }, sk); - } + const blob = partialSignTxn(txn, { version, threshold, pks }, sk); return { - txID: algoTxn.txID().toString(), + txID: txn.txID(), blob, }; } @@ -440,13 +380,14 @@ export function appendSignMultisigTransaction( const multisigTxObj = encoding.decode( multisigTxnBlob ) as EncodedSignedTransaction; - const msigTxn = MultisigTransaction.from_obj_for_encoding(multisigTxObj.txn); - const partialSignedBlob = msigTxn.partialSignTxn( + const msigTxn = Transaction.from_obj_for_encoding(multisigTxObj.txn); + const partialSignedBlob = partialSignTxn( + msigTxn, { version, threshold, pks }, sk ); return { - txID: msigTxn.txID().toString(), + txID: msigTxn.txID(), blob: mergeMultisigTransactions([multisigTxnBlob, partialSignedBlob]), }; } @@ -473,14 +414,15 @@ export function appendSignRawMultisigSignature( const multisigTxObj = encoding.decode( multisigTxnBlob ) as EncodedSignedTransaction; - const msigTxn = MultisigTransaction.from_obj_for_encoding(multisigTxObj.txn); - const partialSignedBlob = msigTxn.partialSignWithMultisigSignature( + const msigTxn = Transaction.from_obj_for_encoding(multisigTxObj.txn); + const partialSignedBlob = partialSignWithMultisigSignature( + msigTxn, { version, threshold, pks }, signerAddr, signature ); return { - txID: msigTxn.txID().toString(), + txID: msigTxn.txID(), blob: mergeMultisigTransactions([multisigTxnBlob, partialSignedBlob]), }; } diff --git a/src/transaction.ts b/src/transaction.ts index a2e2e3537..8b06ab6e7 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -6,123 +6,48 @@ import { base64ToBytes, bytesToBase64 } from './encoding/binarydata.js'; import * as encoding from './encoding/encoding.js'; import * as nacl from './nacl/naclWrappers.js'; import { Address } from './types/address.js'; -import AnyTransaction, { +import { EncodedLogicSig, EncodedMultisig, EncodedSignedTransaction, EncodedTransaction, - MustHaveSuggestedParams, - MustHaveSuggestedParamsInline, + EncodedAssetParams, + EncodedLocalStateSchema, + EncodedGlobalStateSchema, } from './types/transactions/index.js'; import { + SuggestedParams, BoxReference, OnApplicationComplete, TransactionParams, TransactionType, isTransactionType, + PaymentTransactionParams, + AssetConfigurationTransactionParams, + AssetTransferTransactionParams, + AssetFreezeTransactionParams, + KeyRegistrationTransactionParams, + ApplicationCallTransactionParams, + StateProofTransactionParams, } from './types/transactions/base.js'; import * as utils from './utils/utils.js'; const ALGORAND_TRANSACTION_LENGTH = 52; export const ALGORAND_MIN_TX_FEE = 1000; // version v5 const ALGORAND_TRANSACTION_LEASE_LENGTH = 32; -const ALGORAND_MAX_ASSET_DECIMALS = 19; const NUM_ADDL_BYTES_AFTER_SIGNING = 75; // NUM_ADDL_BYTES_AFTER_SIGNING is the number of bytes added to a txn after signing it -const ALGORAND_TRANSACTION_LEASE_LABEL_LENGTH = 5; -const ALGORAND_TRANSACTION_ADDRESS_LENGTH = 32; -const ALGORAND_TRANSACTION_REKEY_LABEL_LENGTH = 5; const ASSET_METADATA_HASH_LENGTH = 32; const KEYREG_VOTE_KEY_LENGTH = 32; const KEYREG_SELECTION_KEY_LENGTH = 32; const KEYREG_STATE_PROOF_KEY_LENGTH = 64; +const ALGORAND_TRANSACTION_GROUP_LENGTH = 32; -type AnyTransactionWithParams = MustHaveSuggestedParams; -type AnyTransactionWithParamsInline = - MustHaveSuggestedParamsInline; - -/** - * A modified version of the transaction params. Represents the internal structure that the Transaction class uses - * to store inputted transaction objects. - */ -// Omit allows overwriting properties -interface TransactionStorageStructure - extends Omit< - TransactionParams, - | 'sender' - | 'receiver' - | 'genesisHash' - | 'closeRemainderTo' - | 'voteKey' - | 'selectionKey' - | 'stateProofKey' - | 'assetManager' - | 'assetReserve' - | 'assetFreeze' - | 'assetClawback' - | 'assetSender' - | 'freezeAccount' - | 'appAccounts' - | 'suggestedParams' - | 'rekeyTo' - > { - sender: string | Address; - receiver: string | Address; - fee: number; - amount: number | bigint; - firstValid: number; - lastValid: number; - note?: Uint8Array; - genesisID: string; - genesisHash: string | Uint8Array; - lease?: Uint8Array; - closeRemainderTo?: string | Address; - voteKey: string | Uint8Array; - selectionKey: string | Uint8Array; - stateProofKey: string | Uint8Array; - voteFirst: number; - voteLast: number; - voteKeyDilution: number; - assetIndex: number; - assetTotal: number | bigint; - assetDecimals: number; - assetDefaultFrozen: boolean; - assetManager: string | Address; - assetReserve: string | Address; - assetFreeze: string | Address; - assetClawback: string | Address; - assetUnitName: string; - assetName: string; - assetURL: string; - assetMetadataHash?: string | Uint8Array; - freezeAccount: string | Address; - assetFrozen: boolean; - assetSender?: string | Address; - appIndex: number; - appOnComplete: OnApplicationComplete; - appLocalInts: number; - appLocalByteSlices: number; - appGlobalInts: number; - appGlobalByteSlices: number; - appApprovalProgram: Uint8Array; - appClearProgram: Uint8Array; - appArgs?: Uint8Array[]; - appAccounts?: string[] | Address[]; - appForeignApps?: number[]; - appForeignAssets?: number[]; - type?: TransactionType; - flatFee: boolean; - rekeyTo?: string | Address; - nonParticipation?: boolean; - group?: Uint8Array; - extraPages?: number; - boxes?: BoxReference[]; - stateProofType?: number | bigint; - stateProof?: Uint8Array; - stateProofMessage?: Uint8Array; +function uint8ArrayIsEmpty(input: Uint8Array): boolean { + return input.every((value) => value === 0); } function getKeyregKey( - input: undefined | string | Uint8Array | Uint8Array, + input: undefined | string | Uint8Array, inputName: string, length: number ): Uint8Array | undefined { @@ -130,990 +55,896 @@ function getKeyregKey( return undefined; } - let inputAsBuffer: Uint8Array | undefined; + let inputBytes: Uint8Array | undefined; if (typeof input === 'string') { - inputAsBuffer = base64ToBytes(input); - } else if (input.constructor === Uint8Array) { - inputAsBuffer = input; + inputBytes = base64ToBytes(input); + } else if (input instanceof Uint8Array) { + inputBytes = input; } - if (inputAsBuffer == null || inputAsBuffer.byteLength !== length) { + if (inputBytes == null || inputBytes.byteLength !== length) { throw Error( `${inputName} must be a ${length} byte Uint8Array or base64 string.` ); } - return inputAsBuffer; + return inputBytes; +} + +function ensureAddress(input: unknown): Address { + if (input == null) { + throw new Error('Address must not be null or undefined'); + } + if (typeof input === 'string') { + return address.decodeAddress(input); + } + if ( + typeof input === 'object' && + (input as Record).publicKey instanceof Uint8Array && + (input as Record).checksum instanceof Uint8Array + ) { + return input as Address; + } + throw new Error(`Not an address: ${input}`); +} + +function optionalAddress(input: unknown): Address | undefined { + if (input == null) { + return undefined; + } + let addr: Address; + if ( + typeof input === 'object' && + (input as Record).publicKey instanceof Uint8Array && + (input as Record).checksum instanceof Uint8Array + ) { + addr = input as Address; + } else if (typeof input === 'string') { + addr = address.decodeAddress(input); + } else { + throw new Error(`Not an address: ${input}`); + } + if (uint8ArrayIsEmpty(addr.publicKey)) { + // If it's the zero address, throw an error so that the user won't be surprised that this gets dropped + throw new Error( + 'Invalid use of the zero address. To omit this value, pass in undefined' + ); + } + return addr; +} + +function optionalUint8Array(input: unknown): Uint8Array | undefined { + if (typeof input === 'undefined') { + return undefined; + } + if (input instanceof Uint8Array) { + return input; + } + throw new Error(`Not a Uint8Array: ${input}`); +} + +function ensureUint8Array(input: unknown): Uint8Array { + if (input instanceof Uint8Array) { + return input; + } + throw new Error(`Not a Uint8Array: ${input}`); +} + +function optionalUint64(input: unknown): bigint | undefined { + if (typeof input === 'undefined') { + return undefined; + } + return utils.ensureUint64(input); +} + +function ensureBoolean(input: unknown): boolean { + if (input === true || input === false) { + return input; + } + throw new Error(`Not a boolean: ${input}`); +} + +function ensureArray(input: unknown): unknown[] { + if (Array.isArray(input)) { + return input.slice(); + } + throw new Error(`Not an array: ${input}`); +} + +function optionalFixedLengthByteArray( + input: unknown, + length: number, + name: string +): Uint8Array | undefined { + const bytes = optionalUint8Array(input); + if (typeof bytes === 'undefined') { + return undefined; + } + if (bytes.byteLength !== length) { + throw new Error( + `${name} must be ${length} bytes long, was ${bytes.byteLength}` + ); + } + if (uint8ArrayIsEmpty(bytes)) { + // if contains all 0s, omit it + return undefined; + } + return bytes; +} + +interface TransactionBoxReference { + readonly appIndex: bigint; + readonly name: Uint8Array; +} + +function ensureBoxReference(input: unknown): TransactionBoxReference { + if (input != null && typeof input === 'object') { + const { appIndex, name } = input as BoxReference; + return { + appIndex: utils.ensureUint64(appIndex), + name: ensureUint8Array(name), + }; + } + throw new Error(`Not a box reference: ${input}`); +} + +const TX_TAG = new TextEncoder().encode('TX'); + +interface PaymentTransactionFields { + readonly receiver: Address; + readonly amount: bigint; + readonly closeRemainderTo?: Address; +} + +interface KeyRegistrationTransactionFields { + readonly voteKey?: Uint8Array; + readonly selectionKey?: Uint8Array; + readonly stateProofKey?: Uint8Array; + readonly voteFirst?: bigint; + readonly voteLast?: bigint; + readonly voteKeyDilution?: bigint; + readonly nonParticipation: boolean; +} + +interface AssetConfigTransactionFields { + readonly assetId: bigint; + readonly assetTotal: bigint; + readonly assetDecimals: number; + readonly assetDefaultFrozen: boolean; + readonly assetManager?: Address; + readonly assetReserve?: Address; + readonly assetFreeze?: Address; + readonly assetClawback?: Address; + readonly assetUnitName?: string; + readonly assetName?: string; + readonly assetURL?: string; + readonly assetMetadataHash?: Uint8Array; +} + +interface AssetTransferTransactionFields { + readonly assetId: bigint; + readonly amount: bigint; + readonly sender?: Address; + readonly receiver: Address; + readonly closeRemainderTo?: Address; +} + +interface AssetFreezeTransactionFields { + readonly assetId: bigint; + readonly freezeAccount: Address; + readonly assetFrozen: boolean; +} + +interface ApplicationTransactionFields { + readonly appId: bigint; + readonly appOnComplete: OnApplicationComplete; + readonly appLocalInts: number; + readonly appLocalByteSlices: number; + readonly appGlobalInts: number; + readonly appGlobalByteSlices: number; + readonly extraPages: number; + readonly appApprovalProgram: Uint8Array; + readonly appClearProgram: Uint8Array; + readonly appArgs: ReadonlyArray; + readonly appAccounts: ReadonlyArray
; + readonly appForeignApps: ReadonlyArray; + readonly appForeignAssets: ReadonlyArray; + readonly boxes: ReadonlyArray; +} + +interface StateProofTransactionFields { + readonly stateProofType: number; + readonly stateProof: Uint8Array; + readonly stateProofMessage: Uint8Array; } /** * Transaction enables construction of Algorand transactions * */ -export class Transaction implements TransactionStorageStructure { - name = 'Transaction'; - tag = new TextEncoder().encode('TX'); - - // Implement transaction params - sender: Address; - receiver: Address; - fee: number; - amount: number | bigint; - firstValid: number; - lastValid: number; - note?: Uint8Array; - genesisID: string; - genesisHash: Uint8Array; - lease?: Uint8Array; - closeRemainderTo?: Address; - voteKey: Uint8Array; - selectionKey: Uint8Array; - stateProofKey: Uint8Array; - voteFirst: number; - voteLast: number; - voteKeyDilution: number; - assetIndex: number; - assetTotal: number | bigint; - assetDecimals: number; - assetDefaultFrozen: boolean; - assetManager: Address; - assetReserve: Address; - assetFreeze: Address; - assetClawback: Address; - assetUnitName: string; - assetName: string; - assetURL: string; - assetMetadataHash?: Uint8Array; - freezeAccount: Address; - assetFrozen: boolean; - assetSender?: Address; - appIndex: number; - appOnComplete: OnApplicationComplete; - appLocalInts: number; - appLocalByteSlices: number; - appGlobalInts: number; - appGlobalByteSlices: number; - appApprovalProgram: Uint8Array; - appClearProgram: Uint8Array; - appArgs?: Uint8Array[]; - appAccounts?: Address[]; - appForeignApps?: number[]; - appForeignAssets?: number[]; - boxes?: BoxReference[]; - type?: TransactionType; - flatFee: boolean; - rekeyTo?: Address; - nonParticipation?: boolean; - group?: Uint8Array; - extraPages?: number; - stateProofType?: number | bigint; - stateProof?: Uint8Array; - stateProofMessage?: Uint8Array; - - constructor({ ...transaction }: AnyTransaction) { - // Populate defaults - /* eslint-disable no-param-reassign */ - const defaults: Partial = { - type: TransactionType.pay, - flatFee: false, - nonParticipation: false, - }; - // Default type - if (typeof transaction.type === 'undefined') { - transaction.type = defaults.type; - } - // Default flatFee - if ( - typeof (transaction as AnyTransactionWithParamsInline).flatFee === - 'undefined' - ) { - (transaction as AnyTransactionWithParamsInline).flatFee = - defaults.flatFee; - } - // Default nonParticipation - if ( - transaction.type === TransactionType.keyreg && - typeof transaction.voteKey !== 'undefined' && - typeof transaction.nonParticipation === 'undefined' - ) { - transaction.nonParticipation = defaults.nonParticipation; - } - /* eslint-enable no-param-reassign */ +export class Transaction { + /** common */ + public readonly type: TransactionType; + public readonly sender: Address; + public readonly note: Uint8Array; + public readonly lease?: Uint8Array; + public readonly rekeyTo?: Address; - // Move suggested parameters from its object to inline - if ( - (transaction as AnyTransactionWithParams).suggestedParams !== undefined - ) { - // Create a temporary reference to the transaction object that has params inline and also as a suggested params object - // - Helpful for moving params from named object to inline - const reference = transaction as AnyTransactionWithParams & - AnyTransactionWithParamsInline; - reference.genesisHash = reference.suggestedParams.genesisHash; - reference.fee = reference.suggestedParams.fee; - if (reference.suggestedParams.flatFee !== undefined) - reference.flatFee = reference.suggestedParams.flatFee; - reference.firstValid = reference.suggestedParams.firstValid; - reference.lastValid = reference.suggestedParams.lastValid; - reference.genesisID = reference.suggestedParams.genesisID; - } + /** group */ + public group: Uint8Array; - // At this point all suggestedParams have been moved to be inline, so we can reassign the transaction object type - // to one which is more useful as we prepare properties for storing - const txn = transaction as TransactionStorageStructure; + /** suggested params */ + public fee: bigint; + public readonly firstValid: bigint; + public readonly lastValid: bigint; + public readonly genesisID?: string; + public readonly genesisHash: Uint8Array; - txn.sender = address.decodeAddress(txn.sender as string); - if (txn.receiver !== undefined) - txn.receiver = address.decodeAddress(txn.receiver as string); - if (txn.closeRemainderTo !== undefined) - txn.closeRemainderTo = address.decodeAddress( - txn.closeRemainderTo as string - ); - if (txn.assetManager !== undefined) - txn.assetManager = address.decodeAddress(txn.assetManager as string); - if (txn.assetReserve !== undefined) - txn.assetReserve = address.decodeAddress(txn.assetReserve as string); - if (txn.assetFreeze !== undefined) - txn.assetFreeze = address.decodeAddress(txn.assetFreeze as string); - if (txn.assetClawback !== undefined) - txn.assetClawback = address.decodeAddress(txn.assetClawback as string); - if (txn.assetSender !== undefined) - txn.assetSender = address.decodeAddress(txn.assetSender as string); - if (txn.freezeAccount !== undefined) - txn.freezeAccount = address.decodeAddress(txn.freezeAccount as string); - if (txn.rekeyTo !== undefined) - txn.rekeyTo = address.decodeAddress(txn.rekeyTo as string); - if (txn.genesisHash === undefined) - throw Error('genesis hash must be specified and in a base64 string.'); - - txn.genesisHash = base64ToBytes(txn.genesisHash as string); + /** type-specific fields */ + public readonly payment?: PaymentTransactionFields; + public readonly keyreg?: KeyRegistrationTransactionFields; + public readonly assetConfig?: AssetConfigTransactionFields; + public readonly assetTransfer?: AssetTransferTransactionFields; + public readonly assetFreeze?: AssetFreezeTransactionFields; + public readonly applicationCall?: ApplicationTransactionFields; + public readonly stateProof?: StateProofTransactionFields; - if ( - txn.amount !== undefined && - (!( - Number.isSafeInteger(txn.amount) || - (typeof txn.amount === 'bigint' && - txn.amount <= BigInt('0xffffffffffffffff')) - ) || - txn.amount < 0) - ) - throw Error( - 'Amount must be a positive number and smaller than 2^64-1. If the number is larger than 2^53-1, use bigint.' - ); - if (!Number.isSafeInteger(txn.fee) || txn.fee < 0) - throw Error('fee must be a positive number and smaller than 2^53-1'); - if (!Number.isSafeInteger(txn.firstValid) || txn.firstValid < 0) - throw Error('firstValid must be a positive number'); - if (!Number.isSafeInteger(txn.lastValid) || txn.lastValid < 0) - throw Error('lastValid must be a positive number'); - if ( - txn.extraPages !== undefined && - (!Number.isInteger(txn.extraPages) || - txn.extraPages < 0 || - txn.extraPages > 3) - ) - throw Error('extraPages must be an Integer between and including 0 to 3'); - if ( - txn.assetTotal !== undefined && - (!( - Number.isSafeInteger(txn.assetTotal) || - (typeof txn.assetTotal === 'bigint' && - txn.assetTotal <= BigInt('0xffffffffffffffff')) - ) || - txn.assetTotal < 0) - ) - throw Error( - 'Total asset issuance must be a positive number and smaller than 2^64-1. If the number is larger than 2^53-1, use bigint.' - ); - if ( - txn.assetDecimals !== undefined && - (!Number.isSafeInteger(txn.assetDecimals) || - txn.assetDecimals < 0 || - txn.assetDecimals > ALGORAND_MAX_ASSET_DECIMALS) - ) - throw Error( - `assetDecimals must be a positive number and smaller than ${ALGORAND_MAX_ASSET_DECIMALS.toString()}` - ); - if ( - txn.assetIndex !== undefined && - (!Number.isSafeInteger(txn.assetIndex) || txn.assetIndex < 0) - ) - throw Error( - 'Asset index must be a positive number and smaller than 2^53-1' - ); - if ( - txn.appIndex !== undefined && - (!Number.isSafeInteger(txn.appIndex) || txn.appIndex < 0) - ) - throw Error( - 'Application index must be a positive number and smaller than 2^53-1' - ); - if ( - txn.appLocalInts !== undefined && - (!Number.isSafeInteger(txn.appLocalInts) || txn.appLocalInts < 0) - ) - throw Error( - 'Application local ints count must be a positive number and smaller than 2^53-1' - ); - if ( - txn.appLocalByteSlices !== undefined && - (!Number.isSafeInteger(txn.appLocalByteSlices) || - txn.appLocalByteSlices < 0) - ) - throw Error( - 'Application local byte slices count must be a positive number and smaller than 2^53-1' - ); - if ( - txn.appGlobalInts !== undefined && - (!Number.isSafeInteger(txn.appGlobalInts) || txn.appGlobalInts < 0) - ) - throw Error( - 'Application global ints count must be a positive number and smaller than 2^53-1' - ); - if ( - txn.appGlobalByteSlices !== undefined && - (!Number.isSafeInteger(txn.appGlobalByteSlices) || - txn.appGlobalByteSlices < 0) - ) - throw Error( - 'Application global byte slices count must be a positive number and smaller than 2^53-1' - ); - if (txn.appApprovalProgram !== undefined) { - if (txn.appApprovalProgram.constructor !== Uint8Array) - throw Error('appApprovalProgram must be a Uint8Array.'); + constructor(params: TransactionParams) { + if (!isTransactionType(params.type)) { + throw new Error(`Invalid transaction type: ${params.type}`); } - if (txn.appClearProgram !== undefined) { - if (txn.appClearProgram.constructor !== Uint8Array) - throw Error('appClearProgram must be a Uint8Array.'); + + // Common fields + this.type = params.type; // verified above + this.sender = ensureAddress(params.sender); + this.note = ensureUint8Array(params.note ?? new Uint8Array()); + this.lease = optionalFixedLengthByteArray( + params.lease, + ALGORAND_TRANSACTION_LEASE_LENGTH, + 'lease' + ); + this.rekeyTo = optionalAddress(params.rekeyTo); + + // Group + this.group = new Uint8Array(); + + // Suggested params fields + this.firstValid = utils.ensureUint64(params.suggestedParams.firstValid); + this.lastValid = utils.ensureUint64(params.suggestedParams.lastValid); + if (params.suggestedParams.genesisID) { + if (typeof params.suggestedParams.genesisID !== 'string') { + throw new Error('Genesis ID must be a string if present'); + } + this.genesisID = params.suggestedParams.genesisID; } - if (txn.appArgs !== undefined) { - if (!Array.isArray(txn.appArgs)) - throw Error('appArgs must be an Array of Uint8Array.'); - txn.appArgs = txn.appArgs.slice(); - txn.appArgs.forEach((arg) => { - if (arg.constructor !== Uint8Array) - throw Error('each element of AppArgs must be a Uint8Array.'); - }); - } else { - txn.appArgs = []; + if (!params.suggestedParams.genesisHash) { + throw new Error('Genesis hash must be specified'); } - if (txn.appAccounts !== undefined) { - if (!Array.isArray(txn.appAccounts)) - throw Error('appAccounts must be an Array of addresses.'); - txn.appAccounts = txn.appAccounts.map((addressAsString) => - address.decodeAddress(addressAsString) + this.genesisHash = base64ToBytes(params.suggestedParams.genesisHash); + // Fee is handled at the end + + const fieldsPresent: TransactionType[] = []; + if (params.paymentParams) fieldsPresent.push(TransactionType.pay); + if (params.keyregParams) fieldsPresent.push(TransactionType.keyreg); + if (params.assetConfigParams) fieldsPresent.push(TransactionType.acfg); + if (params.assetTransferParams) fieldsPresent.push(TransactionType.axfer); + if (params.assetFreezeParams) fieldsPresent.push(TransactionType.afrz); + if (params.appCallParams) fieldsPresent.push(TransactionType.appl); + if (params.stateProofParams) fieldsPresent.push(TransactionType.stpf); + + if (fieldsPresent.length !== 1) { + throw new Error( + `Transaction has wrong number of type fields present (${fieldsPresent.length}): ${fieldsPresent}` ); } - if (txn.appForeignApps !== undefined) { - if (!Array.isArray(txn.appForeignApps)) - throw Error('appForeignApps must be an Array of integers.'); - txn.appForeignApps = txn.appForeignApps.slice(); - txn.appForeignApps.forEach((foreignAppIndex) => { - if (!Number.isSafeInteger(foreignAppIndex) || foreignAppIndex < 0) - throw Error( - 'each foreign application index must be a positive number and smaller than 2^53-1' - ); - }); - } - if (txn.appForeignAssets !== undefined) { - if (!Array.isArray(txn.appForeignAssets)) - throw Error('appForeignAssets must be an Array of integers.'); - txn.appForeignAssets = txn.appForeignAssets.slice(); - txn.appForeignAssets.forEach((foreignAssetIndex) => { - if (!Number.isSafeInteger(foreignAssetIndex) || foreignAssetIndex < 0) - throw Error( - 'each foreign asset index must be a positive number and smaller than 2^53-1' - ); - }); + + if (this.type !== fieldsPresent[0]) { + throw new Error( + `Transaction has type ${this.type} but fields present for ${fieldsPresent[0]}` + ); } - if (txn.boxes !== undefined) { - if (!Array.isArray(txn.boxes)) - throw Error('boxes must be an Array of BoxReference.'); - txn.boxes = txn.boxes.slice(); - txn.boxes.forEach((box) => { - if ( - !Number.isSafeInteger(box.appIndex) || - box.name.constructor !== Uint8Array - ) - throw Error( - 'box app index must be a number and name must be an Uint8Array.' - ); - }); + + if (params.paymentParams) { + this.payment = { + receiver: ensureAddress(params.paymentParams.receiver), + amount: utils.ensureUint64(params.paymentParams.amount), + closeRemainderTo: optionalAddress( + params.paymentParams.closeRemainderTo + ), + }; } - if ( - txn.assetMetadataHash !== undefined && - txn.assetMetadataHash.length !== 0 - ) { - if (typeof txn.assetMetadataHash === 'string') { - txn.assetMetadataHash = new Uint8Array( - new TextEncoder().encode(txn.assetMetadataHash) + + if (params.keyregParams) { + this.keyreg = { + voteKey: getKeyregKey( + params.keyregParams.voteKey, + 'voteKey', + KEYREG_VOTE_KEY_LENGTH + )!, + selectionKey: getKeyregKey( + params.keyregParams.selectionKey, + 'selectionKey', + KEYREG_SELECTION_KEY_LENGTH + )!, + stateProofKey: getKeyregKey( + params.keyregParams.stateProofKey, + 'stateProofKey', + KEYREG_STATE_PROOF_KEY_LENGTH + )!, + voteFirst: optionalUint64(params.keyregParams.voteFirst), + voteLast: optionalUint64(params.keyregParams.voteLast), + voteKeyDilution: optionalUint64(params.keyregParams.voteKeyDilution), + nonParticipation: ensureBoolean( + params.keyregParams.nonParticipation ?? false + ), + }; + // Checking non-participation key registration + if ( + this.keyreg.nonParticipation && + (this.keyreg.voteKey || + this.keyreg.selectionKey || + this.keyreg.stateProofKey || + typeof this.keyreg.voteFirst !== 'undefined' || + typeof this.keyreg.voteLast !== 'undefined' || + typeof this.keyreg.voteKeyDilution !== 'undefined') + ) { + throw new Error( + 'nonParticipation is true but participation params are present.' ); } - + // Checking online key registration if ( - txn.assetMetadataHash.constructor !== Uint8Array || - txn.assetMetadataHash.byteLength !== ASSET_METADATA_HASH_LENGTH + // If we are participating + !this.keyreg.nonParticipation && + // And *ANY* participating fields are present + (this.keyreg.voteKey || + this.keyreg.selectionKey || + this.keyreg.stateProofKey || + typeof this.keyreg.voteFirst !== 'undefined' || + typeof this.keyreg.voteLast !== 'undefined' || + typeof this.keyreg.voteKeyDilution !== 'undefined') && + // Then *ALL* participating fields must be present (with an exception for stateProofKey, + // which was introduced later so for backwards compatibility we don't require it) + !( + this.keyreg.voteKey && + this.keyreg.selectionKey && + typeof this.keyreg.voteFirst !== 'undefined' && + typeof this.keyreg.voteLast !== 'undefined' && + typeof this.keyreg.voteKeyDilution !== 'undefined' + ) ) { - throw Error( - `assetMetadataHash must be a ${ASSET_METADATA_HASH_LENGTH} byte Uint8Array or string.` + throw new Error( + `Online key registration missing at least one of the following fields: voteKey, selectionKey, voteFirst, voteLast, voteKeyDilution` ); } + // The last option is an offline key registration where all the fields + // nonParticipation, voteKey, selectionKey, stateProofKey, voteFirst, voteLast, voteKeyDilution + // are all undefined + } - if (txn.assetMetadataHash.every((value) => value === 0)) { - // if hash contains all 0s, omit it - txn.assetMetadataHash = undefined; - } - } else { - txn.assetMetadataHash = undefined; + if (params.assetConfigParams) { + this.assetConfig = { + assetId: utils.ensureUint64(params.assetConfigParams.assetIndex ?? 0), + assetTotal: utils.ensureUint64(params.assetConfigParams.total ?? 0), + assetDecimals: utils.ensureSafeUnsignedInteger( + params.assetConfigParams.decimals ?? 0 + ), + assetDefaultFrozen: ensureBoolean( + params.assetConfigParams.defaultFrozen ?? false + ), + assetManager: optionalAddress(params.assetConfigParams.manager), + assetReserve: optionalAddress(params.assetConfigParams.reserve), + assetFreeze: optionalAddress(params.assetConfigParams.freeze), + assetClawback: optionalAddress(params.assetConfigParams.clawback), + assetUnitName: params.assetConfigParams.unitName ?? '', + assetName: params.assetConfigParams.assetName ?? '', + assetURL: params.assetConfigParams.assetURL ?? '', + assetMetadataHash: optionalFixedLengthByteArray( + params.assetConfigParams.assetMetadataHash, + ASSET_METADATA_HASH_LENGTH, + 'assetMetadataHash' + ), + }; } - if (txn.note !== undefined) { - if (txn.note.constructor !== Uint8Array) - throw Error('note must be a Uint8Array.'); - } else { - txn.note = new Uint8Array(0); + + if (params.assetTransferParams) { + this.assetTransfer = { + assetId: utils.ensureUint64(params.assetTransferParams.assetIndex), + amount: utils.ensureUint64(params.assetTransferParams.amount), + sender: optionalAddress(params.assetTransferParams.assetSender), + receiver: ensureAddress(params.assetTransferParams.receiver), + closeRemainderTo: optionalAddress( + params.assetTransferParams.closeRemainderTo + ), + }; } - if (txn.lease !== undefined) { - if (txn.lease.constructor !== Uint8Array) - throw Error('lease must be a Uint8Array.'); - if (txn.lease.length !== ALGORAND_TRANSACTION_LEASE_LENGTH) - throw Error( - `lease must be of length ${ALGORAND_TRANSACTION_LEASE_LENGTH.toString()}.` - ); - if (txn.lease.every((value) => value === 0)) { - // if lease contains all 0s, omit it - txn.lease = new Uint8Array(0); - } - } else { - txn.lease = new Uint8Array(0); + + if (params.assetFreezeParams) { + this.assetFreeze = { + assetId: utils.ensureUint64(params.assetFreezeParams.assetIndex), + freezeAccount: ensureAddress(params.assetFreezeParams.freezeTarget), + assetFrozen: ensureBoolean(params.assetFreezeParams.assetFrozen), + }; } - txn.voteKey = getKeyregKey(txn.voteKey, 'voteKey', KEYREG_VOTE_KEY_LENGTH); - txn.selectionKey = getKeyregKey( - txn.selectionKey, - 'selectionKey', - KEYREG_SELECTION_KEY_LENGTH - ); - txn.stateProofKey = getKeyregKey( - txn.stateProofKey, - 'stateProofKey', - KEYREG_STATE_PROOF_KEY_LENGTH - ); - // Checking non-participation key registration - if ( - txn.nonParticipation && - (txn.voteKey || - txn.selectionKey || - txn.voteFirst || - txn.stateProofKey || - txn.voteLast || - txn.voteKeyDilution) - ) { - throw new Error( - 'nonParticipation is true but participation params are present.' - ); + + if (params.appCallParams) { + this.applicationCall = { + appId: utils.ensureUint64(params.appCallParams.appId), + appOnComplete: params.appCallParams.onComplete, // TODO: verify + appLocalInts: utils.ensureSafeUnsignedInteger( + params.appCallParams.numLocalInts ?? 0 + ), + appLocalByteSlices: utils.ensureSafeUnsignedInteger( + params.appCallParams.numLocalByteSlices ?? 0 + ), + appGlobalInts: utils.ensureSafeUnsignedInteger( + params.appCallParams.numGlobalInts ?? 0 + ), + appGlobalByteSlices: utils.ensureSafeUnsignedInteger( + params.appCallParams.numGlobalByteSlices ?? 0 + ), + extraPages: utils.ensureSafeUnsignedInteger( + params.appCallParams.extraPages ?? 0 + ), + appApprovalProgram: ensureUint8Array( + params.appCallParams.approvalProgram ?? new Uint8Array() + ), + appClearProgram: ensureUint8Array( + params.appCallParams.clearProgram ?? new Uint8Array() + ), + appArgs: ensureArray(params.appCallParams.appArgs ?? []).map( + ensureUint8Array + ), + appAccounts: ensureArray(params.appCallParams.accounts ?? []).map( + ensureAddress + ), + appForeignApps: ensureArray(params.appCallParams.foreignApps ?? []).map( + utils.ensureUint64 + ), + appForeignAssets: ensureArray( + params.appCallParams.foreignAssets ?? [] + ).map(utils.ensureUint64), + boxes: ensureArray(params.appCallParams.boxes ?? []).map( + ensureBoxReference + ), + }; } - // Checking online key registration - if ( - !txn.nonParticipation && - (txn.voteKey || - txn.selectionKey || - txn.stateProofKey || - txn.voteFirst || - txn.voteLast || - txn.voteKeyDilution) && - !( - txn.voteKey && - txn.selectionKey && - txn.voteFirst && - txn.voteLast && - txn.voteKeyDilution - ) - // stateProofKey not included here for backwards compatibility - ) { - throw new Error( - 'online key registration missing at least one of the following fields: ' + - 'voteKey, selectionKey, voteFirst, voteLast, voteKeyDilution' - ); + + if (params.stateProofParams) { + this.stateProof = { + stateProofType: utils.ensureSafeUnsignedInteger( + params.stateProofParams.stateProofType ?? 0 + ), + stateProof: ensureUint8Array( + params.stateProofParams.stateProof ?? new Uint8Array() + ), + stateProofMessage: ensureUint8Array( + params.stateProofParams.stateProofMessage ?? new Uint8Array() + ), + }; } - // The last option is an offline key registration where all the fields - // nonParticipation, voteKey, selectionKey, voteFirst, voteLast, voteKeyDilution - // are all undefined/false - // Remove unwanted properties and store transaction on instance - delete (txn as unknown as AnyTransactionWithParams).suggestedParams; - Object.assign(this, utils.removeUndefinedProperties(txn)); + // Determine fee + this.fee = utils.ensureUint64(params.suggestedParams.fee); - // Modify Fee - if (!txn.flatFee) { - this.fee *= this.estimateSize(); + const feeDependsOnSize = !ensureBoolean( + params.suggestedParams.flatFee ?? false + ); + if (feeDependsOnSize) { + const minFee = utils.ensureUint64(params.suggestedParams.minFee); + this.fee *= BigInt(this.estimateSize()); // If suggested fee too small and will be rejected, set to min tx fee - if (this.fee < ALGORAND_MIN_TX_FEE) { - this.fee = ALGORAND_MIN_TX_FEE; + if (this.fee < minFee) { + this.fee = minFee; } } - - // say we are aware of groups - this.group = undefined; - - // stpf fields - if ( - txn.stateProofType !== undefined && - (!Number.isSafeInteger(txn.stateProofType) || txn.stateProofType < 0) - ) - throw Error( - 'State Proof type must be a positive number and smaller than 2^53-1' - ); - if (txn.stateProofMessage !== undefined) { - if (txn.stateProofMessage.constructor !== Uint8Array) - throw Error('stateProofMessage must be a Uint8Array.'); - } else { - txn.stateProofMessage = new Uint8Array(0); - } - if (txn.stateProof !== undefined) { - if (txn.stateProof.constructor !== Uint8Array) - throw Error('stateProof must be a Uint8Array.'); - } else { - txn.stateProof = new Uint8Array(0); - } } // eslint-disable-next-line camelcase - get_obj_for_encoding() { - if (this.type === 'pay') { - const txn: EncodedTransaction = { - amt: this.amount, - fee: this.fee, - fv: this.firstValid, - lv: this.lastValid, - note: this.note, - snd: this.sender.publicKey, - type: 'pay', - gen: this.genesisID, - gh: this.genesisHash, - lx: this.lease, - grp: this.group, - }; + get_obj_for_encoding(): EncodedTransaction { + const forEncoding: EncodedTransaction = { + type: this.type, + gh: this.genesisHash, + lv: this.lastValid, + }; + if (!uint8ArrayIsEmpty(this.sender.publicKey)) { + forEncoding.snd = this.sender.publicKey; + } + if (this.genesisID) { + forEncoding.gen = this.genesisID; + } + if (this.fee) { + forEncoding.fee = this.fee; + } + if (this.firstValid) { + forEncoding.fv = this.firstValid; + } + if (this.note.length) { + forEncoding.note = this.note; + } + if (this.lease) { + forEncoding.lx = this.lease; + } + if (this.rekeyTo) { + forEncoding.rekey = this.rekeyTo.publicKey; + } + if (this.group.length) { + forEncoding.grp = this.group; + } - // parse close address - if ( - this.closeRemainderTo !== undefined && - address.encodeAddress(this.closeRemainderTo.publicKey) !== - address.ALGORAND_ZERO_ADDRESS_STRING - ) { - txn.close = this.closeRemainderTo.publicKey; + if (this.payment) { + if (this.payment.amount) { + forEncoding.amt = this.payment.amount; } - if (this.rekeyTo !== undefined) { - txn.rekey = this.rekeyTo.publicKey; + if (!uint8ArrayIsEmpty(this.payment.receiver.publicKey)) { + forEncoding.rcv = this.payment.receiver.publicKey; } - // allowed zero values - if (this.receiver !== undefined) txn.rcv = this.receiver.publicKey; - if (!txn.note.length) delete txn.note; - if (!txn.amt) delete txn.amt; - if (!txn.fee) delete txn.fee; - if (!txn.fv) delete txn.fv; - if (!txn.gen) delete txn.gen; - if (txn.grp === undefined) delete txn.grp; - if (!txn.lx.length) delete txn.lx; - if (!txn.rekey) delete txn.rekey; - return txn; + if (this.payment.closeRemainderTo) { + forEncoding.close = this.payment.closeRemainderTo.publicKey; + } + return forEncoding; } - if (this.type === 'keyreg') { - const txn: EncodedTransaction = { - fee: this.fee, - fv: this.firstValid, - lv: this.lastValid, - note: this.note, - snd: this.sender.publicKey, - type: this.type, - gen: this.genesisID, - gh: this.genesisHash, - lx: this.lease, - grp: this.group, - votekey: this.voteKey, - selkey: this.selectionKey, - sprfkey: this.stateProofKey, - votefst: this.voteFirst, - votelst: this.voteLast, - votekd: this.voteKeyDilution, - }; - // allowed zero values - if (!txn.note.length) delete txn.note; - if (!txn.lx.length) delete txn.lx; - if (!txn.fee) delete txn.fee; - if (!txn.fv) delete txn.fv; - if (!txn.gen) delete txn.gen; - if (txn.grp === undefined) delete txn.grp; - if (this.rekeyTo !== undefined) { - txn.rekey = this.rekeyTo.publicKey; + + if (this.keyreg) { + if (this.keyreg.voteKey) { + forEncoding.votekey = this.keyreg.voteKey; } - if (this.nonParticipation) { - txn.nonpart = true; + if (this.keyreg.selectionKey) { + forEncoding.selkey = this.keyreg.selectionKey; } - if (!txn.selkey) delete txn.selkey; - if (!txn.votekey) delete txn.votekey; - if (!txn.sprfkey) delete txn.sprfkey; - if (!txn.votefst) delete txn.votefst; - if (!txn.votelst) delete txn.votelst; - if (!txn.votekd) delete txn.votekd; - return txn; - } - if (this.type === 'acfg') { - // asset creation, or asset reconfigure, or asset destruction - const txn: EncodedTransaction = { - fee: this.fee, - fv: this.firstValid, - lv: this.lastValid, - note: this.note, - snd: this.sender.publicKey, - type: this.type, - gen: this.genesisID, - gh: this.genesisHash, - lx: this.lease, - grp: this.group, - caid: this.assetIndex, - apar: { - t: this.assetTotal, - df: this.assetDefaultFrozen, - dc: this.assetDecimals, - }, - }; - if (this.assetManager !== undefined) - txn.apar.m = this.assetManager.publicKey; - if (this.assetReserve !== undefined) - txn.apar.r = this.assetReserve.publicKey; - if (this.assetFreeze !== undefined) - txn.apar.f = this.assetFreeze.publicKey; - if (this.assetClawback !== undefined) - txn.apar.c = this.assetClawback.publicKey; - if (this.assetName !== undefined) txn.apar.an = this.assetName; - if (this.assetUnitName !== undefined) txn.apar.un = this.assetUnitName; - if (this.assetURL !== undefined) txn.apar.au = this.assetURL; - if (this.assetMetadataHash !== undefined) - txn.apar.am = this.assetMetadataHash; - - // allowed zero values - if (!txn.note.length) delete txn.note; - if (!txn.lx.length) delete txn.lx; - if (!txn.amt) delete txn.amt; - if (!txn.fee) delete txn.fee; - if (!txn.fv) delete txn.fv; - if (!txn.gen) delete txn.gen; - if (this.rekeyTo !== undefined) { - txn.rekey = this.rekeyTo.publicKey; + if (this.keyreg.stateProofKey) { + forEncoding.sprfkey = this.keyreg.stateProofKey; } - - if (!txn.caid) delete txn.caid; - if ( - !txn.apar.t && - !txn.apar.un && - !txn.apar.an && - !txn.apar.df && - !txn.apar.m && - !txn.apar.r && - !txn.apar.f && - !txn.apar.c && - !txn.apar.au && - !txn.apar.am && - !txn.apar.dc - ) { - delete txn.apar; - } else { - if (!txn.apar.t) delete txn.apar.t; - if (!txn.apar.dc) delete txn.apar.dc; - if (!txn.apar.un) delete txn.apar.un; - if (!txn.apar.an) delete txn.apar.an; - if (!txn.apar.df) delete txn.apar.df; - if (!txn.apar.m) delete txn.apar.m; - if (!txn.apar.r) delete txn.apar.r; - if (!txn.apar.f) delete txn.apar.f; - if (!txn.apar.c) delete txn.apar.c; - if (!txn.apar.au) delete txn.apar.au; - if (!txn.apar.am) delete txn.apar.am; + if (this.keyreg.voteFirst) { + forEncoding.votefst = this.keyreg.voteFirst; } - if (txn.grp === undefined) delete txn.grp; - - return txn; - } - if (this.type === 'axfer') { - // asset transfer, acceptance, revocation, mint, or burn - const txn: EncodedTransaction = { - aamt: this.amount, - fee: this.fee, - fv: this.firstValid, - lv: this.lastValid, - note: this.note, - snd: this.sender.publicKey, - arcv: this.receiver.publicKey, - type: this.type, - gen: this.genesisID, - gh: this.genesisHash, - lx: this.lease, - grp: this.group, - xaid: this.assetIndex, - }; - if (this.closeRemainderTo !== undefined) - txn.aclose = this.closeRemainderTo.publicKey; - if (this.assetSender !== undefined) txn.asnd = this.assetSender.publicKey; - // allowed zero values - if (!txn.note.length) delete txn.note; - if (!txn.lx.length) delete txn.lx; - if (!txn.aamt) delete txn.aamt; - if (!txn.amt) delete txn.amt; - if (!txn.fee) delete txn.fee; - if (!txn.fv) delete txn.fv; - if (!txn.gen) delete txn.gen; - if (txn.grp === undefined) delete txn.grp; - if (!txn.aclose) delete txn.aclose; - if (!txn.asnd) delete txn.asnd; - if (!txn.rekey) delete txn.rekey; - if (this.rekeyTo !== undefined) { - txn.rekey = this.rekeyTo.publicKey; + if (this.keyreg.voteLast) { + forEncoding.votelst = this.keyreg.voteLast; } - return txn; - } - if (this.type === 'afrz') { - // asset freeze or unfreeze - const txn: EncodedTransaction = { - fee: this.fee, - fv: this.firstValid, - lv: this.lastValid, - note: this.note, - snd: this.sender.publicKey, - type: this.type, - gen: this.genesisID, - gh: this.genesisHash, - lx: this.lease, - grp: this.group, - faid: this.assetIndex, - afrz: this.assetFrozen, - }; - if (this.freezeAccount !== undefined) - txn.fadd = this.freezeAccount.publicKey; - // allowed zero values - if (!txn.note.length) delete txn.note; - if (!txn.lx.length) delete txn.lx; - if (!txn.amt) delete txn.amt; - if (!txn.fee) delete txn.fee; - if (!txn.fv) delete txn.fv; - if (!txn.gen) delete txn.gen; - if (!txn.afrz) delete txn.afrz; - if (txn.grp === undefined) delete txn.grp; - if (this.rekeyTo !== undefined) { - txn.rekey = this.rekeyTo.publicKey; + if (this.keyreg.voteKeyDilution) { + forEncoding.votekd = this.keyreg.voteKeyDilution; + } + if (this.keyreg.nonParticipation) { + forEncoding.nonpart = this.keyreg.nonParticipation; } - return txn; + return forEncoding; } - if (this.type === 'appl') { - // application call of some kind - const txn: EncodedTransaction = { - fee: this.fee, - fv: this.firstValid, - lv: this.lastValid, - note: this.note, - snd: this.sender.publicKey, - type: this.type, - gen: this.genesisID, - gh: this.genesisHash, - lx: this.lease, - grp: this.group, - apid: this.appIndex, - apan: this.appOnComplete, - apls: { - nui: this.appLocalInts, - nbs: this.appLocalByteSlices, - }, - apgs: { - nui: this.appGlobalInts, - nbs: this.appGlobalByteSlices, - }, - apfa: this.appForeignApps, - apas: this.appForeignAssets, - apep: this.extraPages, - apbx: translateBoxReferences( - this.boxes, - this.appForeignApps, - this.appIndex - ), - }; - if (this.rekeyTo !== undefined) { - txn.rekey = this.rekeyTo.publicKey; + + if (this.assetConfig) { + if (this.assetConfig.assetId) { + forEncoding.caid = this.assetConfig.assetId; } - if (this.appApprovalProgram !== undefined) { - txn.apap = this.appApprovalProgram; + const assetParams: EncodedAssetParams = {}; + if (this.assetConfig.assetTotal) { + assetParams.t = this.assetConfig.assetTotal; } - if (this.appClearProgram !== undefined) { - txn.apsu = this.appClearProgram; + if (this.assetConfig.assetDecimals) { + assetParams.dc = this.assetConfig.assetDecimals; } - if (this.appArgs !== undefined) { - txn.apaa = this.appArgs; + if (this.assetConfig.assetDefaultFrozen) { + assetParams.df = this.assetConfig.assetDefaultFrozen; } - if (this.appAccounts !== undefined) { - txn.apat = this.appAccounts.map( - (decodedAddress) => decodedAddress.publicKey - ); + if (this.assetConfig.assetManager) { + assetParams.m = this.assetConfig.assetManager.publicKey; } - // allowed zero values - if (!txn.note.length) delete txn.note; - if (!txn.lx.length) delete txn.lx; - if (!txn.amt) delete txn.amt; - if (!txn.fee) delete txn.fee; - if (!txn.fv) delete txn.fv; - if (!txn.gen) delete txn.gen; - if (!txn.apid) delete txn.apid; - if (!txn.apls.nui) delete txn.apls.nui; - if (!txn.apls.nbs) delete txn.apls.nbs; - if (!txn.apls.nui && !txn.apls.nbs) delete txn.apls; - if (!txn.apgs.nui) delete txn.apgs.nui; - if (!txn.apgs.nbs) delete txn.apgs.nbs; - if (!txn.apaa || !txn.apaa.length) delete txn.apaa; - if (!txn.apgs.nui && !txn.apgs.nbs) delete txn.apgs; - if (!txn.apap) delete txn.apap; - if (!txn.apsu) delete txn.apsu; - if (!txn.apan) delete txn.apan; - if (!txn.apfa || !txn.apfa.length) delete txn.apfa; - if (!txn.apas || !txn.apas.length) delete txn.apas; - for (const box of txn.apbx) { - if (!box.i) delete box.i; - if (!box.n || !box.n.length) delete box.n; + if (this.assetConfig.assetReserve) { + assetParams.r = this.assetConfig.assetReserve.publicKey; } - if (!txn.apbx || !txn.apbx.length) delete txn.apbx; - if (!txn.apat || !txn.apat.length) delete txn.apat; - if (!txn.apep) delete txn.apep; - if (txn.grp === undefined) delete txn.grp; - return txn; - } - if (this.type === 'stpf') { - // state proof txn - const txn: EncodedTransaction = { - fee: this.fee, - fv: this.firstValid, - lv: this.lastValid, - note: this.note, - snd: this.sender.publicKey, - type: this.type, - gen: this.genesisID, - gh: this.genesisHash, - lx: this.lease, - sptype: this.stateProofType, - spmsg: this.stateProofMessage, - sp: this.stateProof, - }; - // allowed zero values - if (!txn.sptype) delete txn.sptype; - if (!txn.note.length) delete txn.note; - if (!txn.lx.length) delete txn.lx; - if (!txn.amt) delete txn.amt; - if (!txn.fee) delete txn.fee; - if (!txn.fv) delete txn.fv; - if (!txn.gen) delete txn.gen; - if (!txn.apid) delete txn.apid; - if (!txn.apaa || !txn.apaa.length) delete txn.apaa; - if (!txn.apap) delete txn.apap; - if (!txn.apsu) delete txn.apsu; - if (!txn.apan) delete txn.apan; - if (!txn.apfa || !txn.apfa.length) delete txn.apfa; - if (!txn.apas || !txn.apas.length) delete txn.apas; - if (!txn.apat || !txn.apat.length) delete txn.apat; - if (!txn.apep) delete txn.apep; - if (txn.grp === undefined) delete txn.grp; - return txn; - } - - return undefined; - } - - // eslint-disable-next-line camelcase - static from_obj_for_encoding(txnForEnc: EncodedTransaction): Transaction { - const txn = Object.create(this.prototype) as Transaction; - txn.name = 'Transaction'; - txn.tag = new TextEncoder().encode('TX'); - - txn.genesisID = txnForEnc.gen; - txn.genesisHash = txnForEnc.gh; - if (!isTransactionType(txnForEnc.type)) { - throw new Error(`Unrecognized transaction type: ${txnForEnc.type}`); + if (this.assetConfig.assetFreeze) { + assetParams.f = this.assetConfig.assetFreeze.publicKey; + } + if (this.assetConfig.assetClawback) { + assetParams.c = this.assetConfig.assetClawback.publicKey; + } + if (this.assetConfig.assetUnitName) { + assetParams.un = this.assetConfig.assetUnitName; + } + if (this.assetConfig.assetName) { + assetParams.an = this.assetConfig.assetName; + } + if (this.assetConfig.assetURL) { + assetParams.au = this.assetConfig.assetURL; + } + if (this.assetConfig.assetMetadataHash) { + assetParams.am = this.assetConfig.assetMetadataHash; + } + if (Object.keys(assetParams).length) { + forEncoding.apar = assetParams; + } + return forEncoding; } - txn.type = txnForEnc.type; - txn.fee = txnForEnc.fee; - txn.firstValid = txnForEnc.fv; - txn.lastValid = txnForEnc.lv; - txn.note = new Uint8Array(txnForEnc.note); - txn.lease = new Uint8Array(txnForEnc.lx); - txn.sender = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.snd)) - ); - if (txnForEnc.grp !== undefined) txn.group = txnForEnc.grp; - if (txnForEnc.rekey !== undefined) - txn.rekeyTo = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.rekey)) - ); - if (txnForEnc.type === 'pay') { - txn.amount = txnForEnc.amt; - txn.receiver = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.rcv)) - ); - if (txnForEnc.close !== undefined) - txn.closeRemainderTo = address.decodeAddress( - address.encodeAddress(txnForEnc.close) - ); - } else if (txnForEnc.type === 'keyreg') { - if (txnForEnc.votekey !== undefined) { - txn.voteKey = txnForEnc.votekey; + if (this.assetTransfer) { + if (this.assetTransfer.assetId) { + forEncoding.xaid = this.assetTransfer.assetId; + } + if (this.assetTransfer.amount) { + forEncoding.aamt = this.assetTransfer.amount; } - if (txnForEnc.selkey !== undefined) { - txn.selectionKey = txnForEnc.selkey; + if (!uint8ArrayIsEmpty(this.assetTransfer.receiver.publicKey)) { + forEncoding.arcv = this.assetTransfer.receiver.publicKey; } - if (txnForEnc.sprfkey !== undefined) { - txn.stateProofKey = txnForEnc.sprfkey; + if (this.assetTransfer.closeRemainderTo) { + forEncoding.aclose = this.assetTransfer.closeRemainderTo.publicKey; } - if (txnForEnc.votekd !== undefined) { - txn.voteKeyDilution = txnForEnc.votekd; + if (this.assetTransfer.sender) { + forEncoding.asnd = this.assetTransfer.sender.publicKey; } - if (txnForEnc.votefst !== undefined) { - txn.voteFirst = txnForEnc.votefst; + return forEncoding; + } + + if (this.assetFreeze) { + if (this.assetFreeze.assetId) { + forEncoding.faid = this.assetFreeze.assetId; } - if (txnForEnc.votelst !== undefined) { - txn.voteLast = txnForEnc.votelst; + if (this.assetFreeze.assetFrozen) { + forEncoding.afrz = this.assetFreeze.assetFrozen; } - if (txnForEnc.nonpart !== undefined) { - txn.nonParticipation = txnForEnc.nonpart; + if (!uint8ArrayIsEmpty(this.assetFreeze.freezeAccount.publicKey)) { + forEncoding.fadd = this.assetFreeze.freezeAccount.publicKey; } - } else if (txnForEnc.type === 'acfg') { - // asset creation, or asset reconfigure, or asset destruction - if (txnForEnc.caid !== undefined) { - txn.assetIndex = txnForEnc.caid; + return forEncoding; + } + + if (this.applicationCall) { + if (this.applicationCall.appId) { + forEncoding.apid = this.applicationCall.appId; } - if (txnForEnc.apar !== undefined) { - txn.assetTotal = txnForEnc.apar.t; - txn.assetDefaultFrozen = txnForEnc.apar.df; - if (txnForEnc.apar.dc !== undefined) - txn.assetDecimals = txnForEnc.apar.dc; - if (txnForEnc.apar.m !== undefined) - txn.assetManager = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.apar.m)) - ); - if (txnForEnc.apar.r !== undefined) - txn.assetReserve = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.apar.r)) - ); - if (txnForEnc.apar.f !== undefined) - txn.assetFreeze = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.apar.f)) - ); - if (txnForEnc.apar.c !== undefined) - txn.assetClawback = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.apar.c)) - ); - if (txnForEnc.apar.un !== undefined) - txn.assetUnitName = txnForEnc.apar.un; - if (txnForEnc.apar.an !== undefined) txn.assetName = txnForEnc.apar.an; - if (txnForEnc.apar.au !== undefined) txn.assetURL = txnForEnc.apar.au; - if (txnForEnc.apar.am !== undefined) - txn.assetMetadataHash = txnForEnc.apar.am; + if (this.applicationCall.appOnComplete) { + forEncoding.apan = this.applicationCall.appOnComplete; } - } else if (txnForEnc.type === 'axfer') { - // asset transfer, acceptance, revocation, mint, or burn - if (txnForEnc.xaid !== undefined) { - txn.assetIndex = txnForEnc.xaid; + if (this.applicationCall.appArgs.length) { + forEncoding.apaa = this.applicationCall.appArgs.slice(); } - if (txnForEnc.aamt !== undefined) txn.amount = txnForEnc.aamt; - if (txnForEnc.aclose !== undefined) { - txn.closeRemainderTo = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.aclose)) + if (this.applicationCall.appAccounts.length) { + forEncoding.apat = this.applicationCall.appAccounts.map( + (decodedAddress) => decodedAddress.publicKey ); } - if (txnForEnc.asnd !== undefined) { - txn.assetSender = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.asnd)) - ); + if (this.applicationCall.appForeignAssets.length) { + forEncoding.apas = this.applicationCall.appForeignAssets.slice(); } - txn.receiver = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.arcv)) - ); - } else if (txnForEnc.type === 'afrz') { - if (txnForEnc.afrz !== undefined) { - txn.assetFrozen = txnForEnc.afrz; + if (this.applicationCall.appForeignApps.length) { + forEncoding.apfa = this.applicationCall.appForeignApps.slice(); } - if (txnForEnc.faid !== undefined) { - txn.assetIndex = txnForEnc.faid; + if (this.applicationCall.boxes.length) { + forEncoding.apbx = translateBoxReferences( + this.applicationCall.boxes, + this.applicationCall.appForeignApps, + this.applicationCall.appId + ); } - txn.freezeAccount = address.decodeAddress( - address.encodeAddress(new Uint8Array(txnForEnc.fadd)) - ); - } else if (txnForEnc.type === 'appl') { - if (txnForEnc.apid !== undefined) { - txn.appIndex = txnForEnc.apid; + if (this.applicationCall.appApprovalProgram.length) { + forEncoding.apap = this.applicationCall.appApprovalProgram; } - if (txnForEnc.apan !== undefined) { - txn.appOnComplete = txnForEnc.apan; + if (this.applicationCall.appClearProgram.length) { + forEncoding.apsu = this.applicationCall.appClearProgram; } - if (txnForEnc.apls !== undefined) { - if (txnForEnc.apls.nui !== undefined) - txn.appLocalInts = txnForEnc.apls.nui; - if (txnForEnc.apls.nbs !== undefined) - txn.appLocalByteSlices = txnForEnc.apls.nbs; + if ( + this.applicationCall.appLocalInts || + this.applicationCall.appLocalByteSlices + ) { + const localSchema: EncodedLocalStateSchema = {}; + if (this.applicationCall.appLocalInts) { + localSchema.nui = this.applicationCall.appLocalInts; + } + if (this.applicationCall.appLocalByteSlices) { + localSchema.nbs = this.applicationCall.appLocalByteSlices; + } + forEncoding.apls = localSchema; } - if (txnForEnc.apgs !== undefined) { - if (txnForEnc.apgs.nui !== undefined) - txn.appGlobalInts = txnForEnc.apgs.nui; - if (txnForEnc.apgs.nbs !== undefined) - txn.appGlobalByteSlices = txnForEnc.apgs.nbs; + if ( + this.applicationCall.appGlobalInts || + this.applicationCall.appGlobalByteSlices + ) { + const globalSchema: EncodedGlobalStateSchema = {}; + if (this.applicationCall.appGlobalInts) { + globalSchema.nui = this.applicationCall.appGlobalInts; + } + if (this.applicationCall.appGlobalByteSlices) { + globalSchema.nbs = this.applicationCall.appGlobalByteSlices; + } + forEncoding.apgs = globalSchema; } - if (txnForEnc.apep !== undefined) { - txn.extraPages = txnForEnc.apep; + if (this.applicationCall.extraPages) { + forEncoding.apep = this.applicationCall.extraPages; } - if (txnForEnc.apap !== undefined) { - txn.appApprovalProgram = new Uint8Array(txnForEnc.apap); + return forEncoding; + } + + if (this.stateProof) { + if (this.stateProof.stateProofType) { + forEncoding.sptype = this.stateProof.stateProofType; } - if (txnForEnc.apsu !== undefined) { - txn.appClearProgram = new Uint8Array(txnForEnc.apsu); + forEncoding.spmsg = this.stateProof.stateProofMessage; + forEncoding.sp = this.stateProof.stateProof; + return forEncoding; + } + + throw new Error(`Unexpected transaction type: ${this.type}`); + } + + // eslint-disable-next-line camelcase + static from_obj_for_encoding(txnForEnc: EncodedTransaction): Transaction { + const suggestedParams: SuggestedParams = { + minFee: BigInt(0), + flatFee: true, + fee: txnForEnc.fee ?? 0, + firstValid: txnForEnc.fv ?? 0, + lastValid: txnForEnc.lv, + genesisHash: bytesToBase64(txnForEnc.gh), // TODO: would like to avoid encoding/decoding here + genesisID: txnForEnc.gen, + }; + + if (!isTransactionType(txnForEnc.type)) { + throw new Error(`Unrecognized transaction type: ${txnForEnc.type}`); + } + + const params: TransactionParams = { + type: txnForEnc.type, + sender: txnForEnc.snd + ? address.encodeAddress(txnForEnc.snd) + : address.ALGORAND_ZERO_ADDRESS_STRING, + note: txnForEnc.note, + lease: txnForEnc.lx, + suggestedParams, + }; + + if (txnForEnc.rekey) { + params.rekeyTo = address.encodeAddress(txnForEnc.rekey); + } + + if (params.type === TransactionType.pay) { + const paymentParams: PaymentTransactionParams = { + amount: txnForEnc.amt ?? 0, + receiver: txnForEnc.rcv + ? address.encodeAddress(txnForEnc.rcv) + : address.ALGORAND_ZERO_ADDRESS_STRING, + }; + if (txnForEnc.close) { + paymentParams.closeRemainderTo = address.encodeAddress(txnForEnc.close); } - if (txnForEnc.apaa !== undefined) { - txn.appArgs = txnForEnc.apaa.map((arg) => new Uint8Array(arg)); + params.paymentParams = paymentParams; + } else if (params.type === TransactionType.keyreg) { + const keyregParams: KeyRegistrationTransactionParams = { + voteKey: txnForEnc.votekey, + selectionKey: txnForEnc.selkey, + stateProofKey: txnForEnc.sprfkey, + voteFirst: txnForEnc.votefst, + voteLast: txnForEnc.votelst, + voteKeyDilution: txnForEnc.votekd, + nonParticipation: txnForEnc.nonpart, + }; + params.keyregParams = keyregParams; + } else if (params.type === TransactionType.acfg) { + const assetConfigParams: AssetConfigurationTransactionParams = { + assetIndex: txnForEnc.caid, + }; + if (txnForEnc.apar) { + assetConfigParams.total = txnForEnc.apar.t; + assetConfigParams.decimals = txnForEnc.apar.dc; + assetConfigParams.defaultFrozen = txnForEnc.apar.df; + assetConfigParams.unitName = txnForEnc.apar.un; + assetConfigParams.assetName = txnForEnc.apar.an; + assetConfigParams.assetURL = txnForEnc.apar.au; + assetConfigParams.assetMetadataHash = txnForEnc.apar.am; + if (txnForEnc.apar.m) { + assetConfigParams.manager = address.encodeAddress(txnForEnc.apar.m); + } + if (txnForEnc.apar.r) { + assetConfigParams.reserve = address.encodeAddress(txnForEnc.apar.r); + } + if (txnForEnc.apar.f) { + assetConfigParams.freeze = address.encodeAddress(txnForEnc.apar.f); + } + if (txnForEnc.apar.c) { + assetConfigParams.clawback = address.encodeAddress(txnForEnc.apar.c); + } } - if (txnForEnc.apat !== undefined) { - txn.appAccounts = txnForEnc.apat.map((addressBytes) => - address.decodeAddress( - address.encodeAddress(new Uint8Array(addressBytes)) - ) + params.assetConfigParams = assetConfigParams; + } else if (params.type === TransactionType.axfer) { + const assetTransferParams: AssetTransferTransactionParams = { + assetIndex: txnForEnc.xaid ?? 0, + amount: txnForEnc.aamt ?? 0, + receiver: txnForEnc.arcv + ? address.encodeAddress(txnForEnc.arcv) + : address.ALGORAND_ZERO_ADDRESS_STRING, + }; + if (txnForEnc.aclose) { + assetTransferParams.closeRemainderTo = address.encodeAddress( + txnForEnc.aclose ); } - if (txnForEnc.apfa !== undefined) { - txn.appForeignApps = txnForEnc.apfa; - } - if (txnForEnc.apas !== undefined) { - txn.appForeignAssets = txnForEnc.apas; - } - if (txnForEnc.apbx !== undefined) { - txn.boxes = txnForEnc.apbx.map((box) => ({ - // We return 0 for the app ID so that it's guaranteed translateBoxReferences will - // translate the app index back to 0. If we instead returned the called app ID, - // translateBoxReferences would translate the app index to a nonzero value if the called - // app is also in the foreign app array. - appIndex: box.i ? txn.appForeignApps[box.i - 1] : 0, - name: box.n, - })); + if (txnForEnc.asnd) { + assetTransferParams.assetSender = address.encodeAddress(txnForEnc.asnd); } - } else if (txnForEnc.type === 'stpf') { - if (txnForEnc.sptype !== undefined) { - txn.stateProofType = txnForEnc.sptype; - } - if (txnForEnc.sp !== undefined) { - txn.stateProof = txnForEnc.sp; + params.assetTransferParams = assetTransferParams; + } else if (params.type === TransactionType.afrz) { + const assetFreezeParams: AssetFreezeTransactionParams = { + assetIndex: txnForEnc.faid ?? 0, + freezeTarget: txnForEnc.fadd + ? address.encodeAddress(txnForEnc.fadd) + : address.ALGORAND_ZERO_ADDRESS_STRING, + assetFrozen: txnForEnc.afrz ?? false, + }; + params.assetFreezeParams = assetFreezeParams; + } else if (params.type === TransactionType.appl) { + const appCallParams: ApplicationCallTransactionParams = { + appId: txnForEnc.apid ?? 0, + onComplete: utils.ensureSafeUnsignedInteger(txnForEnc.apan ?? 0), + appArgs: txnForEnc.apaa, + accounts: (txnForEnc.apat ?? []).map(address.encodeAddress), + foreignAssets: txnForEnc.apas, + foreignApps: txnForEnc.apfa, + approvalProgram: txnForEnc.apap, + clearProgram: txnForEnc.apsu, + numLocalInts: txnForEnc.apls?.nui, + numLocalByteSlices: txnForEnc.apls?.nbs, + numGlobalInts: txnForEnc.apgs?.nui, + numGlobalByteSlices: txnForEnc.apgs?.nbs, + extraPages: txnForEnc.apep, + }; + if (txnForEnc.apbx) { + appCallParams.boxes = txnForEnc.apbx.map((box) => { + const index = utils.ensureSafeUnsignedInteger(box.i ?? 0); + const name = box.n ?? new Uint8Array(); + if (index === 0) { + // We return 0 for the app ID so that it's guaranteed translateBoxReferences will + // translate the app index back to 0. If we instead returned the called app ID, + // translateBoxReferences would translate the app index to a nonzero value if the called + // app is also in the foreign app array. + return { + appIndex: 0, + name, + }; + } + if ( + !appCallParams.foreignApps || + index > appCallParams.foreignApps.length + ) { + throw new Error( + `Cannot find foreign app index ${index} in ${appCallParams.foreignApps}` + ); + } + return { + appIndex: appCallParams.foreignApps[index - 1], + name, + }; + }); } - if (txnForEnc.spmsg !== undefined) { - txn.stateProofMessage = txnForEnc.spmsg; + params.appCallParams = appCallParams; + } else if (params.type === TransactionType.stpf) { + const stateProofParams: StateProofTransactionParams = { + stateProofType: txnForEnc.sptype, + stateProof: txnForEnc.sp, + stateProofMessage: txnForEnc.spmsg, + }; + params.stateProofParams = stateProofParams; + } else { + const exhaustiveCheck: never = params.type; + throw new Error(`Unexpected transaction type: ${exhaustiveCheck}`); + } + + const txn = new Transaction(params); + + if (txnForEnc.grp) { + const group = ensureUint8Array(txnForEnc.grp); + if (group.byteLength !== ALGORAND_TRANSACTION_GROUP_LENGTH) { + throw new Error(`Invalid group length: ${group.byteLength}`); } + txn.group = group; } + return txn; } - estimateSize() { + private estimateSize() { return this.toByte().length + NUM_ADDL_BYTES_AFTER_SIGNING; } bytesToSign() { const encodedMsg = this.toByte(); - return utils.concatArrays(this.tag, encodedMsg); + return utils.concatArrays(TX_TAG, encodedMsg); } toByte() { @@ -1121,13 +952,13 @@ export class Transaction implements TransactionStorageStructure { } // returns the raw signature - rawSignTxn(sk: Uint8Array) { + rawSignTxn(sk: Uint8Array): Uint8Array { const toBeSigned = this.bytesToSign(); const sig = nacl.sign(toBeSigned, sk); return sig; } - signTxn(sk: Uint8Array) { + signTxn(sk: Uint8Array): Uint8Array { // construct signed message const sTxn: EncodedSignedTransaction = { sig: this.rawSignTxn(sk), @@ -1145,7 +976,7 @@ export class Transaction implements TransactionStorageStructure { return new Uint8Array(encoding.encode(sTxn)); } - attachSignature(signerAddr: string, signature: Uint8Array) { + attachSignature(signerAddr: string, signature: Uint8Array): Uint8Array { if (!nacl.isValidSignatureLength(signature.length)) { throw new Error('Invalid signature length'); } @@ -1161,120 +992,16 @@ export class Transaction implements TransactionStorageStructure { return new Uint8Array(encoding.encode(sTxn)); } - rawTxID() { + rawTxID(): Uint8Array { const enMsg = this.toByte(); - const gh = utils.concatArrays(this.tag, enMsg); + const gh = utils.concatArrays(TX_TAG, enMsg); return Uint8Array.from(nacl.genericHash(gh)); } - txID() { + txID(): string { const hash = this.rawTxID(); return base32.encode(hash).slice(0, ALGORAND_TRANSACTION_LENGTH); } - - // add a lease to a transaction not yet having - // supply feePerByte to increment fee accordingly - addLease(lease: Uint8Array, feePerByte = 0) { - let mutableLease: Uint8Array; - - if (lease !== undefined) { - if (lease.constructor !== Uint8Array) - throw Error('lease must be a Uint8Array.'); - if (lease.length !== ALGORAND_TRANSACTION_LEASE_LENGTH) - throw Error( - `lease must be of length ${ALGORAND_TRANSACTION_LEASE_LENGTH.toString()}.` - ); - - mutableLease = new Uint8Array(lease); - } else { - mutableLease = new Uint8Array(0); - } - this.lease = mutableLease; - if (feePerByte !== 0) { - this.fee += - (ALGORAND_TRANSACTION_LEASE_LABEL_LENGTH + - ALGORAND_TRANSACTION_LEASE_LENGTH) * - feePerByte; - } - } - - // add the rekey-to field to a transaction not yet having it - // supply feePerByte to increment fee accordingly - addRekey(rekeyTo: string, feePerByte = 0) { - if (rekeyTo !== undefined) { - this.rekeyTo = address.decodeAddress(rekeyTo); - } - if (feePerByte !== 0) { - this.fee += - (ALGORAND_TRANSACTION_REKEY_LABEL_LENGTH + - ALGORAND_TRANSACTION_ADDRESS_LENGTH) * - feePerByte; - } - } - - // build display dict for prettyPrint and toString - // eslint-disable-next-line no-underscore-dangle - _getDictForDisplay() { - const forPrinting: TransactionStorageStructure & Record = { - ...this, - }; - forPrinting.tag = forPrinting.tag.toString(); - forPrinting.sender = address.encodeAddress( - (forPrinting.sender as Address).publicKey - ); - if (forPrinting.receiver !== undefined) - forPrinting.receiver = address.encodeAddress( - (forPrinting.receiver as Address).publicKey - ); - // things that need fixing: - if (forPrinting.freezeAccount !== undefined) - forPrinting.freezeAccount = address.encodeAddress( - (forPrinting.freezeAccount as Address).publicKey - ); - if (forPrinting.closeRemainderTo !== undefined) - forPrinting.closeRemainderTo = address.encodeAddress( - (forPrinting.closeRemainderTo as Address).publicKey - ); - if (forPrinting.assetManager !== undefined) - forPrinting.assetManager = address.encodeAddress( - (forPrinting.assetManager as Address).publicKey - ); - if (forPrinting.assetReserve !== undefined) - forPrinting.assetReserve = address.encodeAddress( - (forPrinting.assetReserve as Address).publicKey - ); - if (forPrinting.assetFreeze !== undefined) - forPrinting.assetFreeze = address.encodeAddress( - (forPrinting.assetFreeze as Address).publicKey - ); - if (forPrinting.assetClawback !== undefined) - forPrinting.assetClawback = address.encodeAddress( - (forPrinting.assetClawback as Address).publicKey - ); - if (forPrinting.assetSender !== undefined) - forPrinting.assetSender = address.encodeAddress( - (forPrinting.assetSender as Address).publicKey - ); - if (forPrinting.rekeyTo !== undefined) - forPrinting.rekeyTo = address.encodeAddress( - (forPrinting.rekeyTo as Address).publicKey - ); - if (typeof forPrinting.genesisHash !== 'string') - forPrinting.genesisHash = bytesToBase64(forPrinting.genesisHash); - return forPrinting; - } - - // pretty print the transaction to console - prettyPrint() { - // eslint-disable-next-line no-underscore-dangle,no-console - console.log(this._getDictForDisplay()); - } - - // get string representation - toString() { - // eslint-disable-next-line no-underscore-dangle - return JSON.stringify(this._getDictForDisplay()); - } } /** @@ -1365,16 +1092,3 @@ export function decodeSignedTransaction( }; return stxn; } - -/** - * Either a valid transaction object or an instance of the Transaction class - */ -export type TransactionLike = AnyTransaction | Transaction; - -export function instantiateTxnIfNeeded(transactionLike: TransactionLike) { - return transactionLike instanceof Transaction - ? transactionLike - : new Transaction(transactionLike); -} - -export default Transaction; diff --git a/src/types/transactions/application.ts b/src/types/transactions/application.ts deleted file mode 100644 index 70e6e10ca..000000000 --- a/src/types/transactions/application.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { TransactionType, TransactionParams } from './base.js'; -import { ConstructTransaction } from './builder.js'; - -// ----------------------------------- -// > Application Create Transaction -// ----------------------------------- - -type SpecificParametersForCreate = Pick< - TransactionParams, - | 'appIndex' - | 'appOnComplete' - | 'appApprovalProgram' - | 'appClearProgram' - | 'appLocalInts' - | 'appLocalByteSlices' - | 'appGlobalInts' - | 'appGlobalByteSlices' - | 'appArgs' - | 'appAccounts' - | 'appForeignApps' - | 'appForeignAssets' - | 'boxes' - | 'extraPages' ->; - -interface OverwritesForCreate { - type?: TransactionType.appl; -} - -export type ApplicationCreateTransaction = ConstructTransaction< - SpecificParametersForCreate, - OverwritesForCreate ->; - -// ----------------------------------- -// > Application Update Transaction -// ----------------------------------- - -type SpecificParametersForUpdate = Pick< - TransactionParams, - | 'appIndex' - | 'appOnComplete' - | 'appApprovalProgram' - | 'appClearProgram' - | 'appArgs' - | 'appAccounts' - | 'appForeignApps' - | 'appForeignAssets' - | 'boxes' ->; - -interface OverwritesForUpdate { - type?: TransactionType.appl; -} - -export type ApplicationUpdateTransaction = ConstructTransaction< - SpecificParametersForUpdate, - OverwritesForUpdate ->; - -// ----------------------------------- -// > Application Delete Transaction -// ----------------------------------- - -type SpecificParametersForDelete = Pick< - TransactionParams, - | 'appIndex' - | 'appOnComplete' - | 'appArgs' - | 'appAccounts' - | 'appForeignApps' - | 'appForeignAssets' - | 'boxes' ->; - -interface OverwritesForDelete { - type?: TransactionType.appl; -} - -export type ApplicationDeleteTransaction = ConstructTransaction< - SpecificParametersForDelete, - OverwritesForDelete ->; - -// ----------------------------------- -// > Application Opt-In Transaction -// ----------------------------------- - -// Same structure as the application delete transaction -export type ApplicationOptInTransaction = ApplicationDeleteTransaction; - -// ----------------------------------- -// > Application Close Out Transaction -// ----------------------------------- - -// Same structure as the application delete transaction -export type ApplicationCloseOutTransaction = ApplicationDeleteTransaction; - -// -------------------------------------- -// > Application Clear State Transaction -// -------------------------------------- - -// Same structure as the application delete transaction -export type ApplicationClearStateTransaction = ApplicationDeleteTransaction; - -// -------------------------------------- -// > Application Call (NoOp) Transaction -// -------------------------------------- - -// Same structure as the application delete transaction -export type ApplicationNoOpTransaction = ApplicationDeleteTransaction; diff --git a/src/types/transactions/asset.ts b/src/types/transactions/asset.ts deleted file mode 100644 index 0a7781040..000000000 --- a/src/types/transactions/asset.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { TransactionType, TransactionParams } from './base.js'; -import { ConstructTransaction } from './builder.js'; - -// ------------------------------ -// > Asset Create Transaction -// ------------------------------ - -type SpecificParametersForCreate = Pick< - TransactionParams, - | 'assetTotal' - | 'assetDecimals' - | 'assetDefaultFrozen' - | 'assetUnitName' - | 'assetName' - | 'assetURL' - | 'assetMetadataHash' - | 'assetManager' - | 'assetReserve' - | 'assetFreeze' - | 'assetClawback' ->; - -interface OverwritesForCreate { - type?: TransactionType.acfg; -} - -export type AssetCreateTransaction = ConstructTransaction< - SpecificParametersForCreate, - OverwritesForCreate ->; - -// ------------------------------ -// > Asset Config Transaction -// ------------------------------ - -type SpecificParametersForConfig = Pick< - TransactionParams, - | 'assetIndex' - | 'assetManager' - | 'assetReserve' - | 'assetFreeze' - | 'assetClawback' ->; - -interface OverwritesForConfig { - type?: TransactionType.acfg; -} - -export type AssetConfigurationTransaction = ConstructTransaction< - SpecificParametersForConfig, - OverwritesForConfig ->; - -// ------------------------------ -// > Asset Destroy Transaction -// ------------------------------ - -type SpecificParametersForDestroy = Pick; - -interface OverwritesForDestroy { - type?: TransactionType.acfg; -} - -export type AssetDestroyTransaction = ConstructTransaction< - SpecificParametersForDestroy, - OverwritesForDestroy ->; - -// ------------------------------ -// > Asset Freeze Transaction -// ------------------------------ - -type SpecificParametersForFreeze = Pick< - TransactionParams, - 'assetIndex' | 'freezeAccount' | 'assetFrozen' ->; - -interface OverwritesForFreeze { - type?: TransactionType.afrz; -} - -export type AssetFreezeTransaction = ConstructTransaction< - SpecificParametersForFreeze, - OverwritesForFreeze ->; - -// ------------------------------ -// > Asset Transfer Transaction -// ------------------------------ - -type SpecificParametersForTransfer = Pick< - TransactionParams, - | 'sender' - | 'receiver' - | 'closeRemainderTo' - | 'assetSender' - | 'amount' - | 'assetIndex' ->; - -interface OverwritesForTransfer { - type?: TransactionType.axfer; -} - -export type AssetTransferTransaction = ConstructTransaction< - SpecificParametersForTransfer, - OverwritesForTransfer ->; diff --git a/src/types/transactions/base.ts b/src/types/transactions/base.ts index 9294f4536..81d1ab8f1 100644 --- a/src/types/transactions/base.ts +++ b/src/types/transactions/base.ts @@ -1,7 +1,7 @@ +import { Address } from '../address.js'; + /** * Enum for application transaction types. - * - * The full list is available at https://developer.algorand.org/docs/reference/transactions/ */ export enum TransactionType { /** @@ -39,6 +39,11 @@ export enum TransactionType { stpf = 'stpf', } +/** + * Check if a string is a valid transaction type + * @param s - string to check + * @returns true if s is a valid transaction type + */ export function isTransactionType(s: string): s is TransactionType { return ( s === TransactionType.pay || @@ -95,7 +100,7 @@ export enum OnApplicationComplete { } /** - * A dict holding common-to-all-txns arguments + * Contains parameters relevant to the creation of a new transaction in a specific network at a specific time */ export interface SuggestedParams { /** @@ -107,22 +112,27 @@ export interface SuggestedParams { /** * Integer fee per byte, in microAlgos. For a flat fee, set flatFee to true */ - fee: number; + fee: number | bigint; + + /** + * Minimum fee (not per byte) required for the transaction to be confirmed + */ + minFee: number | bigint; /** * First protocol round on which this txn is valid */ - firstValid: number; + firstValid: number | bigint; /** * Last protocol round on which this txn is valid */ - lastValid: number; + lastValid: number | bigint; /** * Specifies genesis ID of network in use */ - genesisID: string; + genesisID?: string; /** * Specifies hash genesis block of network in use @@ -130,13 +140,6 @@ export interface SuggestedParams { genesisHash: string; } -export type SuggestedParamsWithMinFee = SuggestedParams & { - /** - * Minimum fee (not per byte) required for the transaction to be confirmed - */ - minFee: number; -}; - /** * A grouping of the app ID and name of the box in an Uint8Array */ @@ -144,7 +147,7 @@ export interface BoxReference { /** * A unique application index */ - appIndex: number; + appIndex: number | bigint; /** * Name of box to reference @@ -153,211 +156,245 @@ export interface BoxReference { } /** - * A full list of all available transaction parameters + * Contains payment transaction parameters. * * The full documentation is available at: - * https://developer.algorand.org/docs/reference/transactions/#common-fields-header-and-type + * https://developer.algorand.org/docs/get-details/transactions/transactions/#payment-transaction */ -export interface TransactionParams { +export interface PaymentTransactionParams { /** - * String representation of Algorand address of sender + * Algorand address of recipient */ - sender: string; + receiver: string | Address; /** - * String representation of Algorand address of recipient + * Integer amount to send, in microAlgos. Must be nonnegative. */ - receiver: string; + amount: number | bigint; /** - * Integer fee per byte, in microAlgos. For a flat fee, set flatFee to true + * Optional, indicates the sender will close their account and the remaining balance will transfer + * to this account */ - fee: number; + closeRemainderTo?: string | Address; +} +/** + * Contains key registration transaction parameters + * + * The full documentation is available at: + * https://developer.algorand.org/docs/get-details/transactions/transactions/#key-registration-transaction + */ +export interface KeyRegistrationTransactionParams { /** - * Integer amount to send + * 32-byte voting key. For key deregistration, leave undefined */ - amount: number | bigint; + voteKey?: Uint8Array | string; /** - * Integer first protocol round on which this txn is valid + * 32-byte selection key. For key deregistration, leave undefined */ - firstValid: number; + selectionKey?: Uint8Array | string; /** - * Integer last protocol round on which this txn is valid + * 64-byte state proof key. For key deregistration, leave undefined */ - lastValid: number; + stateProofKey?: Uint8Array | string; /** - * Arbitrary data for sender to store + * First round on which voting keys are valid */ - note?: Uint8Array; + voteFirst?: number | bigint; /** - * Specifies genesis ID of network in use + * Last round on which voting keys are valid */ - genesisID: string; + voteLast?: number | bigint; /** - * Specifies hash genesis block of network in use + * The dilution fo the 2-level participation key */ - genesisHash: string; + voteKeyDilution?: number | bigint; /** - * Lease a transaction. The sender cannot send another txn with that same lease until the last round of original txn has passed + * Set this value to true to mark this account as nonparticipating. + * + * All new Algorand accounts are participating by default. This means they earn rewards. */ - lease?: Uint8Array; + nonParticipation?: boolean; +} +/** + * Contains asset configuration transaction parameters. + * + * The full documentation is available at: + * https://developer.algorand.org/docs/get-details/transactions/transactions/#asset-configuration-transaction + */ +export interface AssetConfigurationTransactionParams { /** - * Close out remaining account balance to this account + * Asset index uniquely specifying the asset */ - closeRemainderTo?: string; + assetIndex?: number | bigint; /** - * Voting key bytes. For key deregistration, leave undefined + * Total supply of the asset */ - voteKey: Uint8Array | string; + total?: number | bigint; /** - *Selection key bytes. For key deregistration, leave undefined + * Integer number of decimals for asset unit calcuation */ - selectionKey: Uint8Array | string; + decimals?: number | bigint; /** - * State proof key bytes. For key deregistration, leave undefined + * Whether asset accounts should default to being frozen */ - stateProofKey: Uint8Array | string; + defaultFrozen?: boolean; /** - * First round on which voteKey is valid + * The Algorand address in charge of reserve, freeze, clawback, destruction, etc. */ - voteFirst: number; + manager?: string | Address; /** - * Last round on which voteKey is valid + * The Algorand address representing asset reserve */ - voteLast: number; + reserve?: string | Address; /** - * The dilution fo the 2-level participation key + * The Algorand address with power to freeze/unfreeze asset holdings */ - voteKeyDilution: number; + freeze?: string | Address; /** - * Asset index uniquely specifying the asset + * The Algorand address with power to revoke asset holdings */ - assetIndex: number; + clawback?: string | Address; /** - * Total supply of the asset + * Unit name for this asset */ - assetTotal: number | bigint; + unitName?: string; /** - * Integer number of decimals for asset unit calcuation + * Name for this asset */ - assetDecimals: number; + assetName?: string; /** - * Whether asset accounts should default to being frozen + * URL relating to this asset */ - assetDefaultFrozen: boolean; + assetURL?: string; /** - * String representation of Algorand address in charge of reserve, freeze, clawback, destruction, etc. + * Uint8Array containing a hash commitment with respect to the asset. Must be exactly 32 bytes long. */ - assetManager?: string; + assetMetadataHash?: Uint8Array; +} +/** + * Contains asset transfer transaction parameters. + * + * The full documentation is available at: + * https://developer.algorand.org/docs/get-details/transactions/transactions/#asset-transfer-transaction + */ +export interface AssetTransferTransactionParams { /** - * String representation of Algorand address representing asset reserve + * Asset index uniquely specifying the asset */ - assetReserve?: string; + assetIndex: number | bigint; /** - * String representation of Algorand address with power to freeze/unfreeze asset holdings + * String representation of Algorand address – if provided, and if "sender" is + * the asset's revocation manager, then deduct from "assetSender" rather than "sender" */ - assetFreeze?: string; + assetSender?: string | Address; /** - * String representation of Algorand address with power to revoke asset holdings + * The Algorand address of recipient */ - assetClawback?: string; + receiver: string | Address; /** - * Unit name for this asset - */ - assetUnitName?: string; - /** - * Name for this asset + * Integer amount to send */ - assetName?: string; + amount: number | bigint; /** - * URL relating to this asset + * Close out remaining asset balance of the sender to this account */ - assetURL?: string; + closeRemainderTo?: string | Address; +} +/** + * Contains asset freeze transaction parameters. + * + * The full documentation is available at: + * https://developer.algorand.org/docs/get-details/transactions/transactions/#asset-freeze-transaction + */ +export interface AssetFreezeTransactionParams { /** - * Uint8Array or UTF-8 string representation of a hash commitment with respect to the asset. Must be exactly 32 bytes long. + * Asset index uniquely specifying the asset */ - assetMetadataHash?: Uint8Array | string; + assetIndex: number | bigint; /** - * String representation of Algorand address being frozen or unfrozen + * Algorand address being frozen or unfrozen */ - freezeAccount: string; + freezeTarget: string | Address; /** * true if freezeTarget should be frozen, false if freezeTarget should be allowed to transact */ assetFrozen: boolean; +} +/** + * Contains application call transaction parameters. + * + * The full documentation is available at: + * https://developer.algorand.org/docs/get-details/transactions/transactions/#application-call-transaction + */ +export interface ApplicationCallTransactionParams { /** - * String representation of Algorand address – if provided, and if "sender" is - * the asset's revocation manager, then deduct from "assetSender" rather than "sender" - */ - assetSender?: string; - - /** - * A unique application index + * A unique application ID */ - appIndex: number; + appId: number | bigint; /** * What application should do once the program has been run */ - appOnComplete: OnApplicationComplete; + onComplete: OnApplicationComplete; /** * Restricts number of ints in per-user local state */ - appLocalInts?: number; + numLocalInts?: number | bigint; /** * Restricts number of byte slices in per-user local state */ - appLocalByteSlices?: number; + numLocalByteSlices?: number | bigint; /** * Restricts number of ints in global state */ - appGlobalInts?: number; + numGlobalInts?: number | bigint; /** * Restricts number of byte slices in global state */ - appGlobalByteSlices?: number; + numGlobalByteSlices?: number | bigint; /** * The compiled TEAL that approves a transaction */ - appApprovalProgram?: Uint8Array; + approvalProgram?: Uint8Array; /** * The compiled TEAL program that runs when clearing state */ - appClearProgram?: Uint8Array; + clearProgram?: Uint8Array; /** * Array of Uint8Array, any additional arguments to the application @@ -367,69 +404,121 @@ export interface TransactionParams { /** * Array of Address strings, any additional accounts to supply to the application */ - appAccounts?: string[]; + accounts?: Array; /** * Array of int, any other apps used by the application, identified by index */ - appForeignApps?: number[]; + foreignApps?: Array; /** * Array of int, any assets used by the application, identified by index */ - appForeignAssets?: number[]; + foreignAssets?: Array; + /** + * Int representing extra pages of memory to rent during an application create transaction. + */ + extraPages?: number | bigint; + + /** + * A grouping of the app ID and name of the box in an Uint8Array + */ + boxes?: BoxReference[]; +} + +/** + * Contains state proof transaction parameters. + */ +export interface StateProofTransactionParams { + /* + * Uint64 identifying a particular configuration of state proofs. + */ + stateProofType?: number | bigint; + + /** + * Byte array containing the state proof. + */ + stateProof?: Uint8Array; + + /** + * Byte array containing the state proof message. + */ + stateProofMessage?: Uint8Array; +} + +/** + * A full list of all available transaction parameters + * + * The full documentation is available at: + * https://developer.algorand.org/docs/get-details/transactions/transactions/#common-fields-header-and-type + */ +export interface TransactionParams { /** * Transaction type */ - type?: TransactionType; + type: TransactionType; /** - * Set this to true to specify fee as microalgos-per-txn. + * Algorand address of sender + */ + sender: string | Address; + + /** + * Optional, arbitrary data to be included in the transaction's note field + */ + note?: Uint8Array; + + /** + * Optional, 32-byte lease to associate with this transaction. * - * If the final calculated fee is lower than the protocol minimum fee, the fee will be increased to match the minimum + * The sender cannot send another transaction with the same lease until the last round of original + * transaction has passed. */ - flatFee?: boolean; + lease?: Uint8Array; /** - * A dict holding common-to-all-txns arguments + * The Algorand address that will be used to authorize all future transactions from the sender, if provided. + */ + rekeyTo?: string | Address; + + /** + * Suggested parameters relevant to the network that will accept this transaction */ suggestedParams: SuggestedParams; /** - * String representation of the Algorand address that will be used to authorize all future transactions + * Payment transaction parameters. Only set if type is TransactionType.pay */ - rekeyTo?: string; + paymentParams?: PaymentTransactionParams; /** - * Set this value to true to mark this account as nonparticipating. - * - * All new Algorand accounts are participating by default. This means they earn rewards. + * Key registration transaction parameters. Only set if type is TransactionType.keyreg */ - nonParticipation?: boolean; + keyregParams?: KeyRegistrationTransactionParams; /** - * Int representing extra pages of memory to rent during an application create transaction. + * Asset configuration transaction parameters. Only set if type is TransactionType.acfg */ - extraPages?: number; + assetConfigParams?: AssetConfigurationTransactionParams; /** - * A grouping of the app ID and name of the box in an Uint8Array + * Asset transfer transaction parameters. Only set if type is TransactionType.axfer */ - boxes?: BoxReference[]; + assetTransferParams?: AssetTransferTransactionParams; - /* - * Uint64 identifying a particular configuration of state proofs. + /** + * Asset freeze transaction parameters. Only set if type is TransactionType.afrz */ - stateProofType?: number | bigint; + assetFreezeParams?: AssetFreezeTransactionParams; /** - * Byte array containing the state proof. + * Application call transaction parameters. Only set if type is TransactionType.appl */ - stateProof?: Uint8Array; + appCallParams?: ApplicationCallTransactionParams; /** - * Byte array containing the state proof message. + * State proof transaction parameters. Only set if type is TransactionType.stpf */ - stateProofMessage?: Uint8Array; + stateProofParams?: StateProofTransactionParams; } diff --git a/src/types/transactions/builder.ts b/src/types/transactions/builder.ts deleted file mode 100644 index a5b1deb12..000000000 --- a/src/types/transactions/builder.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { DistributiveOverwrite } from '../utils.js'; -import { TransactionParams, SuggestedParams } from './base.js'; - -/** - * Transaction base with suggested params as object - */ -type TransactionBaseWithSuggestedParams = Pick< - TransactionParams, - 'suggestedParams' | 'sender' | 'type' | 'lease' | 'note' | 'rekeyTo' ->; - -/** - * Transaction base with suggested params included as parameters - */ -type TransactionBaseWithoutSuggestedParams = Pick< - TransactionParams, - | 'flatFee' - | 'fee' - | 'firstValid' - | 'lastValid' - | 'genesisHash' - | 'sender' - | 'type' - | 'genesisID' - | 'lease' - | 'note' - | 'rekeyTo' ->; - -/** - * Transaction common fields. - * - * Base transaction type that is extended for all other transaction types. - * Suggested params must be included, either as named object or included in the rest - * of the parameters. - */ -export type TransactionBase = - | TransactionBaseWithoutSuggestedParams - | TransactionBaseWithSuggestedParams - | (TransactionBaseWithSuggestedParams & - TransactionBaseWithoutSuggestedParams); - -/** - * Transaction builder type that accepts 2 generics: - * - A: Additional parameters on top of the base transaction parameters - * - O: A set of overwrites for transaction parameters - */ -export type ConstructTransaction< - A = {}, - O extends Partial = {}, -> = DistributiveOverwrite; - -/** - * Only accept transaction objects that include suggestedParams as an object - */ -export type MustHaveSuggestedParams = Extract< - T, - { suggestedParams: SuggestedParams } ->; - -/** - * Only accept transaction objects that include suggestedParams inline instead of being - * enclosed in its own property - */ -export type MustHaveSuggestedParamsInline = - Extract; - -export default ConstructTransaction; diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts index fe777e7a0..45afff12d 100644 --- a/src/types/transactions/encoded.ts +++ b/src/types/transactions/encoded.ts @@ -6,17 +6,17 @@ export interface EncodedAssetParams { /** * assetTotal */ - t: number | bigint; + t?: number | bigint; /** * assetDefaultFrozen */ - df: boolean; + df?: boolean; /** * assetDecimals */ - dc: number; + dc?: number | bigint; /** * assetManager @@ -63,36 +63,36 @@ export interface EncodedLocalStateSchema { /** * appLocalInts */ - nui: number; + nui?: number | bigint; /** * appLocalByteSlices */ - nbs: number; + nbs?: number | bigint; } export interface EncodedGlobalStateSchema { /** * appGlobalInts */ - nui: number; + nui?: number | bigint; /** * appGlobalByteSlices */ - nbs: number; + nbs?: number | bigint; } export interface EncodedBoxReference { /** * index of the app ID in the foreign apps array */ - i: number; + i?: number | bigint; /** * box name */ - n: Uint8Array; + n?: Uint8Array; } /** @@ -102,17 +102,17 @@ export interface EncodedTransaction { /** * fee */ - fee?: number; + fee?: number | bigint; /** * firstValid */ - fv?: number; + fv?: number | bigint; /** * lastValid */ - lv: number; + lv: number | bigint; /** * note @@ -122,7 +122,7 @@ export interface EncodedTransaction { /** * sender */ - snd: Uint8Array; + snd?: Uint8Array; /** * type @@ -202,17 +202,17 @@ export interface EncodedTransaction { /** * voteFirst */ - votefst?: number; + votefst?: number | bigint; /** * voteLast */ - votelst?: number; + votelst?: number | bigint; /** * voteKeyDilution */ - votekd?: number; + votekd?: number | bigint; /** * nonParticipation @@ -222,17 +222,17 @@ export interface EncodedTransaction { /** * assetIndex */ - caid?: number; + caid?: number | bigint; /** * assetIndex (but for asset transfers) */ - xaid?: number; + xaid?: number | bigint; /** * assetIndex (but for asset freezing/unfreezing) */ - faid?: number; + faid?: number | bigint; /** * assetFrozen @@ -257,12 +257,12 @@ export interface EncodedTransaction { /** * appIndex */ - apid?: number; + apid?: number | bigint; /** * appOnComplete */ - apan?: number; + apan?: number | bigint; /** * See EncodedLocalStateSchema type @@ -277,12 +277,12 @@ export interface EncodedTransaction { /** * appForeignApps */ - apfa?: number[]; + apfa?: Array; /** * appForeignAssets */ - apas?: number[]; + apas?: Array; /** * appApprovalProgram @@ -307,7 +307,7 @@ export interface EncodedTransaction { /** * extraPages */ - apep?: number; + apep?: number | bigint; /** * boxes diff --git a/src/types/transactions/index.ts b/src/types/transactions/index.ts index 633723b56..de3e406ca 100644 --- a/src/types/transactions/index.ts +++ b/src/types/transactions/index.ts @@ -1,72 +1,2 @@ -import PaymentTxn from './payment.js'; -import KeyRegistrationTxn from './keyreg.js'; -import { - AssetCreateTransaction as AssetCreateTxn, - AssetConfigurationTransaction as AssetConfigTxn, - AssetDestroyTransaction as AssetDestroyTxn, - AssetFreezeTransaction as AssetFreezeTxn, - AssetTransferTransaction as AssetTransferTxn, -} from './asset.js'; -import { - ApplicationCreateTransaction as AppCreateTxn, - ApplicationUpdateTransaction as AppUpdateTxn, - ApplicationDeleteTransaction as AppDeleteTxn, - ApplicationOptInTransaction as AppOptInTxn, - ApplicationCloseOutTransaction as AppCloseOutTxn, - ApplicationClearStateTransaction as AppClearStateTxn, - ApplicationNoOpTransaction as AppNoOpTxn, -} from './application.js'; -import StateProofTxn from './stateproof.js'; - -// Utilities -export { - TransactionParams, - TransactionType, - SuggestedParams, - BoxReference, -} from './base.js'; -export { - MustHaveSuggestedParams, - MustHaveSuggestedParamsInline, -} from './builder.js'; +export * from './base.js'; export * from './encoded.js'; - -// Transaction types -export { default as PaymentTxn } from './payment.js'; -export { default as KeyRegistrationTxn } from './keyreg.js'; -export { - AssetCreateTransaction as AssetCreateTxn, - AssetConfigurationTransaction as AssetConfigTxn, - AssetDestroyTransaction as AssetDestroyTxn, - AssetFreezeTransaction as AssetFreezeTxn, - AssetTransferTransaction as AssetTransferTxn, -} from './asset.js'; -export { - ApplicationCreateTransaction as AppCreateTxn, - ApplicationUpdateTransaction as AppUpdateTxn, - ApplicationDeleteTransaction as AppDeleteTxn, - ApplicationOptInTransaction as AppOptInTxn, - ApplicationCloseOutTransaction as AppCloseOutTxn, - ApplicationClearStateTransaction as AppClearStateTxn, - ApplicationNoOpTransaction as AppNoOpTxn, -} from './application.js'; -export { default as StateProofTxn } from './stateproof.js'; - -// All possible transaction types -type AnyTransaction = - | PaymentTxn - | KeyRegistrationTxn - | AssetCreateTxn - | AssetConfigTxn - | AssetDestroyTxn - | AssetFreezeTxn - | AssetTransferTxn - | AppCreateTxn - | AppUpdateTxn - | AppDeleteTxn - | AppOptInTxn - | AppCloseOutTxn - | AppClearStateTxn - | AppNoOpTxn - | StateProofTxn; -export default AnyTransaction; diff --git a/src/types/transactions/keyreg.ts b/src/types/transactions/keyreg.ts deleted file mode 100644 index 832258f90..000000000 --- a/src/types/transactions/keyreg.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { TransactionType, TransactionParams } from './base.js'; -import { ConstructTransaction } from './builder.js'; - -type SpecificParameters = Pick< - TransactionParams, - | 'voteKey' - | 'selectionKey' - | 'stateProofKey' - | 'voteFirst' - | 'voteLast' - | 'voteKeyDilution' - | 'nonParticipation' ->; - -interface Overwrites { - type?: TransactionType.keyreg; -} - -type KeyRegistrationTransaction = ConstructTransaction< - SpecificParameters, - Overwrites ->; -export default KeyRegistrationTransaction; diff --git a/src/types/transactions/payment.ts b/src/types/transactions/payment.ts deleted file mode 100644 index 7db74c403..000000000 --- a/src/types/transactions/payment.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TransactionType, TransactionParams } from './base.js'; -import { ConstructTransaction } from './builder.js'; - -type SpecificParameters = Pick< - TransactionParams, - 'receiver' | 'amount' | 'closeRemainderTo' ->; - -interface Overwrites { - type?: TransactionType.pay; -} - -type PaymentTransaction = ConstructTransaction; -export default PaymentTransaction; diff --git a/src/types/transactions/stateproof.ts b/src/types/transactions/stateproof.ts deleted file mode 100644 index 290f8929b..000000000 --- a/src/types/transactions/stateproof.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TransactionType, TransactionParams } from './base.js'; -import { ConstructTransaction } from './builder.js'; - -type SpecificParameters = Pick< - TransactionParams, - 'stateProofType' | 'stateProof' | 'stateProofMessage' ->; - -interface Overwrites { - type?: TransactionType.stpf; -} - -type StateProofTransaction = ConstructTransaction< - SpecificParameters, - Overwrites ->; -export default StateProofTransaction; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4c1fcaddc..57c8a389c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -137,3 +137,57 @@ export function isReactNative() { } return false; } + +export function ensureSafeInteger(value: unknown): number { + if (typeof value === 'undefined') { + throw new Error('Value is undefined'); + } + if (typeof value === 'bigint') { + if ( + value > BigInt(Number.MAX_SAFE_INTEGER) || + value < BigInt(Number.MIN_SAFE_INTEGER) + ) { + throw new Error(`BigInt value ${value} is not a safe integer`); + } + return Number(value); + } + if (typeof value === 'number') { + if (Number.isSafeInteger(value)) { + return value; + } + throw new Error(`Value ${value} is not a safe integer`); + } + throw new Error(`Unexpected type ${typeof value}, ${value}`); +} + +export function ensureSafeUnsignedInteger(value: unknown): number { + const intValue = ensureSafeInteger(value); + if (intValue < 0) { + throw new Error(`Value ${intValue} is negative`); + } + return intValue; +} + +export function ensureBigInt(value: unknown): bigint { + if (typeof value === 'undefined') { + throw new Error('Value is undefined'); + } + if (typeof value === 'bigint') { + return value; + } + if (typeof value === 'number') { + if (!Number.isSafeInteger(value)) { + throw new Error(`Value ${value} is not a safe integer`); + } + return BigInt(value); + } + throw new Error(`Unexpected type ${typeof value}, ${value}`); +} + +export function ensureUint64(value: unknown): bigint { + const bigIntValue = ensureBigInt(value); + if (bigIntValue < 0 || bigIntValue > BigInt('0xffffffffffffffff')) { + throw new Error(`Value ${bigIntValue} is not a uint64`); + } + return bigIntValue; +} diff --git a/tests/10.ABI.ts b/tests/10.ABI.ts index 489ff0666..0e8d5d2a3 100644 --- a/tests/10.ABI.ts +++ b/tests/10.ABI.ts @@ -5,11 +5,10 @@ import { AtomicTransactionComposer, AtomicTransactionComposerStatus, MultisigMetadata, - SuggestedParams, generateAccount, makeBasicAccountTransactionSigner, makeMultiSigAccountTransactionSigner, - makePaymentTxnWithSuggestedParams, + makePaymentTxnWithSuggestedParamsFromObject, } from '../src'; import { ABIAddressType, @@ -457,6 +456,7 @@ describe('ABI encoding', () => { const account = generateAccount(); const sender = 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA'; const sp = { + minFee: 1000, fee: 1000, firstValid: 1, lastValid: 1001, @@ -492,15 +492,18 @@ describe('ABI encoding', () => { const txn = txns[0].txn; // Assert that foreign objects were passed in and ordering was correct. - assert.deepStrictEqual(txn.appForeignApps?.length, 2); - assert.deepStrictEqual(txn.appForeignApps[0], 1); - assert.deepStrictEqual(txn.appForeignApps[1], 2); + assert.deepStrictEqual(txn.applicationCall?.appForeignApps?.length, 2); + assert.deepStrictEqual(txn.applicationCall?.appForeignApps[0], 1n); + assert.deepStrictEqual(txn.applicationCall?.appForeignApps[1], 2n); - assert.deepStrictEqual(txn.appForeignAssets?.length, 1); - assert.deepStrictEqual(txn.appForeignAssets[0], 124); + assert.deepStrictEqual(txn.applicationCall?.appForeignAssets?.length, 1); + assert.deepStrictEqual(txn.applicationCall?.appForeignAssets[0], 124n); - assert.deepStrictEqual(txn.appAccounts?.length, 1); - assert.deepStrictEqual(txn.appAccounts[0], decodeAddress(foreignAcct)); + assert.deepStrictEqual(txn.applicationCall?.appAccounts?.length, 1); + assert.deepStrictEqual( + txn.applicationCall?.appAccounts[0], + decodeAddress(foreignAcct) + ); }); it('should accept at least one signature in the multisig', () => { @@ -517,22 +520,21 @@ describe('ABI encoding', () => { const signer = makeMultiSigAccountTransactionSigner(msig, sks); // Create a transaction - const suggestedParams: SuggestedParams = { + const suggestedParams = { genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', genesisID: '', firstValid: 0, lastValid: 1000, fee: 1000, flatFee: true, + minFee: 1000, }; - const actualTxn = makePaymentTxnWithSuggestedParams( - account1.addr, - account2.addr, - 1000, - undefined, - undefined, - suggestedParams - ); + const actualTxn = makePaymentTxnWithSuggestedParamsFromObject({ + sender: account1.addr, + receiver: account2.addr, + amount: 1000, + suggestedParams, + }); // A multisig with 1 signature should be accepted signer([actualTxn], [0]).then((signedTxns) => { diff --git a/tests/3.Address.ts b/tests/3.Address.ts index df0ebbad9..0ebc8ad4e 100644 --- a/tests/3.Address.ts +++ b/tests/3.Address.ts @@ -111,4 +111,13 @@ describe('address', () => { assert.strictEqual(actual, expected); }); }); + + describe('Zero address', () => { + it('should be correct', () => { + assert.strictEqual( + algosdk.ALGORAND_ZERO_ADDRESS_STRING, + algosdk.encodeAddress(new Uint8Array(32)) + ); + }); + }); }); diff --git a/tests/5.Transaction.ts b/tests/5.Transaction.ts index ee4920cd1..74657b77d 100644 --- a/tests/5.Transaction.ts +++ b/tests/5.Transaction.ts @@ -2,189 +2,262 @@ import assert from 'assert'; import algosdk from '../src/index.js'; import { translateBoxReferences } from '../src/boxStorage.js'; -import * as group from '../src/group.js'; describe('Sign', () => { it('should not modify input arrays', () => { const appArgs = [Uint8Array.from([1, 2]), Uint8Array.from([3, 4])]; - const appAccounts = [ + const accounts = [ '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', ]; - const appForeignApps = [17, 200]; - const appForeignAssets = [7, 8, 9]; + const foreignApps = [17, 200]; + const foreignAssets = [7, 8, 9]; const boxes = [{ appIndex: 0, name: Uint8Array.from([0]) }]; - const o = { + const txn = new algosdk.Transaction({ sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', note: new Uint8Array(0), - type: 'appl', - appIndex: 5, - appArgs, - appAccounts, - appForeignApps, - appForeignAssets, - boxes, - } as any; // Temporary type fix, will be unnecessary in following PR - const txn = new algosdk.Transaction(o); + type: algosdk.TransactionType.appl, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, + appCallParams: { + appId: 5, + onComplete: algosdk.OnApplicationComplete.NoOpOC, + appArgs, + accounts, + foreignApps, + foreignAssets, + boxes, + }, + }); assert.deepStrictEqual(appArgs, [ Uint8Array.from([1, 2]), Uint8Array.from([3, 4]), ]); - assert.deepStrictEqual(appAccounts, [ + assert.deepStrictEqual(accounts, [ '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', ]); - assert.deepStrictEqual(appForeignApps, [17, 200]); - assert.deepStrictEqual(appForeignAssets, [7, 8, 9]); - assert.ok(txn.appArgs !== appArgs); - assert.ok((txn.appAccounts as any) !== appAccounts); - assert.ok(txn.appForeignApps !== appForeignApps); - assert.ok(txn.appForeignAssets !== appForeignAssets); - assert.ok(txn.boxes !== boxes); + assert.deepStrictEqual(foreignApps, [17, 200]); + assert.deepStrictEqual(foreignAssets, [7, 8, 9]); + assert.ok(txn.applicationCall); + assert.ok(txn.applicationCall.appArgs !== appArgs); + assert.ok((txn.applicationCall.appAccounts as any) !== accounts); + assert.ok((txn.applicationCall.appForeignApps as any) !== foreignApps); + assert.ok((txn.applicationCall.appForeignAssets as any) !== foreignAssets); + assert.ok((txn.applicationCall.boxes as any) !== boxes); }); it('should not complain on a missing note', () => { - const o = { - sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, - amount: 847, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - note: new Uint8Array(0), - } as any; // Temporary type fix, will be unnecessary in following PR - assert.doesNotThrow(() => new algosdk.Transaction(o)); + for (const note of [undefined, new Uint8Array()]) { + const txn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + paymentParams: { + receiver: + '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, + note, + }); + assert.deepStrictEqual(txn.note, new Uint8Array()); + } }); it('should respect min tx fee', () => { - const o = { - sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 0, - amount: 847, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - note: new Uint8Array([123, 12, 200]), - } as any; // Temporary type fix, will be unnecessary in following PR - const txn = new algosdk.Transaction(o); - assert.strictEqual(txn.fee, 1000); // 1000 is the v5 min txn fee - const txnEnc = txn.get_obj_for_encoding()!; - assert.strictEqual(txnEnc.fee, 1000); + for (const minFee of [1000n, 1001n]) { + const params: algosdk.TransactionParams = { + type: algosdk.TransactionType.pay, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + paymentParams: { + receiver: + '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 847, + }, + suggestedParams: { + minFee, + fee: 0, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, + }; + const zeroFee = new algosdk.Transaction(params); + assert.strictEqual(zeroFee.fee, minFee); + const encZeroFee = zeroFee.get_obj_for_encoding(); + assert.strictEqual(encZeroFee.fee, minFee); + + params.suggestedParams.fee = minFee; // since this is fee per byte, it will be far greater than minFee + const excessFee = new algosdk.Transaction(params); + assert.ok(excessFee.fee > minFee); + const encExcessFee = excessFee.get_obj_for_encoding(); + assert.strictEqual(encExcessFee.fee, excessFee.fee); + } }); it('should accept 0 fee', () => { - const o = { + const txn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 0, - flatFee: true, - amount: 847, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - note: new Uint8Array([123, 12, 200]), - } as any; // Temporary type fix, will be unnecessary in following PR - const txn = new algosdk.Transaction(o); - assert.equal(txn.fee, 0); + paymentParams: { + receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 0, + flatFee: true, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, + }); + assert.strictEqual(txn.fee, 0n); + const encTxn = txn.get_obj_for_encoding(); + assert.strictEqual(encTxn.fee, undefined); // Should be omitted from encoding }); it('should accept lower than min fee', () => { - const o = { + const txn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, - flatFee: true, - amount: 847, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + paymentParams: { + receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + flatFee: true, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - } as any; // Temporary type fix, will be unnecessary in following PR - const txn = new algosdk.Transaction(o); - assert.equal(txn.fee, 10); + }); + assert.strictEqual(txn.fee, 10n); const txnEnc = txn.get_obj_for_encoding(); - assert.equal(txnEnc!.fee, 10); + assert.strictEqual(txnEnc.fee, 10n); }); it('should not complain on a missing genesisID', () => { - const o = { + const o: algosdk.TransactionParams = { + type: algosdk.TransactionType.pay, sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, - amount: 847, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + paymentParams: { + receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, note: new Uint8Array([123, 12, 200]), - } as any; // Temporary type fix, will be unnecessary in following PR + }; assert.doesNotThrow(() => new algosdk.Transaction(o)); }); it('should not complain on an empty genesisID', () => { - const o = { + const o: algosdk.TransactionParams = { + type: algosdk.TransactionType.pay, sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, - amount: 847, - firstValid: 51, - lastValid: 61, + paymentParams: { + receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: '', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - genesisID: '', }; assert.doesNotThrow(() => new algosdk.Transaction(o)); }); - it('should complain if note isnt Uint8Array', () => { - const o = { + it('should complain if note is not Uint8Array', () => { + const o: algosdk.TransactionParams = { + type: algosdk.TransactionType.pay, sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, - amount: 847, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - note: 'new Uint8Array(0)', - } as any; // Temporary type fix, will be unnecessary in following PR + paymentParams: { + receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, + note: 'abcdefg' as any, + }; assert.throws( () => new algosdk.Transaction(o), - new Error('note must be a Uint8Array.') + new Error('Not a Uint8Array: abcdefg') ); }); it('should not drop a note of all zeros', () => { - const txnWithNote = new algosdk.Transaction( - { - sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + const txnWithNote = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + paymentParams: { receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, firstValid: 51, lastValid: 61, genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - note: new Uint8Array(32), - } as any // Temporary type fix, will be unnecessary in following PR - ); + genesisID: 'mock-network', + }, + note: new Uint8Array(32), + }); - const txnWithoutNote = new algosdk.Transaction( - { - sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + const txnWithoutNote = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + paymentParams: { receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, firstValid: 51, lastValid: 61, genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - } as any // Temporary type fix, will be unnecessary in following PR - ); + genesisID: 'mock-network', + }, + }); const serializedWithNote = algosdk.encodeUnsignedTransaction(txnWithNote); const serializedWithoutNote = @@ -194,30 +267,41 @@ describe('Sign', () => { }); it('should drop a lease of all zeros', () => { - const txnWithLease = new algosdk.Transaction( - { - sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + const txnWithLease = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + paymentParams: { receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, firstValid: 51, lastValid: 61, genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - lease: new Uint8Array(32), - } as any // Temporary type fix, will be unnecessary in following PR - ); + genesisID: 'mock-network', + }, + lease: new Uint8Array(32), + }); - const txnWithoutLease = new algosdk.Transaction( - { - sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + const txnWithoutLease = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + paymentParams: { receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - fee: 10, amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, firstValid: 51, lastValid: 61, genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - } as any // Temporary type fix, will be unnecessary in following PR - ); + genesisID: 'mock-network', + }, + lease: new Uint8Array(32), + }); const serializedWithLease = algosdk.encodeUnsignedTransaction(txnWithLease); const serializedWithoutLease = @@ -230,38 +314,46 @@ describe('Sign', () => { const address = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const txnWithHash = new algosdk.Transaction( - { - sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + const txnWithHash = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + assetConfigParams: { assetIndex: 1234, - assetManager: address, - assetReserve: address, - assetFreeze: address, - assetClawback: address, - type: 'acfg', + manager: address, + reserve: address, + freeze: address, + clawback: address, assetMetadataHash: new Uint8Array(32), - } as any // Temporary type fix, will be unnecessary in following PR - ); - - const txnWithoutHash = new algosdk.Transaction( - { - sender: address, + }, + suggestedParams: { + minFee: 1000, fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, + }); + + const txnWithoutHash = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + assetConfigParams: { assetIndex: 1234, - assetManager: address, - assetReserve: address, - assetFreeze: address, - assetClawback: address, - type: 'acfg', - } as any // Temporary type fix, will be unnecessary in following PR - ); + manager: address, + reserve: address, + freeze: address, + clawback: address, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, + }); const serializedWithHash = algosdk.encodeUnsignedTransaction(txnWithHash); const serializedWithoutHash = @@ -270,192 +362,367 @@ describe('Sign', () => { assert.deepStrictEqual(serializedWithHash, serializedWithoutHash); }); - it('should be able to prettyprint and go toString without throwing', () => { - const o = { - sender: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', - receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + it('should error when the zero address is used for an optional field', () => { + const sender = 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, fee: 10, - amount: 847, firstValid: 51, lastValid: 61, genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - note: new Uint8Array(0), - } as any; // Temporary type fix, will be unnecessary in following PR - const txn = new algosdk.Transaction(o); - // assert package recommends just calling prettyPrint over using assert.doesNotThrow - txn.prettyPrint(); // should not throw - txn.toString(); // also should not throw + genesisID: 'mock-network', + }; + + const expectedError = new Error( + 'Invalid use of the zero address. To omit this value, pass in undefined' + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender, + paymentParams: { + receiver: sender, + amount: 0, + }, + rekeyTo: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + suggestedParams, + }), + expectedError + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender, + paymentParams: { + receiver: sender, + amount: 0, + closeRemainderTo: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + }, + suggestedParams, + }), + expectedError + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.axfer, + sender, + assetTransferParams: { + assetIndex: 9999, + receiver: sender, + amount: 0, + closeRemainderTo: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + }, + suggestedParams, + }), + expectedError + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.axfer, + sender, + assetTransferParams: { + assetIndex: 9999, + receiver: sender, + amount: 0, + assetSender: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + }, + suggestedParams, + }), + expectedError + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender, + assetConfigParams: { + assetIndex: 9999, + manager: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + reserve: sender, + freeze: sender, + clawback: sender, + }, + suggestedParams, + }), + expectedError + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender, + assetConfigParams: { + assetIndex: 9999, + manager: sender, + reserve: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + freeze: sender, + clawback: sender, + }, + suggestedParams, + }), + expectedError + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender, + assetConfigParams: { + assetIndex: 9999, + manager: sender, + reserve: sender, + freeze: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + clawback: sender, + }, + suggestedParams, + }), + expectedError + ); + + assert.throws( + () => + new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender, + assetConfigParams: { + assetIndex: 9999, + manager: sender, + reserve: sender, + freeze: sender, + clawback: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + }, + suggestedParams, + }), + expectedError + ); }); describe('should correctly serialize and deserialize from msgpack representation', () => { it('should correctly serialize and deserialize from msgpack representation', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - receiver: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', - fee: 10, - amount: 847, - firstValid: 51, - lastValid: 61, + paymentParams: { + receiver: + 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - genesisID: '', - }; - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize from msgpack representation with flat fee', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - receiver: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', - fee: 2063, - amount: 847, - firstValid: 51, - lastValid: 61, + paymentParams: { + receiver: + 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 2063, + flatFee: true, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - genesisID: '', - flatFee: true, - }; - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize a state proof transaction from msgpack representation', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.stpf, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - fee: 10, - firstValid: 51, - lastValid: 61, + stateProofParams: { + stateProofType: 0, + stateProof: new Uint8Array([1, 1, 1, 1]), + stateProofMessage: new Uint8Array([0, 0, 0, 0]), + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - voteKey: '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=', - selectionKey: 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=', - voteFirst: 123, - voteLast: 456, - voteKeyDilution: 1234, - genesisID: '', - type: 'stpf', - stateProofType: 0, - stateProof: new Uint8Array([1, 1, 1, 1]), - stateProofMessage: new Uint8Array([0, 0, 0, 0]), - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + // console.log( + // `${expectedTxn.stateProofType} ${expectedTxn.stateProofMessage} ${expectedTxn.stateProof} ${expectedTxn.type}` + // ); + const encRep = expectedTxn.get_obj_for_encoding(); + // console.log( + // `${encRep.sptype} ${encRep.spmsg} ${encRep.sp} ${encRep.type}` + // ); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize a key registration transaction from msgpack representation', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.keyreg, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - fee: 10, - firstValid: 51, - lastValid: 61, + keyregParams: { + voteKey: algosdk.base64ToBytes( + '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=' + ), + selectionKey: algosdk.base64ToBytes( + 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=' + ), + stateProofKey: algosdk.base64ToBytes( + 'mgh7ddGf7dF1Z5/9RDzN/JZZF9yA7XYCKJXvqhwPdvI7pLKh7hizaM5rTC2kizVOpVRIU9PXSLeapvBJ/OxQYA==' + ), + voteFirst: 123, + voteLast: 456, + voteKeyDilution: 1234, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - voteKey: '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=', - selectionKey: 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=', - voteFirst: 123, - voteLast: 456, - voteKeyDilution: 1234, - genesisID: '', - type: 'keyreg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize an offline key registration transaction from msgpack representation', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.keyreg, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - fee: 10, - firstValid: 51, - lastValid: 61, + keyregParams: {}, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - genesisID: '', - type: 'keyreg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize an offline key registration transaction from msgpack representation with explicit nonParticipation=false', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.keyreg, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - fee: 10, - firstValid: 51, - lastValid: 61, + keyregParams: { + nonParticipation: false, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - genesisID: '', - nonParticipation: false, - type: 'keyreg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize a nonparticipating key registration transaction from msgpack representation', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.keyreg, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - fee: 10, - firstValid: 51, - lastValid: 61, + keyregParams: { + nonParticipation: true, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', - nonParticipation: true, - genesisID: '', - type: 'keyreg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); @@ -463,26 +730,33 @@ describe('Sign', () => { it('should correctly serialize and deserialize an asset configuration transaction from msgpack representation', () => { const address = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { - sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - assetIndex: 1234, - assetManager: address, - assetReserve: address, - assetFreeze: address, - assetClawback: address, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', + assetConfigParams: { + assetIndex: 1234, + manager: address, + reserve: address, + freeze: address, + clawback: address, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); @@ -490,33 +764,41 @@ describe('Sign', () => { it('should correctly serialize and deserialize an asset creation transaction from msgpack representation', () => { const address = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - assetTotal: 1000, - assetDefaultFrozen: true, - assetUnitName: 'tests', - assetName: 'testcoin', - assetURL: 'testURL', - assetMetadataHash: new Uint8Array( - algosdk.base64ToBytes('ZkFDUE80blJnTzU1ajFuZEFLM1c2U2djNEFQa2N5Rmg=') - ), - assetManager: address, - assetReserve: address, - assetFreeze: address, - assetClawback: address, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + assetConfigParams: { + manager: address, + reserve: address, + freeze: address, + clawback: address, + total: 2n ** 64n - 1n, + decimals: 5, + defaultFrozen: true, + unitName: 'tests', + assetName: 'testcoin', + assetURL: 'https://example.com', + assetMetadataHash: algosdk.base64ToBytes( + 'ZkFDUE80blJnTzU1ajFuZEFLM1c2U2djNEFQa2N5Rmg=' + ), + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); @@ -524,28 +806,35 @@ describe('Sign', () => { it('should correctly serialize and deserialize an asset transfer transaction from msgpack representation', () => { const address = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { - type: 'axfer', + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.axfer, sender: address, - receiver: address, - amount: 100, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - assetIndex: 1234, - assetSender: address, - closeRemainderTo: address, - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; - const encTxn = algosdk.encodeObj(encRep); - const decEncRep = algosdk.decodeObj(encTxn); - const decTxn = algosdk.Transaction.from_obj_for_encoding( - decEncRep as algosdk.EncodedTransaction - ); - const reencRep = decTxn.get_obj_for_encoding(); - assert.deepStrictEqual(reencRep, encRep); + assetTransferParams: { + assetIndex: 1234, + receiver: address, + amount: 100, + closeRemainderTo: address, + assetSender: address, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = expectedTxn.get_obj_for_encoding(); + const encTxn = algosdk.encodeObj(encRep); + const decEncRep = algosdk.decodeObj(encTxn); + const decTxn = algosdk.Transaction.from_obj_for_encoding( + decEncRep as algosdk.EncodedTransaction + ); + assert.deepStrictEqual(decTxn, expectedTxn); + const reencRep = decTxn.get_obj_for_encoding(); + assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize an application create transaction from msgpack representation', () => { @@ -570,6 +859,7 @@ describe('Sign', () => { note: new TextEncoder().encode('note value'), rekeyTo: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', suggestedParams: { + minFee: 1000, fee: 0, firstValid: 322575, lastValid: 323575, @@ -577,12 +867,13 @@ describe('Sign', () => { genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', }, }); - const encRep = expectedTxn.get_obj_for_encoding()!; + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); @@ -590,25 +881,141 @@ describe('Sign', () => { it('should correctly serialize and deserialize an asset freeze transaction from msgpack representation', () => { const address = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.afrz, sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - type: 'afrz', - freezeAccount: address, - assetIndex: 1, - assetFrozen: true, - } as any; // Temporary type fix, will be unnecessary in following PR - - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + assetFreezeParams: { + assetIndex: 1, + assetFrozen: true, + freezeTarget: address, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = expectedTxn.get_obj_for_encoding(); + const encTxn = algosdk.encodeObj(encRep); + const decEncRep = algosdk.decodeObj(encTxn); + const decTxn = algosdk.Transaction.from_obj_for_encoding( + decEncRep as algosdk.EncodedTransaction + ); + assert.deepStrictEqual(decTxn, expectedTxn); + const reencRep = decTxn.get_obj_for_encoding(); + assert.deepStrictEqual(reencRep, encRep); + }); + + it('should correctly serialize and deserialize a payment transaction when the receiver is the zero address', () => { + const txn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + paymentParams: { + receiver: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 1, + lastValid: 1001, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = txn.get_obj_for_encoding(); + assert.strictEqual(encRep.rcv, undefined); + const encTxn = algosdk.encodeObj(encRep); + + const golden = algosdk.base64ToBytes( + 'iaNhbXTNA0+jZmVlzQgqomZ2AaNnZW6sbW9jay1uZXR3b3JromdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk6IqJsds0D6aRub3RlxAN7DMijc25kxCCgiappIuO5mPrf9s1ICN354CHklE44nqPVxjh4ZokZfqR0eXBlo3BheQ==' + ); + assert.deepStrictEqual(encTxn, golden); + + const decEncRep = algosdk.decodeObj(encTxn); + const decTxn = algosdk.Transaction.from_obj_for_encoding( + decEncRep as algosdk.EncodedTransaction + ); + assert.deepStrictEqual(decTxn, txn); + const reencRep = decTxn.get_obj_for_encoding(); + assert.deepStrictEqual(reencRep, encRep); + }); + + it('should correctly serialize and deserialize an asset transfer transaction when the receiver is the zero address', () => { + const txn = new algosdk.Transaction({ + type: algosdk.TransactionType.axfer, + sender: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + assetTransferParams: { + assetIndex: 9999, + receiver: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 1, + lastValid: 1001, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = txn.get_obj_for_encoding(); + assert.strictEqual(encRep.arcv, undefined); const encTxn = algosdk.encodeObj(encRep); + + const golden = algosdk.base64ToBytes( + 'iqRhYW10zQNPo2ZlZc0ImKJmdgGjZ2VurG1vY2stbmV0d29ya6JnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbNA+mkbm90ZcQDewzIo3NuZMQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6kdHlwZaVheGZlcqR4YWlkzScP' + ); + assert.deepStrictEqual(encTxn, golden); + const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, txn); + const reencRep = decTxn.get_obj_for_encoding(); + assert.deepStrictEqual(reencRep, encRep); + }); + + it('should correctly serialize and deserialize an asset freeze transaction when the freeze account is the zero address', () => { + const txn = new algosdk.Transaction({ + type: algosdk.TransactionType.afrz, + sender: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + assetFreezeParams: { + assetIndex: 9999, + freezeTarget: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + assetFrozen: true, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 1, + lastValid: 1001, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = txn.get_obj_for_encoding(); + assert.strictEqual(encRep.fadd, undefined); + const encTxn = algosdk.encodeObj(encRep); + + const golden = algosdk.base64ToBytes( + 'iqRhZnJ6w6RmYWlkzScPo2ZlZc0IeqJmdgGjZ2VurG1vY2stbmV0d29ya6JnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbNA+mkbm90ZcQDewzIo3NuZMQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6kdHlwZaRhZnJ6' + ); + assert.deepStrictEqual(encTxn, golden); + + const decEncRep = algosdk.decodeObj(encTxn); + const decTxn = algosdk.Transaction.from_obj_for_encoding( + decEncRep as algosdk.EncodedTransaction + ); + assert.deepStrictEqual(decTxn, txn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); @@ -616,108 +1023,161 @@ describe('Sign', () => { it('should correctly serialize and deserialize a first round of 0', () => { const address = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.afrz, sender: address, - fee: 10, - firstValid: 0, - lastValid: 1000, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - type: 'afrz', - freezeAccount: address, - assetIndex: 1, - assetFrozen: true, - } as any; // Temporary type fix, will be unnecessary in following PR - - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + assetFreezeParams: { + assetIndex: 1, + assetFrozen: true, + freezeTarget: address, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 0, + lastValid: 1000, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); + const reencRep = decTxn.get_obj_for_encoding(); + assert.deepStrictEqual(reencRep, encRep); + }); + + it('should correctly serialize and deserialize when the sender is the zero address', () => { + const txn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, + sender: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + paymentParams: { + receiver: + 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 1, + lastValid: 1001, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisID: 'mock-network', + }, + note: new Uint8Array([123, 12, 200]), + }); + const encRep = txn.get_obj_for_encoding(); + assert.strictEqual(encRep.snd, undefined); + const encTxn = algosdk.encodeObj(encRep); + + const golden = algosdk.base64ToBytes( + 'iaNhbXTNA0+jZmVlzQgqomZ2AaNnZW6sbW9jay1uZXR3b3JromdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk6IqJsds0D6aRub3RlxAN7DMijcmN2xCCgiappIuO5mPrf9s1ICN354CHklE44nqPVxjh4ZokZfqR0eXBlo3BheQ==' + ); + assert.deepStrictEqual(encTxn, golden); + + const decEncRep = algosdk.decodeObj(encTxn); + const decTxn = algosdk.Transaction.from_obj_for_encoding( + decEncRep as algosdk.EncodedTransaction + ); + assert.deepStrictEqual(decTxn, txn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('reserializes correctly no genesis ID', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - receiver: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', - fee: 10, - amount: 847, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + paymentParams: { + receiver: + 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, note: new Uint8Array([123, 12, 200]), - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('reserializes correctly zero amount', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - receiver: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', - fee: 10, - amount: 0, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + paymentParams: { + receiver: + 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + amount: 0, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const encRep = expectedTxn.get_obj_for_encoding()!; + }); + const encRep = expectedTxn.get_obj_for_encoding(); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( decEncRep as algosdk.EncodedTransaction ); + assert.deepStrictEqual(decTxn, expectedTxn); const reencRep = decTxn.get_obj_for_encoding(); assert.deepStrictEqual(reencRep, encRep); }); it('should correctly serialize and deserialize group object', () => { - const o = { + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender: 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU', - receiver: 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', - fee: 10, - amount: 0, - firstValid: 51, - lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + paymentParams: { + receiver: + 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM', + amount: 847, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 51, + lastValid: 61, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'mock-network', + }, note: new Uint8Array([123, 12, 200]), - } as any; // Temporary type fix, will be unnecessary in following PR - const tx = new algosdk.Transaction(o); - - { - const expectedTxg = new group.TxGroup([tx.rawTxID(), tx.rawTxID()]); - const encRep = expectedTxg.get_obj_for_encoding(); - const encTxg = algosdk.encodeObj(encRep); - const decEncRep = algosdk.decodeObj(encTxg) as any; // Temporary type fix, will be unnecessary in following PR; - const decTxg = group.TxGroup.from_obj_for_encoding(decEncRep); - const reencRep = decTxg.get_obj_for_encoding(); - assert.deepStrictEqual(reencRep, encRep); - } + }); - { - const expectedTxn = tx; - expectedTxn.group = tx.rawTxID(); - const encRep = expectedTxn.get_obj_for_encoding()!; - const encTxn = algosdk.encodeObj(encRep); - const decEncRep = algosdk.decodeObj(encTxn); - const decTxn = algosdk.Transaction.from_obj_for_encoding( - decEncRep as algosdk.EncodedTransaction - ); - const reencRep = decTxn.get_obj_for_encoding(); - assert.deepStrictEqual(reencRep, encRep); - } + expectedTxn.group = algosdk.computeGroupID([expectedTxn]); + const encRep = expectedTxn.get_obj_for_encoding(); + const encTxn = algosdk.encodeObj(encRep); + const decEncRep = algosdk.decodeObj(encTxn); + const decTxn = algosdk.Transaction.from_obj_for_encoding( + decEncRep as algosdk.EncodedTransaction + ); + assert.deepStrictEqual(decTxn, expectedTxn); + const reencRep = decTxn.get_obj_for_encoding(); + assert.deepStrictEqual(reencRep, encRep); }); }); @@ -727,47 +1187,42 @@ describe('Sign', () => { 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; const receiver = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; - const fee = 10; const amount = 847; - const firstValid = 51; - const lastValid = 61; - const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; + const closeRemainderTo = + 'RJB34GFP2BR5YJHKXDUMA2W4UX7DFUUR7QZ4AU5TWVKNA2KTNZIYB4BMRM'; const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - let closeRemainderTo; - const o = { + const note = new Uint8Array([123, 12, 200]); + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender, - receiver, - fee, - amount, - closeRemainderTo, - firstValid, - lastValid, + paymentParams: { + receiver, + amount, + closeRemainderTo, + }, note, - genesisHash, - genesisID, rekeyTo, - }; - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const actualTxn = algosdk.makePaymentTxnWithSuggestedParams( + suggestedParams, + }); + const actualTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender, receiver, amount, closeRemainderTo, note, suggestedParams, - rekeyTo - ); - assert.deepStrictEqual(expectedTxn, actualTxn); + rekeyTo, + }); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should be able to use helper to make a payment transaction with BigInt amount', () => { @@ -775,47 +1230,42 @@ describe('Sign', () => { 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; const receiver = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; - const fee = 10; const amount = 0xffffffffffffffffn; - const firstValid = 51; - const lastValid = 61; const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - let closeRemainderTo; - const o = { + const closeRemainderTo = + 'RJB34GFP2BR5YJHKXDUMA2W4UX7DFUUR7QZ4AU5TWVKNA2KTNZIYB4BMRM'; + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.pay, sender, - receiver, - fee, - amount, - closeRemainderTo, - firstValid, - lastValid, + paymentParams: { + receiver, + amount, + closeRemainderTo, + }, note, - genesisHash, - genesisID, rekeyTo, - }; - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const actualTxn = algosdk.makePaymentTxnWithSuggestedParams( + suggestedParams, + }); + const actualTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender, receiver, amount, closeRemainderTo, note, suggestedParams, - rekeyTo - ); - assert.deepStrictEqual(expectedTxn, actualTxn); + rekeyTo, + }); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should throw if payment amount is too large', () => { @@ -823,234 +1273,216 @@ describe('Sign', () => { 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; const receiver = 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; - const fee = 10; const amount = 0x10000000000000000n; - const firstValid = 51; - const lastValid = 61; const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - let closeRemainderTo; - const o = { + const closeRemainderTo = + 'RJB34GFP2BR5YJHKXDUMA2W4UX7DFUUR7QZ4AU5TWVKNA2KTNZIYB4BMRM'; + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const o: algosdk.TransactionParams = { + type: algosdk.TransactionType.pay, sender, - receiver, - fee, - amount, - closeRemainderTo, - firstValid, - lastValid, + paymentParams: { + receiver, + amount, + closeRemainderTo, + }, note, - genesisHash, - genesisID, rekeyTo, + suggestedParams, }; assert.throws( () => new algosdk.Transaction(o), - new Error( - 'Amount must be a positive number and smaller than 2^64-1. If the number is larger than 2^53-1, use bigint.' - ) + new Error('Value 18446744073709551616 is not a uint64') ); }); it('should be able to use helper to make a keyreg transaction', () => { const sender = 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; - const fee = 10; - const firstValid = 51; - const lastValid = 61; - const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; - const rekeyTo = - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const voteKey = '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE='; - const selectionKey = 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4='; + const voteKey = algosdk.base64ToBytes( + '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=' + ); + const selectionKey = algosdk.base64ToBytes( + 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=' + ); + const stateProofKey = algosdk.base64ToBytes( + 'mgh7ddGf7dF1Z5/9RDzN/JZZF9yA7XYCKJXvqhwPdvI7pLKh7hizaM5rTC2kizVOpVRIU9PXSLeapvBJ/OxQYA==' + ); const voteKeyDilution = 1234; const voteFirst = 123; const voteLast = 456; - const o = { - sender, - fee, - firstValid, - lastValid, - note, - genesisHash, - voteKey, - selectionKey, - voteFirst, - voteLast, - voteKeyDilution, - genesisID, - rekeyTo, - type: 'keyreg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, + const note = new Uint8Array([123, 12, 200]); + const rekeyTo = + 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, }; - const actualTxn = algosdk.makeKeyRegistrationTxnWithSuggestedParams( + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.keyreg, sender, - note, - voteKey, - selectionKey, - voteFirst, - voteLast, - voteKeyDilution, + keyregParams: { + voteKey, + selectionKey, + stateProofKey, + voteFirst, + voteLast, + voteKeyDilution, + }, suggestedParams, - rekeyTo - ); - assert.deepStrictEqual(expectedTxn, actualTxn); + note, + rekeyTo, + }); + const actualTxn = + algosdk.makeKeyRegistrationTxnWithSuggestedParamsFromObject({ + sender, + note, + voteKey, + selectionKey, + stateProofKey, + voteFirst, + voteLast, + voteKeyDilution, + suggestedParams, + rekeyTo, + }); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should be able to use helper to make an offline keyreg transaction', () => { const sender = 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; - const fee = 10; - const firstValid = 51; - const lastValid = 61; const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const voteKey = undefined; - const selectionKey = undefined; - const voteKeyDilution = undefined; - const voteFirst = undefined; - const voteLast = undefined; - const o = { - sender, - fee, - firstValid, - lastValid, - note, - genesisHash, - voteKey, - selectionKey, - voteFirst, - voteLast, - voteKeyDilution, - genesisID, - rekeyTo, - type: 'keyreg', - nonParticipation: false, - } as any; // Temporary type fix, will be unnecessary in following PR + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; assert.throws( () => new algosdk.Transaction({ - ...o, - voteKey: '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=', + type: algosdk.TransactionType.keyreg, + sender, + keyregParams: { + voteKey: algosdk.base64ToBytes( + '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=' + ), + }, + suggestedParams, + note, + rekeyTo, }), new Error( - 'online key registration missing at least one of the following fields: ' + + 'Online key registration missing at least one of the following fields: ' + 'voteKey, selectionKey, voteFirst, voteLast, voteKeyDilution' ) ); - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const actualTxn = algosdk.makeKeyRegistrationTxnWithSuggestedParams( + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.keyreg, sender, - note, - voteKey, - selectionKey, - voteFirst, - voteLast, - voteKeyDilution, + keyregParams: {}, suggestedParams, - rekeyTo - ); - assert.deepStrictEqual(expectedTxn, actualTxn); + note, + rekeyTo, + }); + const actualTxn = + algosdk.makeKeyRegistrationTxnWithSuggestedParamsFromObject({ + sender, + note, + suggestedParams, + rekeyTo, + }); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should be able to use helper to make a nonparticipating keyreg transaction', () => { const sender = 'XMHLMNAVJIMAW2RHJXLXKKK4G3J3U6VONNO3BTAQYVDC3MHTGDP3J5OCRU'; - const fee = 10; - const firstValid = 51; - const lastValid = 61; const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const voteKey = '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE='; - const selectionKey = 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4='; - const voteKeyDilution = 1234; - const voteFirst = 123; - const voteLast = 456; - const nonParticipation = true; - const o = { - sender, - fee, - firstValid, - lastValid, - note, - genesisHash, - nonParticipation, - genesisID, - rekeyTo, - type: 'keyreg', - } as any; // Temporary type fix, will be unnecessary in following PR + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; assert.throws( () => new algosdk.Transaction({ - ...o, - voteKey, - selectionKey, - voteFirst, - voteLast, - voteKeyDilution, + type: algosdk.TransactionType.keyreg, + sender, + keyregParams: { + voteKey: algosdk.base64ToBytes( + '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=' + ), + selectionKey: algosdk.base64ToBytes( + 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=' + ), + voteFirst: 123, + voteLast: 456, + voteKeyDilution: 1234, + nonParticipation: true, + }, + suggestedParams, + note, + rekeyTo, }), new Error( 'nonParticipation is true but participation params are present.' ) ); - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const actualTxn = algosdk.makeKeyRegistrationTxnWithSuggestedParams( + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.keyreg, sender, - note, - undefined, - undefined, - undefined, - undefined, - undefined, + keyregParams: { + nonParticipation: true, + }, suggestedParams, + note, rekeyTo, - nonParticipation - ); - assert.deepStrictEqual(expectedTxn, actualTxn); + }); + const actualTxn = + algosdk.makeKeyRegistrationTxnWithSuggestedParamsFromObject({ + sender, + note, + suggestedParams, + rekeyTo, + nonParticipation: true, + }); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should be able to use helper to make an asset create transaction', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const defaultFrozen = false; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; const total = 100; - const decimals = 0; + const decimals = 1; + const manager = addr; const reserve = addr; const freeze = addr; const clawback = addr; @@ -1060,69 +1492,65 @@ describe('Sign', () => { const assetMetadataHash = new Uint8Array( algosdk.base64ToBytes('dGVzdGhhc2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') ); - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const o = { + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, sender: addr, - fee, - firstValid, - lastValid, + assetConfigParams: { + defaultFrozen, + total, + decimals, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + }, + suggestedParams, note, - genesisHash, - assetTotal: total, - assetDecimals: decimals, - assetDefaultFrozen: defaultFrozen, - assetUnitName: unitName, - assetName, - assetURL, - assetMetadataHash, - assetManager: addr, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - genesisID, rekeyTo, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const actualTxn = algosdk.makeAssetCreateTxnWithSuggestedParams( - addr, - note, - total, - decimals, - defaultFrozen, - addr, - reserve, - freeze, - clawback, - unitName, - assetName, - assetURL, - assetMetadataHash, - suggestedParams, - rekeyTo + }); + const actualTxn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject( + { + sender: addr, + note, + total, + decimals, + defaultFrozen, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + suggestedParams, + rekeyTo, + } ); - assert.deepStrictEqual(expectedTxn, actualTxn); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should be able to use helper to make an asset create transaction with BigInt total', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const defaultFrozen = false; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; const total = 0xffffffffffffffffn; - const decimals = 0; + const decimals = 1; + const manager = addr; const reserve = addr; const freeze = addr; const clawback = addr; @@ -1132,69 +1560,65 @@ describe('Sign', () => { const assetMetadataHash = new Uint8Array( algosdk.base64ToBytes('dGVzdGhhc2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') ); - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const o = { + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, sender: addr, - fee, - firstValid, - lastValid, + assetConfigParams: { + defaultFrozen, + total, + decimals, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + }, + suggestedParams, note, - genesisHash, - assetTotal: total, - assetDecimals: decimals, - assetDefaultFrozen: defaultFrozen, - assetUnitName: unitName, - assetName, - assetURL, - assetMetadataHash, - assetManager: addr, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - genesisID, rekeyTo, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const actualTxn = algosdk.makeAssetCreateTxnWithSuggestedParams( - addr, - note, - total, - decimals, - defaultFrozen, - addr, - reserve, - freeze, - clawback, - unitName, - assetName, - assetURL, - assetMetadataHash, - suggestedParams, - rekeyTo + }); + const actualTxn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject( + { + sender: addr, + note, + total, + decimals, + defaultFrozen, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + suggestedParams, + rekeyTo, + } ); - assert.deepStrictEqual(expectedTxn, actualTxn); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should throw if asset creation total is too large', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const defaultFrozen = false; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; const total = 0x10000000000000000n; - const decimals = 0; + const decimals = 1; + const manager = addr; const reserve = addr; const freeze = addr; const clawback = addr; @@ -1204,415 +1628,318 @@ describe('Sign', () => { const assetMetadataHash = new Uint8Array( algosdk.base64ToBytes('dGVzdGhhc2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') ); - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const o = { + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const params: algosdk.TransactionParams = { + type: algosdk.TransactionType.acfg, sender: addr, - fee, - firstValid, - lastValid, + assetConfigParams: { + defaultFrozen, + total, + decimals, + manager, + reserve, + freeze, + clawback, + unitName, + assetName, + assetURL, + assetMetadataHash, + }, + suggestedParams, note, - genesisHash, - assetTotal: total, - assetDecimals: decimals, - assetDefaultFrozen: defaultFrozen, - assetUnitName: unitName, - assetName, - assetURL, - assetMetadataHash, - assetManager: addr, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - genesisID, rekeyTo, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR + }; assert.throws( - () => new algosdk.Transaction(o), - new Error( - 'Total asset issuance must be a positive number and smaller than 2^64-1. If the number is larger than 2^53-1, use bigint.' - ) + () => new algosdk.Transaction(params), + new Error('Value 18446744073709551616 is not a uint64') ); }); it('should fail to make an asset create transaction with an invalid assetMetadataHash', () => { - const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; - const defaultFrozen = false; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; - const total = 100; - const decimals = 0; - const reserve = addr; - const freeze = addr; - const clawback = addr; - const unitName = 'tst'; - const assetName = 'testcoin'; - const assetURL = 'testURL'; - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; - const note = new Uint8Array([123, 12, 200]); - const rekeyTo = - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const txnTemplate = { - sender: addr, - fee, - firstValid, - lastValid, - note, - genesisHash, - assetTotal: total, - assetDecimals: decimals, - assetDefaultFrozen: defaultFrozen, - assetUnitName: unitName, - assetName, - assetURL, - assetManager: addr, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - genesisID, - rekeyTo, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR - assert.doesNotThrow(() => { - const txnParams = { - assetMetadataHash: '', - ...txnTemplate, + function paramsWithMetadataHash( + assetMetadataHash: any + ): algosdk.TransactionParams { + const addr = + 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; + return { + type: algosdk.TransactionType.acfg, + sender: addr, + assetConfigParams: { + defaultFrozen: false, + total: 100, + decimals: 0, + manager: addr, + reserve: addr, + freeze: addr, + clawback: addr, + unitName: 'tst', + assetName: 'testcoin', + assetURL: 'https://example.com', + assetMetadataHash, + }, + suggestedParams: { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }, + note: new Uint8Array([123, 12, 200]), + rekeyTo: 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM', }; + } + assert.doesNotThrow(() => { + const txnParams = paramsWithMetadataHash(undefined); return new algosdk.Transaction(txnParams); }); assert.throws(() => { - const txnParams = { - assetMetadataHash: 'abc', - ...txnTemplate, - }; - return new algosdk.Transaction(txnParams); - }); - assert.doesNotThrow(() => { - const txnParams = { - assetMetadataHash: 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh', - ...txnTemplate, - }; + const txnParams = paramsWithMetadataHash(new Uint8Array()); return new algosdk.Transaction(txnParams); }); assert.throws(() => { - const txnParams = { - assetMetadataHash: 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh1', - ...txnTemplate, - }; + const txnParams = paramsWithMetadataHash(Uint8Array.from([1, 2, 3])); return new algosdk.Transaction(txnParams); }); assert.doesNotThrow(() => { - const txnParams = { - assetMetadataHash: new Uint8Array(0), - ...txnTemplate, - }; + const txnParams = paramsWithMetadataHash(new Uint8Array(32)); return new algosdk.Transaction(txnParams); }); assert.throws(() => { - const txnParams = { - assetMetadataHash: new Uint8Array([1, 2, 3]), - ...txnTemplate, - }; + const txnParams = paramsWithMetadataHash(new Uint8Array(33)); return new algosdk.Transaction(txnParams); }); - assert.doesNotThrow(() => { - const txnParams = { - assetMetadataHash: new Uint8Array(32), - ...txnTemplate, - }; + assert.throws(() => { + const txnParams = paramsWithMetadataHash(''); return new algosdk.Transaction(txnParams); }); assert.throws(() => { - const txnParams = { - assetMetadataHash: new Uint8Array(33), - ...txnTemplate, - }; + const txnParams = paramsWithMetadataHash( + 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh' + ); return new algosdk.Transaction(txnParams); }); }); it('should be able to use helper to make an asset config transaction', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const assetIndex = 1234; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; const manager = addr; const reserve = addr; const freeze = addr; const clawback = addr; - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const o = { - sender: addr, - fee, - firstValid, - lastValid, - genesisHash, - genesisID, - assetIndex, - assetManager: manager, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - type: 'acfg', - note, - rekeyTo, - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, }; - const actualTxn = algosdk.makeAssetConfigTxnWithSuggestedParams( - addr, - note, - assetIndex, - manager, - reserve, - freeze, - clawback, + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, + sender: addr, + assetConfigParams: { + assetIndex, + manager, + reserve, + freeze, + clawback, + }, suggestedParams, + note, rekeyTo, - true + }); + const actualTxn = algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject( + { + sender: addr, + note, + assetIndex, + manager, + reserve, + freeze, + clawback, + suggestedParams, + rekeyTo, + } ); - assert.deepStrictEqual(expectedTxn, actualTxn); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should throw when disobeying strict address checking in make asset config', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const assetIndex = 1234; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; - const manager = addr; - let reserve; - let freeze; - const clawback = addr; - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; - const note = new Uint8Array([123, 12, 200]); - let threw = false; - try { - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - algosdk.makeAssetConfigTxnWithSuggestedParams( - addr, - note, + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + assert.throws(() => + algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ + sender: addr, assetIndex, - manager, - reserve, - freeze, - clawback, - suggestedParams - ); - } catch { - threw = true; - } - assert.deepStrictEqual(true, threw); + manager: addr, + reserve: addr, + freeze: undefined, + clawback: undefined, + suggestedParams, + }) + ); + + // does not throw when flag enabled + algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ + sender: addr, + assetIndex, + manager: addr, + reserve: addr, + freeze: undefined, + clawback: undefined, + suggestedParams, + allowRoleRemoval: true, + }); }); it('should be able to use helper to make an asset destroy transaction', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const assetIndex = 1234; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const o = { + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.acfg, sender: addr, - fee, - firstValid, - lastValid, - genesisHash, - genesisID, - assetIndex, - type: 'acfg', + assetConfigParams: { + assetIndex, + }, + suggestedParams, note, rekeyTo, - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const actualTxn = algosdk.makeAssetDestroyTxnWithSuggestedParams( - addr, - note, - assetIndex, - suggestedParams, - rekeyTo - ); - assert.deepStrictEqual(expectedTxn, actualTxn); + }); + const actualTxn = + algosdk.makeAssetDestroyTxnWithSuggestedParamsFromObject({ + sender: addr, + note, + assetIndex, + suggestedParams, + rekeyTo, + }); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should be able to use helper to make an asset transfer transaction', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const sender = addr; - const recipient = addr; + const receiver = addr; const assetSender = addr; const closeRemainderTo = addr; const assetIndex = 1234; const amount = 100; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const o = { - type: 'axfer', - sender, - receiver: recipient, - amount, - fee, - firstValid, - lastValid, - genesisHash, - genesisID, - assetIndex, - note, - assetSender, - closeRemainderTo, - rekeyTo, - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, }; - - const actualTxn = algosdk.makeAssetTransferTxnWithSuggestedParams( + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.axfer, sender, - recipient, - closeRemainderTo, - assetSender, - amount, - note, - assetIndex, + assetTransferParams: { + receiver, + assetSender, + closeRemainderTo, + assetIndex, + amount, + }, suggestedParams, - rekeyTo - ); - assert.deepStrictEqual(expectedTxn, actualTxn); + note, + rekeyTo, + }); + const actualTxn = + algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ + sender, + receiver, + closeRemainderTo, + assetSender, + amount, + note, + assetIndex, + suggestedParams, + rekeyTo, + }); + assert.deepStrictEqual(actualTxn, expectedTxn); }); it('should be able to use helper to make an asset freeze transaction', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const fee = 10; const assetIndex = 1234; - const genesisHash = 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='; const freezeTarget = addr; - const genesisID = ''; - const firstValid = 322575; - const lastValid = 322575; const assetFrozen = true; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - const o = { + const suggestedParams: algosdk.SuggestedParams = { + minFee: 1000, + fee: 10, + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisID: 'testnet-v1.0', + firstValid: 51, + lastValid: 61, + }; + const expectedTxn = new algosdk.Transaction({ + type: algosdk.TransactionType.afrz, sender: addr, - fee, - firstValid, - lastValid, - genesisHash, - type: 'afrz', - freezeAccount: freezeTarget, - assetIndex, - assetFrozen, + assetFreezeParams: { + freezeTarget, + assetFrozen, + assetIndex, + }, + suggestedParams, note, - genesisID, rekeyTo, - } as any; // Temporary type fix, will be unnecessary in following PR - const expectedTxn = new algosdk.Transaction(o); - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - - const actualTxn = algosdk.makeAssetFreezeTxnWithSuggestedParams( - addr, - note, - assetIndex, - freezeTarget, - assetFrozen, - suggestedParams, - rekeyTo + }); + const actualTxn = algosdk.makeAssetFreezeTxnWithSuggestedParamsFromObject( + { + sender: addr, + note, + assetIndex, + freezeTarget, + assetFrozen, + suggestedParams, + rekeyTo, + } ); assert.deepStrictEqual(expectedTxn, actualTxn); }); - it('should be able to use helper to assign group ID to mixed Transaction and Dict', () => { - const suggestedParams = { - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - genesisID: '', - firstValid: 322575, - lastValid: 322575 + 1000, - fee: 1000, - flatFee: true, - }; - - const helperTx = algosdk.makePaymentTxnWithSuggestedParams( - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM', - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM', - 1000, - undefined, - new Uint8Array(0), - suggestedParams - ); - const dictTx = { - sender: 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM', - receiver: 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM', - fee: 1000, - flatFee: true, - amount: 0, - firstValid: 322575, - lastValid: 322575 + 1000, - genesisID: '', - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - type: 'pay', - }; - - // Store both transactions - const txns = [helperTx, dictTx] as any[]; // Temporary type fix, will be unnecessary in following PR - - // Group both transactions - const txgroup = algosdk.assignGroupID(txns); - - assert.deepStrictEqual(txgroup[0].group, txgroup[1].group); - }); it('should be able to translate box references to encoded references', () => { const testCases: Array< [ @@ -1637,8 +1964,8 @@ describe('Sign', () => { [100], 9999, [ - { i: 0, n: Uint8Array.from([0, 1, 2, 3]) }, - { i: 0, n: Uint8Array.from([4, 5, 6, 7]) }, + { n: Uint8Array.from([0, 1, 2, 3]) }, + { n: Uint8Array.from([4, 5, 6, 7]) }, ], ], [ diff --git a/tests/6.Multisig.ts b/tests/6.Multisig.ts index 59e06212b..1bd9ae24e 100644 --- a/tests/6.Multisig.ts +++ b/tests/6.Multisig.ts @@ -1,12 +1,7 @@ /* eslint-env mocha */ import assert from 'assert'; import algosdk from '../src/index'; -import { - MultisigTransaction, - MULTISIG_NO_MUTATE_ERROR_MSG, - MULTISIG_USE_PARTIAL_SIGN_ERROR_MSG, - MULTISIG_SIGNATURE_LENGTH_ERROR_MSG, -} from '../src/multisig'; +import { MULTISIG_SIGNATURE_LENGTH_ERROR_MSG } from '../src/multisig'; const sampleAccount1 = algosdk.mnemonicToSecretKey( 'auction inquiry lava second expand liberty glass involve ginger illness length room item discover ahead table doctor term tackle cement bonus profit right above catch' @@ -59,6 +54,7 @@ describe('Multisig Functionality', () => { closeRemainderTo: 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA', suggestedParams: { + minFee: 1000, fee: 1000, flatFee: true, firstValid: 62229, @@ -93,6 +89,7 @@ describe('Multisig Functionality', () => { closeRemainderTo: 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA', suggestedParams: { + minFee: 1000, fee: 1000, flatFee: true, firstValid: 62229, @@ -478,235 +475,4 @@ describe('Multisig Functionality', () => { }, new Error('Invalid multisig transaction, multisig structure missing at index 1')); }); }); - - describe('read-only transaction methods should work as expected on multisig transactions', () => { - let stdPaymentTxn: algosdk.Transaction; - let msigPaymentTxn: MultisigTransaction; - - let stdKeyregTxn: algosdk.Transaction; - let msigKeyregTxn: MultisigTransaction; - - // Create a multisig transaction to use for each test - beforeEach(() => { - const paymentTxnObj = { - snd: algosdk.decodeAddress( - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' - ).publicKey, - rcv: algosdk.decodeAddress( - 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI' - ).publicKey, - fee: 1000, - amt: 1000, - close: algosdk.decodeAddress( - 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA' - ).publicKey, - gh: algosdk.base64ToBytes( - '/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp0=' - ), - fv: 62229, - lv: 63229, - gen: 'devnet-v38.0', - type: 'pay', - note: algosdk.base64ToBytes('RSYiABhShvs='), - }; - - stdPaymentTxn = algosdk.Transaction.from_obj_for_encoding(paymentTxnObj); - msigPaymentTxn = MultisigTransaction.from_obj_for_encoding(paymentTxnObj); - - const keyregTxnObj = { - snd: algosdk.decodeAddress( - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' - ).publicKey, - fee: 10, - fv: 51, - lv: 61, - note: Uint8Array.from([123, 12, 200]), - gh: algosdk.base64ToBytes( - 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' - ), - votekey: algosdk.base64ToBytes( - '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=' - ), - selkey: algosdk.base64ToBytes( - 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=' - ), - votefst: 123, - votelst: 456, - votekd: 1234, - gen: 'devnet-v38.0', - type: 'keyreg', - }; - - stdKeyregTxn = algosdk.Transaction.from_obj_for_encoding(keyregTxnObj); - msigKeyregTxn = MultisigTransaction.from_obj_for_encoding(keyregTxnObj); - }); - - it('`estimateSize` method should match expected result', () => { - assert.strictEqual( - stdPaymentTxn.estimateSize(), - msigPaymentTxn.estimateSize() - ); - assert.strictEqual( - stdKeyregTxn.estimateSize(), - msigKeyregTxn.estimateSize() - ); - }); - - it('`txID` method should match expected result', () => { - assert.strictEqual(stdPaymentTxn.txID(), msigPaymentTxn.txID()); - assert.strictEqual(stdKeyregTxn.txID(), msigKeyregTxn.txID()); - }); - - it('`toString` method should match expected result', () => { - assert.strictEqual(stdPaymentTxn.toString(), msigPaymentTxn.toString()); - assert.strictEqual(stdKeyregTxn.toString(), msigKeyregTxn.toString()); - }); - }); - - describe('inherited MultisigTransaction methods that mutate transactions should throw errors', () => { - let msigPaymentTxn: MultisigTransaction; - let msigKeyregTxn: MultisigTransaction; - - // Create a multisig transaction to use for each test - beforeEach(() => { - const paymentTxnObj = { - snd: algosdk.decodeAddress( - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' - ).publicKey, - rcv: algosdk.decodeAddress( - 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI' - ).publicKey, - fee: 1000, - amt: 1000, - close: algosdk.decodeAddress( - 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA' - ).publicKey, - gh: algosdk.base64ToBytes( - '/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp0=' - ), - fv: 62229, - lv: 63229, - gen: 'devnet-v38.0', - type: 'pay', - note: algosdk.base64ToBytes('RSYiABhShvs='), - }; - - const keyregTxnObj = { - snd: algosdk.decodeAddress( - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' - ).publicKey, - fee: 10, - fv: 51, - lv: 61, - note: Uint8Array.from([123, 12, 200]), - gh: algosdk.base64ToBytes( - 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' - ), - votekey: algosdk.base64ToBytes( - '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=' - ), - selkey: algosdk.base64ToBytes( - 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=' - ), - votefst: 123, - votelst: 456, - votekd: 1234, - gen: 'devnet-v38.0', - type: 'keyreg', - }; - - msigPaymentTxn = MultisigTransaction.from_obj_for_encoding(paymentTxnObj); - msigKeyregTxn = MultisigTransaction.from_obj_for_encoding(keyregTxnObj); - }); - - it('error should be thrown when attempting to add a lease to a transaction', () => { - assert.throws( - msigPaymentTxn.addLease, - new Error(MULTISIG_NO_MUTATE_ERROR_MSG) - ); - assert.throws( - msigKeyregTxn.addLease, - new Error(MULTISIG_NO_MUTATE_ERROR_MSG) - ); - }); - - it('error should be thrown when attempting to add a rekey to a transaction', () => { - assert.throws( - msigPaymentTxn.addRekey, - new Error(MULTISIG_NO_MUTATE_ERROR_MSG) - ); - assert.throws( - msigKeyregTxn.addRekey, - new Error(MULTISIG_NO_MUTATE_ERROR_MSG) - ); - }); - }); - - describe('error should be thrown when attempting to sign a transaction', () => { - let msigPaymentTxn: MultisigTransaction; - let msigKeyregTxn: MultisigTransaction; - - // Create a multisig transaction to use for each test - beforeEach(() => { - const paymentTxnObj = { - snd: algosdk.decodeAddress( - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' - ).publicKey, - rcv: algosdk.decodeAddress( - 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI' - ).publicKey, - fee: 1000, - amt: 1000, - close: algosdk.decodeAddress( - 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA' - ).publicKey, - gh: algosdk.base64ToBytes( - '/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp0=' - ), - fv: 62229, - lv: 63229, - gen: 'devnet-v38.0', - type: 'pay', - note: algosdk.base64ToBytes('RSYiABhShvs='), - }; - - const keyregTxnObj = { - snd: algosdk.decodeAddress( - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' - ).publicKey, - fee: 10, - fv: 51, - lv: 61, - note: Uint8Array.from([123, 12, 200]), - gh: algosdk.base64ToBytes( - 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' - ), - votekey: algosdk.base64ToBytes( - '5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKE=' - ), - selkey: algosdk.base64ToBytes( - 'oImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX4=' - ), - votefst: 123, - votelst: 456, - votekd: 1234, - gen: 'devnet-v38.0', - type: 'keyreg', - }; - - msigPaymentTxn = MultisigTransaction.from_obj_for_encoding(paymentTxnObj); - msigKeyregTxn = MultisigTransaction.from_obj_for_encoding(keyregTxnObj); - }); - - it('signTxn method should throw an error', () => { - assert.throws( - () => msigPaymentTxn.signTxn(new Uint8Array()), - new Error(MULTISIG_USE_PARTIAL_SIGN_ERROR_MSG) - ); - assert.throws( - () => msigKeyregTxn.signTxn(new Uint8Array()), - new Error(MULTISIG_USE_PARTIAL_SIGN_ERROR_MSG) - ); - }); - }); }); diff --git a/tests/7.AlgoSDK.ts b/tests/7.AlgoSDK.ts index 1cc31d1d7..0cad7910c 100644 --- a/tests/7.AlgoSDK.ts +++ b/tests/7.AlgoSDK.ts @@ -31,6 +31,7 @@ describe('Algosdk (AKA end to end)', () => { it('should not mutate unsigned transaction when going to or from encoded buffer', () => { const receiver = 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI'; + const minFee = 1000; const fee = 4; const amount = 1000; const firstValid = 12466; @@ -39,7 +40,7 @@ describe('Algosdk (AKA end to end)', () => { const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; const closeRemainderTo = 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA'; - const note = new Uint8Array(algosdk.base64ToBytes('6gAVR0Nsv5Y=')); + const note = algosdk.base64ToBytes('6gAVR0Nsv5Y='); const sender = receiver; const suggestedParams = { genesisHash, @@ -47,15 +48,16 @@ describe('Algosdk (AKA end to end)', () => { firstValid, lastValid, fee, + minFee, }; - const txnAsObj = algosdk.makePaymentTxnWithSuggestedParams( + const txnAsObj = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender, receiver, amount, closeRemainderTo, note, - suggestedParams - ); + suggestedParams, + }); const txnAsBuffer = algosdk.encodeUnsignedTransaction(txnAsObj); const txnAsObjRecovered = algosdk.decodeUnsignedTransaction(txnAsBuffer); const txnAsBufferRecovered = @@ -66,12 +68,17 @@ describe('Algosdk (AKA end to end)', () => { 'i6NhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0EmKJmds0wsqNnZW6sZGV2bmV0LXYzMy4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds00mqRub3RlxAjqABVHQ2y/lqNyY3bEIHts4k/rW6zAsWTinCIsV/X2PcOH1DkEglhBHF/hD3wCo3NuZMQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKkdHlwZaNwYXk=' ) ); + const goldenDecoded = + algosdk.decodeUnsignedTransaction(txnAsBufferGolden); + assert.deepStrictEqual(txnAsObj, goldenDecoded); + assert.deepStrictEqual(txnAsBufferGolden, txnAsBufferRecovered); }); it('should not mutate signed transaction when going to or from encoded buffer', () => { const receiver = 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI'; + const minFee = 1000; const fee = 4; const amount = 1000; const firstValid = 12466; @@ -88,15 +95,16 @@ describe('Algosdk (AKA end to end)', () => { firstValid, lastValid, fee, + minFee, }; - const txnAsObj = algosdk.makePaymentTxnWithSuggestedParams( + const txnAsObj = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender, receiver, amount, closeRemainderTo, note, - suggestedParams - ); + suggestedParams, + }); const sk = algosdk.mnemonicToSecretKey( 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor' ); @@ -118,53 +126,63 @@ describe('Algosdk (AKA end to end)', () => { describe('Sign', () => { it('should return a blob that matches the go code', () => { - const sk = algosdk.mnemonicToSecretKey( + const account = algosdk.mnemonicToSecretKey( 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor' ); - const golden = - 'gqNzaWfEQPhUAZ3xkDDcc8FvOVo6UinzmKBCqs0woYSfodlmBMfQvGbeUx3Srxy3dyJDzv7rLm26BRv9FnL2/AuT7NYfiAWjdHhui6NhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0EmKJmds0wsqNnZW6sZGV2bmV0LXYzMy4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds00mqRub3RlxAjqABVHQ2y/lqNyY3bEIHts4k/rW6zAsWTinCIsV/X2PcOH1DkEglhBHF/hD3wCo3NuZMQg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGkdHlwZaNwYXk='; - const o = { + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + sender: account.addr, receiver: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI', - fee: 4, amount: 1000, - firstValid: 12466, - lastValid: 13466, - genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', closeRemainderTo: 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA', - note: new Uint8Array(algosdk.base64ToBytes('6gAVR0Nsv5Y=')), - } as any; // Temporary type fix, will be unnecessary in following PR + note: algosdk.base64ToBytes('6gAVR0Nsv5Y='), + suggestedParams: { + minFee: 1000, + fee: 4, + firstValid: 12466, + lastValid: 13466, + genesisID: 'devnet-v33.0', + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, + }); - const jsDec = algosdk.signTransaction(o, sk.sk); - assert.deepStrictEqual(jsDec.blob, algosdk.base64ToBytes(golden)); + const signed = algosdk.signTransaction(txn, account.sk); + + const golden = + 'gqNzaWfEQPhUAZ3xkDDcc8FvOVo6UinzmKBCqs0woYSfodlmBMfQvGbeUx3Srxy3dyJDzv7rLm26BRv9FnL2/AuT7NYfiAWjdHhui6NhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0EmKJmds0wsqNnZW6sZGV2bmV0LXYzMy4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds00mqRub3RlxAjqABVHQ2y/lqNyY3bEIHts4k/rW6zAsWTinCIsV/X2PcOH1DkEglhBHF/hD3wCo3NuZMQg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGkdHlwZaNwYXk='; + + assert.deepStrictEqual(signed.blob, algosdk.base64ToBytes(golden)); // // Check txid const txGolden = '5FJDJD5LMZC3EHUYYJNH5I23U4X6H2KXABNDGPIL557ZMJ33GZHQ'; - assert.deepStrictEqual(jsDec.txID, txGolden); + assert.deepStrictEqual(signed.txID, txGolden); }); it('should return a blob that matches the go code when using a flat fee', () => { - const sk = algosdk.mnemonicToSecretKey( + const { addr, sk } = algosdk.mnemonicToSecretKey( 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor' ); const golden = 'gqNzaWfEQPhUAZ3xkDDcc8FvOVo6UinzmKBCqs0woYSfodlmBMfQvGbeUx3Srxy3dyJDzv7rLm26BRv9FnL2/AuT7NYfiAWjdHhui6NhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0EmKJmds0wsqNnZW6sZGV2bmV0LXYzMy4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds00mqRub3RlxAjqABVHQ2y/lqNyY3bEIHts4k/rW6zAsWTinCIsV/X2PcOH1DkEglhBHF/hD3wCo3NuZMQg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGkdHlwZaNwYXk='; - const o = { + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + sender: addr, receiver: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI', - fee: 1176, amount: 1000, - firstValid: 12466, - lastValid: 13466, - genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', closeRemainderTo: 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA', note: new Uint8Array(algosdk.base64ToBytes('6gAVR0Nsv5Y=')), - flatFee: true, - } as any; // Temporary type fix, will be unnecessary in following PR + suggestedParams: { + minFee: 1000, + fee: 1176, + flatFee: true, + firstValid: 12466, + lastValid: 13466, + genesisID: 'devnet-v33.0', + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, + }); - const jsDec = algosdk.signTransaction(o, sk.sk); + const jsDec = algosdk.signTransaction(txn, sk); assert.deepStrictEqual(jsDec.blob, algosdk.base64ToBytes(golden)); // // Check txid @@ -173,33 +191,41 @@ describe('Algosdk (AKA end to end)', () => { }); it('should return a blob that matches the go code when constructing with a lease', () => { - const sk = algosdk.mnemonicToSecretKey( + const account = algosdk.mnemonicToSecretKey( 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor' ); - const golden = - 'gqNzaWfEQOMmFSIKsZvpW0txwzhmbgQjxv6IyN7BbV5sZ2aNgFbVcrWUnqPpQQxfPhV/wdu9jzEPUU1jAujYtcNCxJ7ONgejdHhujKNhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0FLKJmds0wsqNnZW6sZGV2bmV0LXYzMy4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds00mqJseMQgAQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwSkbm90ZcQI6gAVR0Nsv5ajcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEIOfw+E0GgR358xyNh4sRVfRnHVGhhcIAkIZn9ElYcGihpHR5cGWjcGF5'; // prettier-ignore const lease = new Uint8Array([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]); - const o = { + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + sender: account.addr, receiver: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI', - fee: 4, amount: 1000, - firstValid: 12466, - lastValid: 13466, - genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', closeRemainderTo: 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA', note: new Uint8Array(algosdk.base64ToBytes('6gAVR0Nsv5Y=')), lease, - } as any; // Temporary type fix, will be unnecessary in following PR + suggestedParams: { + minFee: 1000, + fee: 4, + firstValid: 12466, + lastValid: 13466, + genesisID: 'devnet-v33.0', + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, + }); + const signed = algosdk.signTransaction(txn, account.sk); - const jsDec = algosdk.signTransaction(o, sk.sk); - assert.deepStrictEqual(jsDec.blob, algosdk.base64ToBytes(golden)); + const golden = algosdk.base64ToBytes( + 'gqNzaWfEQOMmFSIKsZvpW0txwzhmbgQjxv6IyN7BbV5sZ2aNgFbVcrWUnqPpQQxfPhV/wdu9jzEPUU1jAujYtcNCxJ7ONgejdHhujKNhbXTNA+ilY2xvc2XEIEDpNJKIJWTLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0FLKJmds0wsqNnZW6sZGV2bmV0LXYzMy4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds00mqJseMQgAQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwSkbm90ZcQI6gAVR0Nsv5ajcmN2xCB7bOJP61uswLFk4pwiLFf19j3Dh9Q5BIJYQRxf4Q98AqNzbmTEIOfw+E0GgR358xyNh4sRVfRnHVGhhcIAkIZn9ElYcGihpHR5cGWjcGF5' + ); + const goldenDecoded = algosdk.decodeObj(golden); + const actualDecoded = algosdk.decodeObj(signed.blob); + assert.deepStrictEqual(actualDecoded, goldenDecoded); + assert.deepStrictEqual(signed.blob, golden); // Check txid const txGolden = '7BG6COBZKF6I6W5XY72ZE4HXV6LLZ6ENSR6DASEGSTXYXR4XJOOQ'; - assert.deepStrictEqual(jsDec.txID, txGolden); + assert.deepStrictEqual(signed.txID, txGolden); }); it('should return a blob that matches the go code when adding a lease', () => { @@ -212,6 +238,7 @@ describe('Algosdk (AKA end to end)', () => { const lease = new Uint8Array([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]); const receiver = 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI'; + const minFee = 1000; const fee = 4; const amount = 1000; const firstValid = 12466; @@ -229,23 +256,24 @@ describe('Algosdk (AKA end to end)', () => { firstValid, lastValid, fee, + minFee, }; - const txn = algosdk.makePaymentTxnWithSuggestedParams( + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender, receiver, amount, closeRemainderTo, note, - suggestedParams - ); - txn.addLease(lease, fee); + suggestedParams, + lease, + }); const txnBytes = txn.signTxn(sk.sk); assert.deepStrictEqual(txnBytes, algosdk.base64ToBytes(golden)); // Check txid const txGolden = '7BG6COBZKF6I6W5XY72ZE4HXV6LLZ6ENSR6DASEGSTXYXR4XJOOQ'; - assert.deepStrictEqual(txn.txID().toString(), txGolden); + assert.deepStrictEqual(txn.txID(), txGolden); }); }); @@ -280,6 +308,7 @@ describe('Algosdk (AKA end to end)', () => { genesisID: 'devnet-v33.0', genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', fee: 4, + minFee: 1000, }, }); @@ -319,6 +348,7 @@ describe('Algosdk (AKA end to end)', () => { genesisID: 'devnet-v33.0', genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', fee: 4, + minFee: 1000, }, }); @@ -347,26 +377,31 @@ describe('Algosdk (AKA end to end)', () => { 'BFRTECKTOOE7A5LHCF3TTEOH2A7BW46IYT2SX5VP6ANKEXHZYJY77SJTVM', '47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU', ], - }; // msig address - RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM + }; + const msigAddr = algosdk.multisigAddress(params); // RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM const mnem3 = 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor'; const { sk } = algosdk.mnemonicToSecretKey(mnem3); - const o = { + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + sender: msigAddr, receiver: 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI', - fee: 4, amount: 1000, - firstValid: 12466, - lastValid: 13466, - genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', closeRemainderTo: 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA', note: algosdk.base64ToBytes('X4Bl4wQ9rCo='), - } as any; // Temporary type fix, will be unnecessary in following PR + suggestedParams: { + minFee: 1000, + fee: 4, + firstValid: 12466, + lastValid: 13466, + genesisID: 'devnet-v33.0', + genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + }, + }); - const jsDec = algosdk.signMultisigTransaction(o, params, sk); + const jsDec = algosdk.signMultisigTransaction(txn, params, sk); // this golden also contains the correct multisig address const golden = algosdk.base64ToBytes( 'gqRtc2lng6ZzdWJzaWeTgaJwa8QgG37AsEvqYbeWkJfmy/QH4QinBTUdC8mKvrEiCairgXiBonBrxCAJYzIJU3OJ8HVnEXc5kcfQPhtzyMT1K/av8BqiXPnCcYKicGvEIOfw+E0GgR358xyNh4sRVfRnHVGhhcIAkIZn9ElYcGihoXPEQF6nXZ7CgInd1h7NVspIPFZNhkPL+vGFpTNwH3Eh9gwPM8pf1EPTHfPvjf14sS7xN7mTK+wrz7Odhp4rdWBNUASjdGhyAqF2AaN0eG6Lo2FtdM0D6KVjbG9zZcQgQOk0koglZMvOnFmmm2dUJonpocOiqepbZabopEIf/FejZmVlzQSYomZ2zTCyo2dlbqxkZXZuZXQtdjMzLjCiZ2jEICYLIAmgk6iGi3lYci+l5Ubt5+0X5NhcTHivsEUmkO3Somx2zTSapG5vdGXECF+AZeMEPawqo3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc25kxCCNkrSJkAFzoE36Q1mjZmpq/OosQqBd2cH3PuulR4A36aR0eXBlo3BheQ==' @@ -377,68 +412,6 @@ describe('Algosdk (AKA end to end)', () => { const txGolden = 'TDIO6RJWJIVDDJZELMSX5CPJW7MUNM3QR4YAHYAKHF3W2CFRTI7A'; assert.deepStrictEqual(jsDec.txID, txGolden); }); - - it('should return the same blob whether using dict-of-args or algosdk.makeFooTransaction', () => { - const params = { - version: 1, - threshold: 2, - addrs: [ - 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA', - 'BFRTECKTOOE7A5LHCF3TTEOH2A7BW46IYT2SX5VP6ANKEXHZYJY77SJTVM', - '47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU', - ], - }; // msig address - RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM - - const mnemonic = - 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor'; - const { sk } = algosdk.mnemonicToSecretKey(mnemonic); - - const toAddr = - 'PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI'; - const fromAddr = - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM'; - const fee = 4; - const amount = 1000; - const firstValid = 12466; - const lastValid = 13466; - const genesisID = 'devnet-v33.0'; - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const closeRemainder = - 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA'; - const note = algosdk.base64ToBytes('X4Bl4wQ9rCo='); - const oDict = { - receiver: toAddr, - sender: fromAddr, - fee, - amount, - firstValid, - lastValid, - genesisID, - genesisHash, - closeRemainderTo: closeRemainder, - note, - }; - const suggestedParams = { - genesisHash, - genesisID, - firstValid, - lastValid, - fee, - }; - const oObj = algosdk.makePaymentTxnWithSuggestedParams( - fromAddr, - toAddr, - amount, - closeRemainder, - note, - suggestedParams - ); - - const oDictOutput = algosdk.signMultisigTransaction(oDict, params, sk); - const oObjOutput = algosdk.signMultisigTransaction(oObj, params, sk); - assert.deepStrictEqual(oDictOutput.txID, oObjOutput.txID); - assert.deepStrictEqual(oDictOutput.blob, oObjOutput.blob); - }); }); describe('Multisig Append', () => { @@ -497,49 +470,53 @@ describe('Algosdk (AKA end to end)', () => { const address = 'UPYAFLHSIPMJOHVXU2MPLQ46GXJKSDCEMZ6RLCQ7GWB5PRDKJUWKKXECXI'; const [fromAddress, toAddress] = [address, address]; + const minFee = 1000; const fee = 1000; const amount = 2000; const genesisID = 'devnet-v1.0'; const genesisHash = 'sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E'; const firstValid1 = 710399; const note1 = algosdk.base64ToBytes('wRKw5cJ0CMo='); - const o1 = { + const tx1 = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ receiver: toAddress, sender: fromAddress, - fee, amount, - firstValid: firstValid1, - lastValid: firstValid1 + 1000, - genesisID, - genesisHash, note: note1, - flatFee: true, - }; + suggestedParams: { + minFee, + fee, + flatFee: true, + firstValid: firstValid1, + lastValid: firstValid1 + 1000, + genesisID, + genesisHash, + }, + }); const firstValid2 = 710515; const note2 = algosdk.base64ToBytes('dBlHI6BdrIg='); - const o2 = { + const tx2 = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ receiver: toAddress, sender: fromAddress, - fee, amount, - firstValid: firstValid2, - lastValid: firstValid2 + 1000, - genesisID, - genesisHash, note: note2, - flatFee: true, - }; + suggestedParams: { + minFee, + fee, + flatFee: true, + firstValid: firstValid2, + lastValid: firstValid2 + 1000, + genesisID, + genesisHash, + }, + }); const goldenTx1 = 'gaN0eG6Ko2FtdM0H0KNmZWXNA+iiZnbOAArW/6NnZW6rZGV2bmV0LXYxLjCiZ2jEILAtz+3tknW6iiStLW4gnSvbXUqW3ul3ghinaDc5pY9Bomx2zgAK2uekbm90ZcQIwRKw5cJ0CMqjcmN2xCCj8AKs8kPYlx63ppj1w5410qkMRGZ9FYofNYPXxGpNLKNzbmTEIKPwAqzyQ9iXHremmPXDnjXSqQxEZn0Vih81g9fEak0spHR5cGWjcGF5'; const goldenTx2 = 'gaN0eG6Ko2FtdM0H0KNmZWXNA+iiZnbOAArXc6NnZW6rZGV2bmV0LXYxLjCiZ2jEILAtz+3tknW6iiStLW4gnSvbXUqW3ul3ghinaDc5pY9Bomx2zgAK21ukbm90ZcQIdBlHI6BdrIijcmN2xCCj8AKs8kPYlx63ppj1w5410qkMRGZ9FYofNYPXxGpNLKNzbmTEIKPwAqzyQ9iXHremmPXDnjXSqQxEZn0Vih81g9fEak0spHR5cGWjcGF5'; - const tx1 = new algosdk.Transaction(o1); - const tx2 = new algosdk.Transaction(o2); - // goal clerk send dumps unsigned transaction as signed with empty signature in order to save tx type let stx1 = algosdk.encodeObj({ txn: tx1.get_obj_for_encoding() }); let stx2 = algosdk.encodeObj({ txn: tx2.get_obj_for_encoding() }); @@ -550,44 +527,25 @@ describe('Algosdk (AKA end to end)', () => { // simulating that behavior here const goldenTxg = 'gaN0eG6Lo2FtdM0H0KNmZWXNA+iiZnbOAArW/6NnZW6rZGV2bmV0LXYxLjCiZ2jEILAtz+3tknW6iiStLW4gnSvbXUqW3ul3ghinaDc5pY9Bo2dycMQgLiQ9OBup9H/bZLSfQUH2S6iHUM6FQ3PLuv9FNKyt09SibHbOAAra56Rub3RlxAjBErDlwnQIyqNyY3bEIKPwAqzyQ9iXHremmPXDnjXSqQxEZn0Vih81g9fEak0so3NuZMQgo/ACrPJD2Jcet6aY9cOeNdKpDERmfRWKHzWD18RqTSykdHlwZaNwYXmBo3R4boujYW10zQfQo2ZlZc0D6KJmds4ACtdzo2dlbqtkZXZuZXQtdjEuMKJnaMQgsC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0GjZ3JwxCAuJD04G6n0f9tktJ9BQfZLqIdQzoVDc8u6/0U0rK3T1KJsds4ACttbpG5vdGXECHQZRyOgXayIo3JjdsQgo/ACrPJD2Jcet6aY9cOeNdKpDERmfRWKHzWD18RqTSyjc25kxCCj8AKs8kPYlx63ppj1w5410qkMRGZ9FYofNYPXxGpNLKR0eXBlo3BheQ=='; - { - const gid = algosdk.computeGroupID([tx1, tx2]); - tx1.group = gid; - tx2.group = gid; - stx1 = algosdk.encodeObj({ txn: tx1.get_obj_for_encoding() }); - stx2 = algosdk.encodeObj({ txn: tx2.get_obj_for_encoding() }); - const concat = utils.concatArrays(stx1, stx2); - assert.deepStrictEqual(concat, algosdk.base64ToBytes(goldenTxg)); - } - - // check computeGroupID for list of dicts (not Transaction objects) - { - const gid = algosdk.computeGroupID([o1, o2]); - tx1.group = gid; - tx2.group = gid; - stx1 = algosdk.encodeObj({ txn: tx1.get_obj_for_encoding() }); - stx2 = algosdk.encodeObj({ txn: tx2.get_obj_for_encoding() }); - const concat = utils.concatArrays(stx1, stx2); - assert.deepStrictEqual(concat, algosdk.base64ToBytes(goldenTxg)); - } - - // check filtering by address in assignGroupID - let result; - result = algosdk.assignGroupID([tx1, tx2]); - assert.equal(result.length, 2); - - result = algosdk.assignGroupID([tx1, tx2], ''); - assert.equal(result.length, 2); - - result = algosdk.assignGroupID([tx1, tx2], address); - assert.equal(result.length, 2); - - result = algosdk.assignGroupID( - [tx1, tx2], - 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA' - ); - assert.ok(result instanceof Array); - assert.equal(result.length, 0); + const gid = algosdk.computeGroupID([tx1, tx2]); + tx1.group = gid; + tx2.group = gid; + stx1 = algosdk.encodeObj({ txn: tx1.get_obj_for_encoding() }); + stx2 = algosdk.encodeObj({ txn: tx2.get_obj_for_encoding() }); + const concat = utils.concatArrays(stx1, stx2); + assert.deepStrictEqual(concat, algosdk.base64ToBytes(goldenTxg)); + + // check assignGroupID + tx1.group = new Uint8Array(); + tx2.group = new Uint8Array(); + + const input = [tx1, tx2]; + const result = algosdk.assignGroupID(input); + assert.strictEqual(result.length, 2); + assert.strictEqual(result, input); + + assert.deepStrictEqual(tx1.group, gid); + assert.deepStrictEqual(tx2.group, gid); }); }); @@ -600,24 +558,30 @@ describe('Algosdk (AKA end to end)', () => { const sk = algosdk.mnemonicToSecretKey( 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred' ); - const createTxn = { - sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - assetTotal: 100, - assetDefaultFrozen: false, - assetManager: address, - assetReserve: address, - assetFreeze: address, - assetClawback: address, - assetUnitName: 'tst', - assetName: 'testcoin', - assetURL: 'website', - assetMetadataHash: 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh', - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR + const createTxn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject( + { + sender: address, + total: 100, + defaultFrozen: false, + manager: address, + reserve: address, + freeze: address, + clawback: address, + unitName: 'tst', + assetName: 'testcoin', + assetURL: 'website', + assetMetadataHash: new TextEncoder().encode( + 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh' + ), + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + } + ); const jsDecCreate = algosdk.signTransaction(createTxn, sk.sk); assert.deepStrictEqual(jsDecCreate.blob, algosdk.base64ToBytes(golden)); }); @@ -630,25 +594,31 @@ describe('Algosdk (AKA end to end)', () => { const sk = algosdk.mnemonicToSecretKey( 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred' ); - const createTxn = { - sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - assetTotal: 100, - assetDecimals: 1, - assetDefaultFrozen: false, - assetManager: address, - assetReserve: address, - assetFreeze: address, - assetClawback: address, - assetUnitName: 'tst', - assetName: 'testcoin', - assetURL: 'website', - assetMetadataHash: 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh', - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR + const createTxn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject( + { + sender: address, + total: 100, + decimals: 1, + defaultFrozen: false, + manager: address, + reserve: address, + freeze: address, + clawback: address, + unitName: 'tst', + assetName: 'testcoin', + assetURL: 'website', + assetMetadataHash: new TextEncoder().encode( + 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh' + ), + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + } + ); const jsDecCreate = algosdk.signTransaction(createTxn, sk.sk); assert.deepStrictEqual(jsDecCreate.blob, algosdk.base64ToBytes(golden)); }); @@ -661,20 +631,22 @@ describe('Algosdk (AKA end to end)', () => { const sk = algosdk.mnemonicToSecretKey( 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred' ); - const o = { + const txn = algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', assetIndex: 1234, - assetManager: address, - assetReserve: address, - assetFreeze: address, - assetClawback: address, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR - const jsDec = algosdk.signTransaction(o, sk.sk); + manager: address, + reserve: address, + freeze: address, + clawback: address, + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + }); + const jsDec = algosdk.signTransaction(txn, sk.sk); assert.deepStrictEqual(jsDec.blob, algosdk.base64ToBytes(golden)); }); @@ -686,36 +658,40 @@ describe('Algosdk (AKA end to end)', () => { const sk = algosdk.mnemonicToSecretKey( 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred' ); - const o = { + const txn = algosdk.makeAssetDestroyTxnWithSuggestedParamsFromObject({ sender: address, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', assetIndex: 1, - type: 'acfg', - } as any; // Temporary type fix, will be unnecessary in following PR - const jsDec = algosdk.signTransaction(o, sk.sk); + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + }); + const jsDec = algosdk.signTransaction(txn, sk.sk); assert.deepStrictEqual(jsDec.blob, algosdk.base64ToBytes(golden)); }); it('should return a blob that matches the go code for asset freeze', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { + const txn = algosdk.makeAssetFreezeTxnWithSuggestedParamsFromObject({ sender: addr, - fee: 10, - firstValid: 322575, - lastValid: 323576, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - type: 'afrz', - freezeAccount: addr, + freezeTarget: addr, assetIndex: 1, assetFrozen: true, - } as any; // Temporary type fix, will be unnecessary in following PR + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323576, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + }); const mnem = 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred'; const { sk } = algosdk.mnemonicToSecretKey(mnem); - const jsDec = algosdk.signTransaction(o, sk); + const jsDec = algosdk.signTransaction(txn, sk); const golden = algosdk.base64ToBytes( 'gqNzaWfEQAhru5V2Xvr19s4pGnI0aslqwY4lA2skzpYtDTAN9DKSH5+qsfQQhm4oq+9VHVj7e1rQC49S28vQZmzDTVnYDQGjdHhuiaRhZnJ6w6RmYWRkxCAJ+9J2LAj4bFrmv23Xp6kB3mZ111Dgfoxcdphkfbbh/aRmYWlkAaNmZWXNCRqiZnbOAATsD6JnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOAATv+KNzbmTEIAn70nYsCPhsWua/bdenqQHeZnXXUOB+jFx2mGR9tuH9pHR5cGWkYWZyeg==' ); @@ -724,23 +700,25 @@ describe('Algosdk (AKA end to end)', () => { it('should return a blob that matches the go code for asset transfer', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { - type: 'axfer', + const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ sender: addr, receiver: addr, amount: 1, - fee: 10, - firstValid: 322575, - lastValid: 323576, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', assetIndex: 1, closeRemainderTo: addr, - } as any; // Temporary type fix, will be unnecessary in following PR + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323576, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + }); const mnem = 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred'; const { sk } = algosdk.mnemonicToSecretKey(mnem); - const jsDec = algosdk.signTransaction(o, sk); + const jsDec = algosdk.signTransaction(txn, sk); const golden = algosdk.base64ToBytes( 'gqNzaWfEQNkEs3WdfFq6IQKJdF1n0/hbV9waLsvojy9pM1T4fvwfMNdjGQDy+LeesuQUfQVTneJD4VfMP7zKx4OUlItbrwSjdHhuiqRhYW10AaZhY2xvc2XEIAn70nYsCPhsWua/bdenqQHeZnXXUOB+jFx2mGR9tuH9pGFyY3bEIAn70nYsCPhsWua/bdenqQHeZnXXUOB+jFx2mGR9tuH9o2ZlZc0KvqJmds4ABOwPomdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk6IqJsds4ABO/4o3NuZMQgCfvSdiwI+Gxa5r9t16epAd5mdddQ4H6MXHaYZH224f2kdHlwZaVheGZlcqR4YWlkAQ==' ); @@ -749,50 +727,60 @@ describe('Algosdk (AKA end to end)', () => { it('should return a blob that matches the go code for asset accept', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { - type: 'axfer', + const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ sender: addr, receiver: addr, amount: 0, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', assetIndex: 1, - } as any; // Temporary type fix, will be unnecessary in following PR + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + }); const mnem = 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred'; const { sk } = algosdk.mnemonicToSecretKey(mnem); - const jsDec = algosdk.signTransaction(o, sk); + const jsDec = algosdk.signTransaction(txn, sk); const golden = algosdk.base64ToBytes( 'gqNzaWfEQJ7q2rOT8Sb/wB0F87ld+1zMprxVlYqbUbe+oz0WM63FctIi+K9eYFSqT26XBZ4Rr3+VTJpBE+JLKs8nctl9hgijdHhuiKRhcmN2xCAJ+9J2LAj4bFrmv23Xp6kB3mZ111Dgfoxcdphkfbbh/aNmZWXNCOiiZnbOAATsD6JnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOAATv96NzbmTEIAn70nYsCPhsWua/bdenqQHeZnXXUOB+jFx2mGR9tuH9pHR5cGWlYXhmZXKkeGFpZAE=' ); + const goldenDecoded = algosdk.decodeObj(golden); + const actualDecoded = algosdk.decodeObj(jsDec.blob); + assert.deepStrictEqual(actualDecoded, goldenDecoded); assert.deepStrictEqual(jsDec.blob, golden); }); it('should return a blob that matches the go code for asset revoke', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; - const o = { - type: 'axfer', + const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ sender: addr, receiver: addr, assetSender: addr, amount: 1, - fee: 10, - firstValid: 322575, - lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', assetIndex: 1, - } as any; // Temporary type fix, will be unnecessary in following PR + suggestedParams: { + minFee: 1000, + fee: 10, + firstValid: 322575, + lastValid: 323575, + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + }, + }); const mnem = 'awful drop leaf tennis indoor begin mandate discover uncle seven only coil atom any hospital uncover make any climb actor armed measure need above hundred'; const { sk } = algosdk.mnemonicToSecretKey(mnem); - const jsDec = algosdk.signTransaction(o, sk); + const jsDec = algosdk.signTransaction(txn, sk); const golden = algosdk.base64ToBytes( 'gqNzaWfEQHsgfEAmEHUxLLLR9s+Y/yq5WeoGo/jAArCbany+7ZYwExMySzAhmV7M7S8+LBtJalB4EhzEUMKmt3kNKk6+vAWjdHhuiqRhYW10AaRhcmN2xCAJ+9J2LAj4bFrmv23Xp6kB3mZ111Dgfoxcdphkfbbh/aRhc25kxCAJ+9J2LAj4bFrmv23Xp6kB3mZ111Dgfoxcdphkfbbh/aNmZWXNCqqiZnbOAATsD6JnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOAATv96NzbmTEIAn70nYsCPhsWua/bdenqQHeZnXXUOB+jFx2mGR9tuH9pHR5cGWlYXhmZXKkeGFpZAE=' ); + const goldenDecoded = algosdk.decodeObj(golden); + const actualDecoded = algosdk.decodeObj(jsDec.blob); + assert.deepStrictEqual(actualDecoded, goldenDecoded); assert.deepStrictEqual(jsDec.blob, golden); }); }); @@ -889,24 +877,28 @@ describe('Algosdk (AKA end to end)', () => { const mn = 'advice pudding treat near rule blouse same whisper inner electric quit surface sunny dismiss leader blood seat clown cost exist hospital century reform able sponsor'; const fee = 1000; + const minFee = 1000; const amount = 2000; const firstValid = 2063137; const genesisID = 'devnet-v1.0'; const genesisHash = 'sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E='; const note = algosdk.base64ToBytes('8xMCTuLQ810='); - const txn = { + const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ receiver: toAddress, sender: fromAddress, - fee, amount, - firstValid, - lastValid: firstValid + 1000, - genesisID, - genesisHash, note, - flatFee: true, - }; + suggestedParams: { + flatFee: true, + fee, + minFee, + firstValid, + lastValid: firstValid + 1000, + genesisID, + genesisHash, + }, + }); const program = Uint8Array.from([1, 32, 1, 1, 34]); // int 1 const args = [ diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts index b08040267..78441b6d6 100644 --- a/tests/8.LogicSig.ts +++ b/tests/8.LogicSig.ts @@ -426,6 +426,7 @@ describe('signLogicSigTransaction', () => { receiver: otherAddr, amount: 5000, suggestedParams: { + minFee: 1000, flatFee: true, fee: 217000, firstValid: 972508, @@ -498,6 +499,7 @@ describe('signLogicSigTransaction', () => { receiver: otherAddr, amount: 5000, suggestedParams: { + minFee: 1000, flatFee: true, fee: 217000, firstValid: 972508, @@ -647,49 +649,6 @@ describe('signLogicSigTransaction', () => { }); }); }); - - it('should sign a raw transaction object', () => { - const lsig = new algosdk.LogicSig(program); - - const sender = lsig.address(); - const receiver = - 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; - const fee = 10; - const amount = 847; - const firstValid = 51; - const lastValid = 61; - const note = new Uint8Array([123, 12, 200]); - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; - const genesisID = ''; - const rekeyTo = - 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; - let closeRemainderTo; - const txn = { - sender, - receiver, - fee, - amount, - closeRemainderTo, - firstValid, - lastValid, - note, - genesisHash, - genesisID, - rekeyTo, - }; - - const actual = algosdk.signLogicSigTransaction(txn, lsig); - const expected = { - txID: 'D7H6THOHOCEWJYNWMKHVOR2W36KAJXSGG6DMNTHTBWONBCG4XATA', - blob: new Uint8Array( - algosdk.base64ToBytes( - 'gqRsc2lngaFsxAUBIAEBIqN0eG6Ko2FtdM0DT6NmZWXNCniiZnYzomdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsdj2kbm90ZcQDewzIo3JjdsQgoImqaSLjuZj63/bNSAjd+eAh5JROOJ6j1cY4eGaJGX6lcmVrZXnEIDAhUOuXI/Dnhg1MAE4rbltxOOB+7lUduJbsxucZf2DUo3NuZMQg9nYtrHWxmX1sLJYYBoBQdJDXlREv/n+3YLJzivnH8a2kdHlwZaNwYXk=' - ) - ), - }; - - assert.deepStrictEqual(actual, expected); - }); }); describe('ProgramSourceMap', () => { diff --git a/tests/cucumber/integration.tags b/tests/cucumber/integration.tags index eefd3bdd0..ed8782d44 100644 --- a/tests/cucumber/integration.tags +++ b/tests/cucumber/integration.tags @@ -3,7 +3,6 @@ @applications.boxes @applications.verified @assets -@auction @c2c @compile @compile.disassemble diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index f03b1a7e7..3087bf2fb 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -415,16 +415,13 @@ module.exports = function getSteps(options) { // Fund the rekey address with some Algos const sp = await this.v2Client.getTransactionParams().do(); if (sp.firstValid === 0) sp.firstValid = 1; - const fundingTxnArgs = { - sender: this.accounts[0], - receiver: this.rekey, - amount: DEV_MODE_INITIAL_MICROALGOS, - fee: sp.fee, - firstValid: sp.firstValid, - lastValid: sp.lastValid, - genesisHash: sp.genesisHash, - genesisID: sp.genesisID, - }; + const fundingTxnArgs = + algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + sender: this.accounts[0], + receiver: this.rekey, + amount: DEV_MODE_INITIAL_MICROALGOS, + suggestedParams: sp, + }); const stxKmd = await this.kcl.signTransaction( this.handle, @@ -509,17 +506,13 @@ module.exports = function getSteps(options) { this.pk = this.rekey; const result = await this.v2Client.getTransactionParams().do(); this.lastValid = result.lastValid; - this.txn = { + this.txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender: this.rekey, receiver: this.accounts[1], - fee: result.fee, - firstValid: result.firstValid, - lastValid: result.lastValid, - genesisHash: result.genesisHash, - genesisID: result.genesisID, note: makeUint8Array(algosdk.base64ToBytes(note)), amount: parseInt(amt), - }; + suggestedParams: result, + }); return this.txn; } ); @@ -535,17 +528,13 @@ module.exports = function getSteps(options) { addrs: this.accounts, }; - this.txn = { + this.txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender: algosdk.multisigAddress(this.msig), receiver: this.accounts[1], - fee: result.fee, - firstValid: result.firstValid, - lastValid: result.lastValid, - genesisHash: result.genesisHash, - genesisID: result.genesisID, note: makeUint8Array(algosdk.base64ToBytes(note)), amount: parseInt(amt), - }; + suggestedParams: result, + }); return this.txn; } ); @@ -687,26 +676,22 @@ module.exports = function getSteps(options) { }); When('I create the flat fee payment transaction', function () { - this.txn = { + this.txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + sender: this.pk, receiver: this.receiver, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - genesisHash: this.gh, - flatFee: true, - }; - if (this.gen) { - this.txn.genesisID = this.gen; - } - if (this.close) { - this.txn.closeRemainderTo = this.close; - } - if (this.note) { - this.txn.note = this.note; - } - if (this.amt) { - this.txn.amount = this.amt; - } + amount: this.amt ?? 0, + closeRemainderTo: this.close, + note: this.note, + suggestedParams: { + minFee: 1000, // Shouldn't matter because flatFee=true + flatFee: true, + fee: this.fee, + firstValid: this.fv, + lastValid: this.lv, + genesisHash: this.gh, + genesisID: this.gen, + }, + }); }); Given('encoded multisig transaction {string}', function (encTxn) { @@ -757,51 +742,41 @@ module.exports = function getSteps(options) { }); When('I create the multisig payment transaction', function () { - this.txn = { + this.txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender: algosdk.multisigAddress(this.msig), receiver: this.receiver, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - genesisHash: this.gh, - }; - if (this.gen) { - this.txn.genesisID = this.gen; - } - if (this.close) { - this.txn.closeRemainderTo = this.close; - } - if (this.note) { - this.txn.note = this.note; - } - if (this.amt) { - this.txn.amount = this.amt; - } + amount: this.amt ?? 0, + closeRemainderTo: this.close, + note: this.note, + suggestedParams: { + minFee: 1000, // Hardcoding, but it would be nice to take this as an argument + fee: this.fee, + firstValid: this.fv, + lastValid: this.lv, + genesisHash: this.gh, + genesisID: this.gen, + }, + }); return this.txn; }); When('I create the multisig payment transaction with zero fee', function () { - this.txn = { + this.txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender: algosdk.multisigAddress(this.msig), receiver: this.receiver, - fee: this.fee, - flatFee: true, - firstValid: this.fv, - lastValid: this.lv, - genesisHash: this.gh, - }; - if (this.gen) { - this.txn.genesisID = this.gen; - } - if (this.close) { - this.txn.closeRemainderTo = this.close; - } - if (this.note) { - this.txn.note = this.note; - } - if (this.amt) { - this.txn.amount = this.amt; - } + amount: this.amt ?? 0, + closeRemainderTo: this.close, + note: this.note, + suggestedParams: { + minFee: 1000, // Shouldn't matter because flatFee=true + fee: this.fee, + flatFee: true, + firstValid: this.fv, + lastValid: this.lv, + genesisHash: this.gh, + genesisID: this.gen, + }, + }); return this.txn; }); @@ -960,13 +935,6 @@ module.exports = function getSteps(options) { this.pk = from; const result = await this.v2Client.getTransactionParams().do(); - const suggestedParams = { - fee: result.fee, - firstValid: result.firstValid, - lastValid: result.lastValid, - genesisHash: result.genesisHash, - genesisID: result.genesisID, - }; this.lastValid = result.lastValid; if (type === 'online') { @@ -978,18 +946,18 @@ module.exports = function getSteps(options) { voteFirst: 1, voteLast: 2000, voteKeyDilution: 10, - suggestedParams, + suggestedParams: result, }); } else if (type === 'offline') { this.txn = algosdk.makeKeyRegistrationTxnWithSuggestedParamsFromObject({ sender: from, - suggestedParams, + suggestedParams: result, }); } else if (type === 'nonparticipation') { this.txn = algosdk.makeKeyRegistrationTxnWithSuggestedParamsFromObject({ sender: from, nonParticipation: true, - suggestedParams, + suggestedParams: result, }); } else { throw new Error(`Unrecognized keyreg type: ${type}`); @@ -1008,7 +976,9 @@ module.exports = function getSteps(options) { name: 'testcoin', unitname: 'coins', url: 'http://test', - metadataHash: 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh', + metadataHash: new TextEncoder().encode( + 'fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh' + ), expectedParams: undefined, queriedParams: undefined, lastTxn: undefined, @@ -1025,7 +995,7 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const parsedIssuance = parseInt(issuance); + const parsedIssuance = BigInt(issuance); const decimals = 0; const defaultFrozen = false; const assetName = this.assetTestFixture.name; @@ -1036,43 +1006,37 @@ module.exports = function getSteps(options) { const reserve = this.assetTestFixture.creator; const freeze = this.assetTestFixture.creator; const clawback = this.assetTestFixture.creator; - const genesisID = ''; - const type = 'acfg'; - this.assetTestFixture.lastTxn = { - sender: this.assetTestFixture.creator, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetTotal: parsedIssuance, - assetDecimals: decimals, - assetDefaultFrozen: defaultFrozen, - assetUnitName: unitName, - assetName, - assetURL, - assetMetadataHash: metadataHash, - assetManager: manager, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - genesisID, - type, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject({ + sender: this.assetTestFixture.creator, + note: this.note, + total: parsedIssuance, + decimals, + defaultFrozen, + unitName, + assetName, + assetURL, + assetMetadataHash: metadataHash, + manager, + reserve, + freeze, + clawback, + suggestedParams: this.params, + }); // update vars used by other helpers this.assetTestFixture.expectedParams = { creator: this.assetTestFixture.creator, total: parsedIssuance, - defaultfrozen: defaultFrozen, - unitname: unitName, - assetname: assetName, + defaultFrozen, + unitName, + name: assetName, url: assetURL, - metadatahash: metadataHash, - managerkey: manager, - reserveaddr: reserve, - freezeaddr: freeze, - clawbackaddr: clawback, + metadataHash, + manager, + reserve, + freeze, + clawback, decimals, }; this.txn = this.assetTestFixture.lastTxn; @@ -1091,7 +1055,7 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const parsedIssuance = parseInt(issuance); + const parsedIssuance = BigInt(issuance); const decimals = 0; const defaultFrozen = true; const assetName = this.assetTestFixture.name; @@ -1102,43 +1066,37 @@ module.exports = function getSteps(options) { const reserve = this.assetTestFixture.creator; const freeze = this.assetTestFixture.creator; const clawback = this.assetTestFixture.creator; - const genesisID = ''; - const type = 'acfg'; - this.assetTestFixture.lastTxn = { - sender: this.assetTestFixture.creator, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetTotal: parsedIssuance, - assetDecimals: decimals, - assetDefaultFrozen: defaultFrozen, - assetUnitName: unitName, - assetName, - assetURL, - assetMetadataHash: metadataHash, - assetManager: manager, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - genesisID, - type, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject({ + sender: this.assetTestFixture.creator, + note: this.note, + total: parsedIssuance, + decimals, + defaultFrozen, + unitName, + assetName, + assetURL, + assetMetadataHash: metadataHash, + manager, + reserve, + freeze, + clawback, + suggestedParams: this.params, + }); // update vars used by other helpers this.assetTestFixture.expectedParams = { creator: this.assetTestFixture.creator, total: parsedIssuance, - defaultfrozen: defaultFrozen, - unitname: unitName, - assetname: assetName, + defaultFrozen, + unitName, + name: assetName, url: assetURL, - metadatahash: metadataHash, - managerkey: manager, - reserveaddr: reserve, - freezeaddr: freeze, - clawbackaddr: clawback, + metadataHash, + manager, + reserve, + freeze, + clawback, decimals, }; this.txn = this.assetTestFixture.lastTxn; @@ -1179,15 +1137,16 @@ module.exports = function getSteps(options) { }); Then('the asset info should match the expected asset info', function () { - Object.keys(this.assetTestFixture.expectedParams).forEach((key) => { - assert.strictEqual( - true, - this.assetTestFixture.expectedParams[key] === - this.assetTestFixture.queriedParams.params[key] || - typeof this.assetTestFixture.expectedParams[key] === 'undefined' || - typeof this.assetTestFixture.queriedParams.params[key] === 'undefined' + for (const [key, expectedValue] of Object.entries( + this.assetTestFixture.expectedParams + )) { + const actualValue = this.assetTestFixture.queriedParams.params[key]; + assert.deepStrictEqual( + actualValue, + expectedValue, + `Asset params do not match for ${key}. Actual: ${actualValue}, Expected: ${expectedValue}` ); - }); + } }); When( @@ -1206,28 +1165,23 @@ module.exports = function getSteps(options) { let reserve; let freeze; let clawback; - const genesisID = ''; - const type = 'acfg'; - this.assetTestFixture.lastTxn = { - sender: this.assetTestFixture.creator, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetManager: manager, - assetReserve: reserve, - assetFreeze: freeze, - assetClawback: clawback, - assetIndex: parseInt(this.assetTestFixture.index), - genesisID, - type, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ + sender: this.assetTestFixture.creator, + note: this.note, + manager, + reserve, + freeze, + clawback, + assetIndex: parseInt(this.assetTestFixture.index), + suggestedParams: this.params, + allowRoleRemoval: true, + }); // update vars used by other helpers - this.assetTestFixture.expectedParams.reserveaddr = ''; - this.assetTestFixture.expectedParams.freezeaddr = ''; - this.assetTestFixture.expectedParams.clawbackaddr = ''; + this.assetTestFixture.expectedParams.reserve = undefined; + this.assetTestFixture.expectedParams.freeze = undefined; + this.assetTestFixture.expectedParams.clawback = undefined; this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; [this.pk] = this.accounts; @@ -1242,20 +1196,14 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const genesisID = ''; - const type = 'acfg'; - - this.assetTestFixture.lastTxn = { - sender: this.assetTestFixture.creator, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetIndex: parseInt(this.assetTestFixture.index), - genesisID, - type, - }; + + this.assetTestFixture.lastTxn = + algosdk.makeAssetDestroyTxnWithSuggestedParamsFromObject({ + sender: this.assetTestFixture.creator, + note: this.note, + assetIndex: parseInt(this.assetTestFixture.index), + suggestedParams: this.params, + }); // update vars used by other helpers this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; @@ -1282,22 +1230,16 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const genesisID = ''; - const type = 'axfer'; - this.assetTestFixture.lastTxn = { - sender: accountToUse, - receiver: accountToUse, - amount: 0, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetIndex: parseInt(this.assetTestFixture.index), - genesisID, - type, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ + sender: accountToUse, + receiver: accountToUse, + amount: 0, + note: this.note, + assetIndex: parseInt(this.assetTestFixture.index), + suggestedParams: this.params, + }); // update vars used by other helpers this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; @@ -1314,22 +1256,16 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const genesisID = ''; - const type = 'axfer'; - this.assetTestFixture.lastTxn = { - sender: this.assetTestFixture.creator, - receiver: this.accounts[1], - amount: parseInt(amount), - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetIndex: parseInt(this.assetTestFixture.index), - genesisID, - type, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ + sender: this.assetTestFixture.creator, + receiver: this.accounts[1], + amount: parseInt(amount), + note: this.note, + assetIndex: parseInt(this.assetTestFixture.index), + suggestedParams: this.params, + }); // update vars used by other helpers this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; @@ -1346,22 +1282,16 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const genesisID = ''; - const type = 'axfer'; - this.assetTestFixture.lastTxn = { - receiver: this.assetTestFixture.creator, - sender: this.accounts[1], - amount: parseInt(amount), - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetIndex: parseInt(this.assetTestFixture.index), - genesisID, - type, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ + receiver: this.assetTestFixture.creator, + sender: this.accounts[1], + amount: parseInt(amount), + note: this.note, + assetIndex: parseInt(this.assetTestFixture.index), + suggestedParams: this.params, + }); // update vars used by other helpers this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; @@ -1403,18 +1333,15 @@ module.exports = function getSteps(options) { this.gh = this.params.genesisHash; const freezer = this.assetTestFixture.creator; - this.assetTestFixture.lastTxn = { - sender: freezer, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - genesisHash: this.gh, - type: 'afrz', - freezeAccount: this.accounts[1], - assetIndex: parseInt(this.assetTestFixture.index), - assetFrozen: false, - note: this.note, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetFreezeTxnWithSuggestedParamsFromObject({ + sender: freezer, + freezeTarget: this.accounts[1], + assetIndex: parseInt(this.assetTestFixture.index), + assetFrozen: false, + note: this.note, + suggestedParams: this.params, + }); // update vars used by other helpers this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; @@ -1433,18 +1360,15 @@ module.exports = function getSteps(options) { this.gh = this.params.genesisHash; const freezer = this.assetTestFixture.creator; - this.assetTestFixture.lastTxn = { - sender: freezer, - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - genesisHash: this.gh, - type: 'afrz', - freezeAccount: this.accounts[1], - assetIndex: parseInt(this.assetTestFixture.index), - assetFrozen: true, - note: this.note, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetFreezeTxnWithSuggestedParamsFromObject({ + sender: freezer, + freezeTarget: this.accounts[1], + assetIndex: parseInt(this.assetTestFixture.index), + assetFrozen: true, + note: this.note, + suggestedParams: this.params, + }); // update vars used by other helpers this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; @@ -1461,23 +1385,18 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const genesisID = ''; - const type = 'axfer'; - this.assetTestFixture.lastTxn = { - sender: this.assetTestFixture.creator, - receiver: this.assetTestFixture.creator, - assetSender: this.accounts[1], - amount: parseInt(amount), - fee: this.fee, - firstValid: this.fv, - lastValid: this.lv, - note: this.note, - genesisHash: this.gh, - assetIndex: parseInt(this.assetTestFixture.index), - genesisID, - type, - }; + this.assetTestFixture.lastTxn = + algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ + sender: this.assetTestFixture.creator, + receiver: this.assetTestFixture.creator, + assetSender: this.accounts[1], + amount: parseInt(amount), + note: this.note, + genesisHash: this.gh, + assetIndex: parseInt(this.assetTestFixture.index), + suggestedParams: this.params, + }); // update vars used by other helpers this.txn = this.assetTestFixture.lastTxn; this.lastValid = this.params.lastValid; @@ -2020,7 +1939,7 @@ module.exports = function getSteps(options) { 'the parsed Suggested Transaction Parameters response should have first round valid of {int}', (firstValid) => { assert.strictEqual( - firstValid, + BigInt(firstValid), anySuggestedTransactionsResponse.firstValid ); } @@ -2782,7 +2701,7 @@ module.exports = function getSteps(options) { /// ///////////////////////////////// When('I add a rekeyTo field with address {string}', function (address) { - this.txn.rekeyTo = address; + this.txn.rekeyTo = algosdk.decodeAddress(address); }); When( @@ -2790,12 +2709,14 @@ module.exports = function getSteps(options) { function () { const keypair = keyPairFromSecretKey(this.sk); const pubKeyFromSk = keypair.publicKey; - this.txn.rekeyTo = algosdk.encodeAddress(pubKeyFromSk); + this.txn.rekeyTo = algosdk.decodeAddress( + algosdk.encodeAddress(pubKeyFromSk) + ); } ); When('I set the from address to {string}', function (sender) { - this.txn.sender = sender; + this.txn.sender = algosdk.decodeAddress(sender); }); let dryrunResponse; @@ -2818,14 +2739,12 @@ module.exports = function getSteps(options) { When('I dryrun a {string} program {string}', async function (kind, program) { const data = await loadResource(program); - const algoTxn = new algosdk.Transaction({ + const sp = await this.v2Client.getTransactionParams().do(); + const algoTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender: 'UAPJE355K7BG7RQVMTZOW7QW4ICZJEIC3RZGYG5LSHZ65K6LCNFPJDSR7M', - fee: 1000, + receiver: 'UAPJE355K7BG7RQVMTZOW7QW4ICZJEIC3RZGYG5LSHZ65K6LCNFPJDSR7M', amount: 1000, - firstValid: 1, - lastValid: 1000, - type: 'pay', - genesisHash: 'ZIkPs8pTDxbRJsFB1yJ7gvnpDu0Q85FRkl2NCkEAQLU=', + suggestedParams: sp, }); let txns; let sources; @@ -3010,16 +2929,16 @@ module.exports = function getSteps(options) { async function (amount) { const sp = await this.v2Client.getTransactionParams().do(); if (sp.firstValid === 0) sp.firstValid = 1; - const fundingTxnArgs = { + const fundingTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender: this.accounts[0], receiver: algosdk.getApplicationAddress(this.currentApplicationIndex), amount, suggestedParams: sp, - }; + }); const stxn = await this.kcl.signTransaction( this.handle, this.wallet_pswd, - fundingTxnArgs + fundingTxn ); const fundingResponse = await this.v2Client.sendRawTransaction(stxn).do(); @@ -3038,6 +2957,7 @@ module.exports = function getSteps(options) { assert.ok(['true', 'false'].includes(flatFee)); this.suggestedParams = { + minFee: 1000, // Would be nice to take this as an argument in the future flatFee: flatFee === 'true', fee, firstValid, @@ -3062,19 +2982,21 @@ module.exports = function getSteps(options) { ) { assert.ok(['true', 'false'].includes(nonpart)); - this.txn = algosdk.makeKeyRegistrationTxnWithSuggestedParams( + this.txn = algosdk.makeKeyRegistrationTxnWithSuggestedParamsFromObject({ sender, - undefined, - votePk.length ? votePk : undefined, - selectionPk.length ? selectionPk : undefined, - voteFirst, - voteLast, - keyDilution, - this.suggestedParams, - undefined, - nonpart === 'true', - stateProofPk.length ? stateProofPk : undefined - ); + voteKey: votePk.length ? algosdk.base64ToBytes(votePk) : undefined, + selectionKey: selectionPk.length + ? algosdk.base64ToBytes(selectionPk) + : undefined, + stateProofKey: stateProofPk.length + ? algosdk.base64ToBytes(stateProofPk) + : undefined, + voteFirst: voteFirst || undefined, + voteLast: voteLast || undefined, + voteKeyDilution: keyDilution || undefined, + nonParticipation: nonpart === 'true', + suggestedParams: this.suggestedParams, + }); } ); @@ -3204,119 +3126,102 @@ module.exports = function getSteps(options) { lastValid, fee, flatFee: true, + minFee: 1000, // Shouldn't matter because flatFee=true }; switch (operationString) { case 'call': - this.txn = algosdk.makeApplicationNoOpTxn( + this.txn = algosdk.makeApplicationNoOpTxnFromObject({ sender, - sp, - appIndex, + appId: appIndex, appArgs, - appAccounts, + accounts: appAccounts, foreignApps, foreignAssets, - undefined, - undefined, - undefined, - boxes - ); + boxes, + suggestedParams: sp, + }); return; case 'create': - this.txn = algosdk.makeApplicationCreateTxn( + this.txn = algosdk.makeApplicationCreateTxnFromObject({ sender, - sp, - operation, - approvalProgramBytes, - clearProgramBytes, + onComplete: operation, + approvalProgram: approvalProgramBytes, + clearProgram: clearProgramBytes, numLocalInts, numLocalByteSlices, numGlobalInts, numGlobalByteSlices, + extraPages, appArgs, - appAccounts, + accounts: appAccounts, foreignApps, foreignAssets, - undefined, - undefined, - undefined, - extraPages, - boxes - ); + boxes, + suggestedParams: sp, + }); return; case 'update': - this.txn = algosdk.makeApplicationUpdateTxn( + this.txn = algosdk.makeApplicationUpdateTxnFromObject({ sender, - sp, - appIndex, - approvalProgramBytes, - clearProgramBytes, + appId: appIndex, + approvalProgram: approvalProgramBytes, + clearProgram: clearProgramBytes, appArgs, - appAccounts, + accounts: appAccounts, foreignApps, foreignAssets, - undefined, - undefined, - undefined, - boxes - ); + boxes, + suggestedParams: sp, + }); return; case 'optin': - this.txn = algosdk.makeApplicationOptInTxn( + this.txn = algosdk.makeApplicationOptInTxnFromObject({ sender, - sp, - appIndex, + appId: appIndex, appArgs, - appAccounts, + accounts: appAccounts, foreignApps, foreignAssets, - undefined, - undefined, - undefined, - boxes - ); + boxes, + suggestedParams: sp, + }); return; case 'delete': - this.txn = algosdk.makeApplicationDeleteTxn( + this.txn = algosdk.makeApplicationDeleteTxnFromObject({ sender, - sp, - appIndex, + appId: appIndex, appArgs, - appAccounts, + accounts: appAccounts, foreignApps, foreignAssets, - undefined, - undefined, - undefined, - boxes - ); + boxes, + suggestedParams: sp, + }); return; case 'clear': - this.txn = algosdk.makeApplicationClearStateTxn( + this.txn = algosdk.makeApplicationClearStateTxnFromObject({ sender, - sp, - appIndex, + appId: appIndex, appArgs, - appAccounts, + accounts: appAccounts, foreignApps, foreignAssets, - boxes - ); + boxes, + suggestedParams: sp, + }); return; case 'closeout': - this.txn = algosdk.makeApplicationCloseOutTxn( + this.txn = algosdk.makeApplicationCloseOutTxnFromObject({ sender, - sp, - appIndex, + appId: appIndex, appArgs, - appAccounts, + accounts: appAccounts, foreignApps, foreignAssets, - undefined, - undefined, - undefined, - boxes - ); + boxes, + suggestedParams: sp, + }); return; default: throw Error( @@ -3391,16 +3296,16 @@ module.exports = function getSteps(options) { const sp = await this.v2Client.getTransactionParams().do(); if (sp.firstValid === 0) sp.firstValid = 1; - const fundingTxnArgs = { + const fundingTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ sender: this.accounts[0], receiver: this.transientAccount.addr, amount: fundingAmount, suggestedParams: sp, - }; + }); const stxKmd = await this.kcl.signTransaction( this.handle, this.wallet_pswd, - fundingTxnArgs + fundingTxn ); const fundingResponse = await this.v2Client .sendRawTransaction(stxKmd) @@ -3493,26 +3398,24 @@ module.exports = function getSteps(options) { } const sp = await this.v2Client.getTransactionParams().do(); if (sp.firstValid === 0) sp.firstValid = 1; - const o = { - type: 'appl', + this.txn = algosdk.makeApplicationCallTxnFromObject({ sender: this.transientAccount.addr, - suggestedParams: sp, - appIndex: this.currentApplicationIndex, - appOnComplete: operation, - appLocalInts: numLocalInts, - appLocalByteSlices: numLocalByteSlices, - appGlobalInts: numGlobalInts, - appGlobalByteSlices: numGlobalByteSlices, - appApprovalProgram: approvalProgramBytes, - appClearProgram: clearProgramBytes, + appId: this.currentApplicationIndex, + onComplete: operation, + numLocalInts, + numLocalByteSlices, + numGlobalInts, + numGlobalByteSlices, + extraPages, + approvalProgram: approvalProgramBytes, + clearProgram: clearProgramBytes, appArgs, - appAccounts, - appForeignApps: foreignApps, - appForeignAssets: foreignAssets, + accounts: appAccounts, + foreignApps, + foreignAssets, boxes, - extraPages, - }; - this.txn = new algosdk.Transaction(o); + suggestedParams: sp, + }); } ); @@ -3525,8 +3428,7 @@ module.exports = function getSteps(options) { } catch (err) { if (errorString !== '') { // error was expected. check that err.message includes expected string. - const errorContainsString = err.message.includes(errorString); - assert.deepStrictEqual(true, errorContainsString); + assert.ok(err.message.includes(errorString), err); } else { // unexpected error, rethrow. throw err; From ced00e03ef1c155fa078df66e5f4495c27cf8a9d Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 25 Jan 2024 12:33:06 -0500 Subject: [PATCH 02/17] Fix integration tests --- tests/cucumber/steps/steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 3087bf2fb..fa14c7498 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -995,7 +995,7 @@ module.exports = function getSteps(options) { this.lv = this.params.lastValid; this.note = undefined; this.gh = this.params.genesisHash; - const parsedIssuance = BigInt(issuance); + const parsedIssuance = parseInt(issuance); const decimals = 0; const defaultFrozen = false; const assetName = this.assetTestFixture.name; From be5e4696f1d5adfec24f9f4e3b49040706bd9532 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 26 Jan 2024 14:48:15 -0500 Subject: [PATCH 03/17] Introduce Address class --- examples/accounts.ts | 12 +- examples/app.ts | 2 +- examples/atomics.ts | 2 +- examples/kmd.ts | 2 +- examples/overview.ts | 4 +- examples/utils.ts | 6 +- src/account.ts | 6 +- src/boxStorage.ts | 2 +- src/client/v2/algod/algod.ts | 2 +- src/composer.ts | 55 ++++--- src/dryrun.ts | 18 +-- src/encoding/address.ts | 268 ++++++++++++++++----------------- src/logicsig.ts | 69 +++++---- src/main.ts | 12 +- src/makeTxn.ts | 29 +++- src/mnemonic/mnemonic.ts | 6 +- src/multisig.ts | 174 +++++++++++++++++---- src/signer.ts | 2 +- src/transaction.ts | 82 +++++----- src/types/account.ts | 4 +- src/types/address.ts | 7 - src/types/index.ts | 3 - src/types/multisig.ts | 22 --- src/types/transactions/base.ts | 20 ++- src/utils/utils.ts | 2 +- tests/2.Encoding.ts | 25 +++ tests/3.Address.ts | 35 +++-- tests/6.Multisig.ts | 26 ++-- tests/7.AlgoSDK.ts | 20 +-- tests/8.LogicSig.ts | 47 +++--- tests/cucumber/steps/steps.js | 25 ++- 31 files changed, 580 insertions(+), 409 deletions(-) delete mode 100644 src/types/address.ts delete mode 100644 src/types/index.ts delete mode 100644 src/types/multisig.ts diff --git a/examples/accounts.ts b/examples/accounts.ts index e9289a33e..d1adb7384 100644 --- a/examples/accounts.ts +++ b/examples/accounts.ts @@ -19,7 +19,7 @@ async function main() { const mnemonic = 'creek phrase island true then hope employ veteran rapid hurdle above liberty tissue connect alcohol timber idle ten frog bulb embody crunch taxi abstract month'; const recoveredAccount = algosdk.mnemonicToSecretKey(mnemonic); - console.log('Recovered mnemonic account: ', recoveredAccount.addr); + console.log('Recovered mnemonic account: ', recoveredAccount.addr.toString()); // example: ACCOUNT_RECOVER_MNEMONIC const funder = accounts[0]; @@ -31,14 +31,14 @@ async function main() { signerAccounts.push(algosdk.generateAccount()); // multiSigParams is used when creating the address and when signing transactions - const multiSigParams = { + const multiSigParams: algosdk.MultisigMetadata = { version: 1, threshold: 2, addrs: signerAccounts.map((a) => a.addr), }; const multisigAddr = algosdk.multisigAddress(multiSigParams); - console.log('Created MultiSig Address: ', multisigAddr); + console.log('Created MultiSig Address: ', multisigAddr.toString()); // example: MULTISIG_CREATE const fundMsigTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ @@ -80,7 +80,7 @@ async function main() { // example: ACCOUNT_GENERATE const generatedAccount = algosdk.generateAccount(); const passphrase = algosdk.secretKeyToMnemonic(generatedAccount.sk); - console.log(`My address: ${generatedAccount.addr}`); + console.log(`My address: ${generatedAccount.addr.toString()}`); console.log(`My passphrase: ${passphrase}`); // example: ACCOUNT_GENERATE @@ -98,7 +98,7 @@ async function main() { await client.sendRawTransaction(rekeyTxn.signTxn(acct1.privateKey)).do(); await algosdk.waitForConfirmation(client, rekeyTxn.txID(), 3); - const acctInfo = await client.accountInformation(acct1.addr).do(); + const acctInfo = await client.accountInformation(acct1.addr.toString()).do(); console.log( `Account Info: ${JSON.stringify(acctInfo)} Auth Addr: ${ @@ -117,7 +117,7 @@ async function main() { rekeyTo: acct1.addr, }); await client.sendRawTransaction(rekeyBack.signTxn(acct2.privateKey)).do(); - await algosdk.waitForConfirmation(client, rekeyBack.txID().toString(), 3); + await algosdk.waitForConfirmation(client, rekeyBack.txID(), 3); } main(); diff --git a/examples/app.ts b/examples/app.ts index 3db7fe3de..555285963 100644 --- a/examples/app.ts +++ b/examples/app.ts @@ -143,7 +143,7 @@ async function main() { console.log(`Decoded global state - ${globalKey}: ${globalValue}`); const accountAppInfo = await algodClient - .accountApplicationInformation(caller.addr, appId) + .accountApplicationInformation(caller.addr.toString(), appId) .do(); const localState = accountAppInfo.appLocalState.keyValue[0]; diff --git a/examples/atomics.ts b/examples/atomics.ts index b410f5c60..a86139e0c 100644 --- a/examples/atomics.ts +++ b/examples/atomics.ts @@ -57,7 +57,7 @@ async function main() { // example: TRANSACTION_FEE_OVERRIDE const sp = await client.getTransactionParams().do(); - sp.fee = 2 * minFee; + sp.fee = BigInt(2) * sp.minFee; sp.flatFee = true; // example: TRANSACTION_FEE_OVERRIDE diff --git a/examples/kmd.ts b/examples/kmd.ts index 42d525f3e..a70b5d5c8 100644 --- a/examples/kmd.ts +++ b/examples/kmd.ts @@ -56,7 +56,7 @@ async function main() { // example: KMD_IMPORT_ACCOUNT const newAccount = algosdk.generateAccount(); - console.log('Account: ', newAccount.addr); + console.log('Account: ', newAccount.addr.toString()); const importedAccount = await kmdClient.importKey( wallethandle, newAccount.sk diff --git a/examples/overview.ts b/examples/overview.ts index a94d863ca..b05fa2557 100644 --- a/examples/overview.ts +++ b/examples/overview.ts @@ -45,7 +45,9 @@ async function main() { // example: TRANSACTION_PAYMENT_SUBMIT // example: ALGOD_FETCH_ACCOUNT_INFO - const acctInfo = await algodClient.accountInformation(acct.addr).do(); + const acctInfo = await algodClient + .accountInformation(acct.addr.toString()) + .do(); console.log(`Account balance: ${acctInfo.amount} microAlgos`); // example: ALGOD_FETCH_ACCOUNT_INFO } diff --git a/examples/utils.ts b/examples/utils.ts index a33c73225..b748799c2 100644 --- a/examples/utils.ts +++ b/examples/utils.ts @@ -45,7 +45,7 @@ export function getLocalAlgodClient() { } export interface SandboxAccount { - addr: string; + addr: algosdk.Address; privateKey: Uint8Array; signer: algosdk.TransactionSigner; } @@ -81,8 +81,8 @@ export async function getLocalAccounts(): Promise { kmdClient.releaseWalletHandle(handle); return keys.map((k) => { - const addr = algosdk.encodeAddress(k.private_key.slice(32)); - const acct = { sk: k.private_key, addr } as algosdk.Account; + const addr = new algosdk.Address(k.private_key.slice(32)); + const acct: algosdk.Account = { sk: k.private_key, addr }; const signer = algosdk.makeBasicAccountTransactionSigner(acct); return { diff --git a/src/account.ts b/src/account.ts index 23912402a..89c7b33e4 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,5 +1,5 @@ import * as nacl from './nacl/naclWrappers.js'; -import * as address from './encoding/address.js'; +import { Address } from './encoding/address.js'; import Account from './types/account.js'; /** @@ -7,6 +7,6 @@ import Account from './types/account.js'; */ export default function generateAccount(): Account { const keys = nacl.keyPair(); - const encodedPk = address.encodeAddress(keys.publicKey); - return { addr: encodedPk, sk: keys.secretKey }; + const addr = new Address(keys.publicKey); + return { addr, sk: keys.secretKey }; } diff --git a/src/boxStorage.ts b/src/boxStorage.ts index 14f65c49c..13d7145af 100644 --- a/src/boxStorage.ts +++ b/src/boxStorage.ts @@ -1,4 +1,4 @@ -import { EncodedBoxReference } from './types/index.js'; +import { EncodedBoxReference } from './types/transactions/index.js'; import { BoxReference } from './types/transactions/base.js'; function translateBoxReference( diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index 5b1306b62..de1c642c4 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -37,7 +37,7 @@ import SetBlockOffsetTimestamp from './setBlockOffsetTimestamp.js'; import GetBlockOffsetTimestamp from './getBlockOffsetTimestamp.js'; import Disassemble from './disassemble.js'; import SimulateRawTransactions from './simulateTransaction.js'; -import { EncodedSignedTransaction } from '../../../types/index.js'; +import { EncodedSignedTransaction } from '../../../types/transactions/index.js'; import * as encoding from '../../../encoding/encoding.js'; import Ready from './ready.js'; import UnsetSyncRound from './unsetSyncRound.js'; diff --git a/src/composer.ts b/src/composer.ts index e18433b73..a77f6d4e2 100644 --- a/src/composer.ts +++ b/src/composer.ts @@ -18,6 +18,7 @@ import { SimulateResponse, } from './client/v2/algod/models/types.js'; import * as encoding from './encoding/encoding.js'; +import { Address } from './encoding/address.js'; import { assignGroupID } from './group.js'; import { makeApplicationCallTxnFromObject } from './makeTxn.js'; import { @@ -26,13 +27,13 @@ import { TransactionWithSigner, } from './signer.js'; import { decodeSignedTransaction, Transaction } from './transaction.js'; -import { EncodedSignedTransaction } from './types/index.js'; +import { EncodedSignedTransaction } from './types/transactions/index.js'; import { BoxReference, OnApplicationComplete, SuggestedParams, } from './types/transactions/base.js'; -import { arrayEqual } from './utils/utils.js'; +import { arrayEqual, ensureUint64 } from './utils/utils.js'; import { waitForConfirmation } from './wait.js'; // First 4 bytes of SHA-512/256 hash of "return" @@ -153,7 +154,7 @@ export class AtomicTransactionComposer { theClone.transactions = this.transactions.map(({ txn, signer }) => ({ // not quite a deep copy, but good enough for our purposes (modifying txn.group in buildGroup) txn: Transaction.from_obj_for_encoding({ - ...txn.get_obj_for_encoding()!, + ...txn.get_obj_for_encoding(), // erase the group ID grp: undefined, }), @@ -221,13 +222,13 @@ export class AtomicTransactionComposer { signer, }: { /** The ID of the smart contract to call. Set this to 0 to indicate an application creation call. */ - appID: number; + appID: number | bigint; /** The method to call on the smart contract */ method: ABIMethod; /** The arguments to include in the method call. If omitted, no arguments will be passed to the method. */ methodArgs?: ABIArgument[]; /** The address of the sender of this application call */ - sender: string; + sender: string | Address; /** Transactions params to use for this application call */ suggestedParams: SuggestedParams; /** The OnComplete action to take for this application call. If omitted, OnApplicationComplete.NoOpOC will be used. */ @@ -247,11 +248,11 @@ export class AtomicTransactionComposer { /** The number of extra pages to allocate for the application's programs. Only set this if this is an application creation call. If omitted, defaults to 0. */ extraPages?: number; /** Array of Address strings that represent external accounts supplied to this application. If accounts are provided here, the accounts specified in the method args will appear after these. */ - appAccounts?: string[]; + appAccounts?: Array; /** Array of App ID numbers that represent external apps supplied to this application. If apps are provided here, the apps specified in the method args will appear after these. */ - appForeignApps?: number[]; + appForeignApps?: Array; /** Array of Asset ID numbers that represent external assets supplied to this application. If assets are provided here, the assets specified in the method args will appear after these. */ - appForeignAssets?: number[]; + appForeignAssets?: Array; /** The box references for this application call */ boxes?: BoxReference[]; /** The note value for this application call */ @@ -259,7 +260,7 @@ export class AtomicTransactionComposer { /** The lease value for this application call */ lease?: Uint8Array; /** If provided, the address that the sender will be rekeyed to at the conclusion of this application call */ - rekeyTo?: string; + rekeyTo?: string | Address; /** A transaction signer that can authorize this application call from sender */ signer: TransactionSigner; }): void { @@ -278,7 +279,7 @@ export class AtomicTransactionComposer { ); } - if (appID === 0) { + if (BigInt(appID) === BigInt(0)) { if ( approvalProgram == null || clearProgram == null || @@ -388,12 +389,20 @@ export class AtomicTransactionComposer { } const resolvedRefIndexes: number[] = []; + // Converting addresses to string form for easier comparison const foreignAccounts: string[] = - appAccounts == null ? [] : appAccounts.slice(); - const foreignApps: number[] = - appForeignApps == null ? [] : appForeignApps.slice(); - const foreignAssets: number[] = - appForeignAssets == null ? [] : appForeignAssets.slice(); + appAccounts == null + ? [] + : appAccounts.map((addr) => { + if (typeof addr === 'string') { + return addr; + } + return addr.toString(); + }); + const foreignApps: bigint[] = + appForeignApps == null ? [] : appForeignApps.map(ensureUint64); + const foreignAssets: bigint[] = + appForeignAssets == null ? [] : appForeignAssets.map(ensureUint64); for (let i = 0; i < refArgTypes.length; i++) { const refType = refArgTypes[i]; const refValue = refArgValues[i]; @@ -403,7 +412,13 @@ export class AtomicTransactionComposer { case ABIReferenceType.account: { const addressType = new ABIAddressType(); const address = addressType.decode(addressType.encode(refValue)); - resolved = populateForeignArray(address, foreignAccounts, sender); + const senderString = + typeof sender === 'string' ? sender : sender.toString(); + resolved = populateForeignArray( + address, + foreignAccounts, + senderString + ); break; } case ABIReferenceType.application: { @@ -414,7 +429,11 @@ export class AtomicTransactionComposer { `Expected safe integer for application value, got ${refAppID}` ); } - resolved = populateForeignArray(Number(refAppID), foreignApps, appID); + resolved = populateForeignArray( + refAppID, + foreignApps, + ensureUint64(appID) + ); break; } case ABIReferenceType.asset: { @@ -425,7 +444,7 @@ export class AtomicTransactionComposer { `Expected safe integer for asset value, got ${refAssetID}` ); } - resolved = populateForeignArray(Number(refAssetID), foreignAssets); + resolved = populateForeignArray(refAssetID, foreignAssets); break; } default: diff --git a/src/dryrun.ts b/src/dryrun.ts index 92425f6f0..bbf97c381 100644 --- a/src/dryrun.ts +++ b/src/dryrun.ts @@ -10,7 +10,7 @@ import { EvalDeltaKeyValue, TealValue, } from './client/v2/algod/models/types.js'; -import { encodeAddress, getApplicationAddress } from './encoding/address.js'; +import { getApplicationAddress } from './encoding/address.js'; import { base64ToBytes, bytesToHex } from './encoding/binarydata.js'; import { SignedTransaction } from './transaction.js'; import { TransactionType } from './types/transactions/index.js'; @@ -53,17 +53,17 @@ export async function createDryrun({ for (const t of txns) { if (t.txn.type === TransactionType.appl) { - accts.push(encodeAddress(t.txn.sender.publicKey)); + accts.push(t.txn.sender.toString()); accts.push( - ...t.txn.applicationCall!.appAccounts.map((a) => - encodeAddress(a.publicKey) - ) + ...t.txn.applicationCall!.appAccounts.map((a) => a.toString()) ); apps.push(...t.txn.applicationCall!.appForeignApps); accts.push( - ...t.txn.applicationCall!.appForeignApps.map(getApplicationAddress) + ...t.txn + .applicationCall!.appForeignApps.map(getApplicationAddress) + .map((a) => a.toString()) ); assets.push(...t.txn.applicationCall!.appForeignAssets); @@ -74,7 +74,7 @@ export async function createDryrun({ new Application({ id: defaultAppId, params: new ApplicationParams({ - creator: encodeAddress(t.txn.sender.publicKey), + creator: t.txn.sender.toString(), approvalProgram: t.txn.applicationCall!.appApprovalProgram, clearStateProgram: t.txn.applicationCall!.appClearProgram, localStateSchema: new ApplicationStateSchema({ @@ -91,7 +91,7 @@ export async function createDryrun({ } else { const { appId } = t.txn.applicationCall!; apps.push(appId); - accts.push(getApplicationAddress(appId)); + accts.push(getApplicationAddress(appId).toString()); } } } @@ -140,7 +140,7 @@ export async function createDryrun({ await Promise.all(acctPromises); return new DryrunRequest({ - txns: txns.map((st) => ({ ...st, txn: st.txn.get_obj_for_encoding()! })), + txns: txns.map((st) => ({ ...st, txn: st.txn.get_obj_for_encoding() })), accounts: acctInfos, apps: appInfos, latestTimestamp: latestTimestamp ?? 0, diff --git a/src/encoding/address.ts b/src/encoding/address.ts index 879cab773..77a2f7509 100644 --- a/src/encoding/address.ts +++ b/src/encoding/address.ts @@ -2,70 +2,144 @@ import base32 from 'hi-base32'; import * as nacl from '../nacl/naclWrappers.js'; import * as utils from '../utils/utils.js'; import { encodeUint64 } from './uint64.js'; -import { Address } from '../types/address.js'; -import { MultisigMetadata } from '../types/multisig.js'; +import { bytesToHex } from './binarydata.js'; -const ALGORAND_ADDRESS_BYTE_LENGTH = 36; -const ALGORAND_CHECKSUM_BYTE_LENGTH = 4; -const ALGORAND_ADDRESS_LENGTH = 58; +export const ALGORAND_ADDRESS_BYTE_LENGTH = 36; +export const ALGORAND_CHECKSUM_BYTE_LENGTH = 4; +export const ALGORAND_ADDRESS_LENGTH = 58; export const ALGORAND_ZERO_ADDRESS_STRING = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ'; -// Convert "MultisigAddr" UTF-8 to byte array -const MULTISIG_PREIMG2ADDR_PREFIX = new Uint8Array([ - 77, 117, 108, 116, 105, 115, 105, 103, 65, 100, 100, 114, -]); - -const APP_ID_PREFIX = new TextEncoder().encode('appID'); - export const MALFORMED_ADDRESS_ERROR_MSG = 'address seems to be malformed'; export const CHECKSUM_ADDRESS_ERROR_MSG = 'wrong checksum for address'; -export const INVALID_MSIG_VERSION_ERROR_MSG = 'invalid multisig version'; -export const INVALID_MSIG_THRESHOLD_ERROR_MSG = 'bad multisig threshold'; -export const INVALID_MSIG_PK_ERROR_MSG = - 'bad multisig public key - wrong length'; -export const UNEXPECTED_PK_LEN_ERROR_MSG = - 'nacl public key length is not 32 bytes'; + +function checksumFromPublicKey(pk: Uint8Array): Uint8Array { + return Uint8Array.from( + nacl + .genericHash(pk) + .slice( + nacl.HASH_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, + nacl.HASH_BYTES_LENGTH + ) + ); +} /** - * decodeAddress takes an Algorand address in string form and decodes it into a Uint8Array. - * @param address - an Algorand address with checksum. - * @returns the decoded form of the address's public key and checksum + * Represents an Algorand address */ -export function decodeAddress(address: string): Address { - if (typeof address !== 'string' || address.length !== ALGORAND_ADDRESS_LENGTH) - throw new Error(MALFORMED_ADDRESS_ERROR_MSG); - - // try to decode - const decoded = base32.decode.asBytes(address.toString()); - // Sanity check - if (decoded.length !== ALGORAND_ADDRESS_BYTE_LENGTH) - throw new Error(MALFORMED_ADDRESS_ERROR_MSG); - - // Find publickey and checksum - const pk = new Uint8Array( - decoded.slice( - 0, +export class Address { + /** + * The binary form of the address. For standard accounts, this is the public key. + */ + public readonly publicKey: Uint8Array; + + /** + * Create a new Address object from its binary form. + * @param publicKey - The binary form of the address. Must be 32 bytes. + */ + constructor(publicKey: Uint8Array) { + if (!(publicKey instanceof Uint8Array)) { + throw new Error( + `${MALFORMED_ADDRESS_ERROR_MSG}: ${publicKey} is not Uint8Array, type ${typeof publicKey}` + ); + } + if ( + publicKey.length !== ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH ) - ); - const cs = new Uint8Array( - decoded.slice(nacl.PUBLIC_KEY_LENGTH, ALGORAND_ADDRESS_BYTE_LENGTH) - ); + throw new Error( + `${MALFORMED_ADDRESS_ERROR_MSG}: 0x${bytesToHex(publicKey)}, length ${publicKey.length}` + ); + this.publicKey = publicKey; + } - // Compute checksum - const checksum = nacl - .genericHash(pk) - .slice( - nacl.HASH_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, - nacl.HASH_BYTES_LENGTH + /** + * Check if the address is equal to another address. + */ + equals(other: Address): boolean { + return ( + other instanceof Address && + utils.arrayEqual(this.publicKey, other.publicKey) ); + } - // Check if the checksum and the address are equal - if (!utils.arrayEqual(checksum, cs)) - throw new Error(CHECKSUM_ADDRESS_ERROR_MSG); + /** + * Compute the 4 byte checksum of the address. + */ + checksum(): Uint8Array { + return checksumFromPublicKey(this.publicKey); + } - return { publicKey: pk, checksum: cs }; + /** + * Encode the address into a string form. + */ + toString(): string { + const addr = base32.encode( + utils.concatArrays(this.publicKey, this.checksum()) + ); + return addr.slice(0, ALGORAND_ADDRESS_LENGTH); // removing the extra '====' + } + + /** + * Decode an address from a string. + * @param address - The address to decode. Must be 58 bytes long. + * @returns An Address object corresponding to the input string. + */ + static fromString(address: string): Address { + if (typeof address !== 'string') + throw new Error( + `${MALFORMED_ADDRESS_ERROR_MSG}: expected string, got ${typeof address}, ${address}` + ); + if (address.length !== ALGORAND_ADDRESS_LENGTH) + throw new Error( + `${MALFORMED_ADDRESS_ERROR_MSG}: expected length ${ALGORAND_ADDRESS_LENGTH}, got ${address.length}: ${address}` + ); + + // try to decode + const decoded = base32.decode.asBytes(address); + // Sanity check + if (decoded.length !== ALGORAND_ADDRESS_BYTE_LENGTH) + throw new Error( + `${MALFORMED_ADDRESS_ERROR_MSG}: expected byte length ${ALGORAND_ADDRESS_BYTE_LENGTH}, got ${decoded.length}` + ); + + // Find publickey and checksum + const pk = new Uint8Array( + decoded.slice( + 0, + ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH + ) + ); + const cs = new Uint8Array( + decoded.slice(nacl.PUBLIC_KEY_LENGTH, ALGORAND_ADDRESS_BYTE_LENGTH) + ); + const checksum = checksumFromPublicKey(pk); + // Check if the checksum and the address are equal + if (!utils.arrayEqual(checksum, cs)) + throw new Error(CHECKSUM_ADDRESS_ERROR_MSG); + + return new Address(pk); + } + + /** + * Get the zero address. + */ + static zeroAddress(): Address { + return new Address( + new Uint8Array( + ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH + ) + ); + } +} + +/** + * decodeAddress takes an Algorand address in string form and decodes it into a Uint8Array. + * @param address - an Algorand address with checksum. + * @returns the decoded form of the address's public key and checksum + */ +export function decodeAddress(address: string): Address { + return Address.fromString(address); } /** @@ -73,10 +147,10 @@ export function decodeAddress(address: string): Address { * @param address - an Algorand address with checksum. * @returns true if valid, false otherwise */ -export function isValidAddress(address: string) { +export function isValidAddress(address: string): boolean { // Try to decode try { - decodeAddress(address); + Address.fromString(address); } catch (e) { return false; } @@ -88,101 +162,19 @@ export function isValidAddress(address: string) { * @param address - a raw Algorand address * @returns the address and checksum encoded as a string. */ -export function encodeAddress(address: Uint8Array) { - // Check address length - if ( - address.length !== - ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH - ) - throw new Error( - `${MALFORMED_ADDRESS_ERROR_MSG}: ${address}, length ${address.length}` - ); - // compute checksum - const checksum = nacl - .genericHash(address) - .slice( - nacl.PUBLIC_KEY_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH, - nacl.PUBLIC_KEY_LENGTH - ); - const addr = base32.encode(utils.concatArrays(address, checksum)); - - return addr.toString().slice(0, ALGORAND_ADDRESS_LENGTH); // removing the extra '====' +export function encodeAddress(address: Uint8Array): string { + return new Address(address).toString(); } -/** - * fromMultisigPreImg takes multisig parameters and returns a 32 byte typed array public key, - * representing an address that identifies the "exact group, version, and public keys" that are required for signing. - * Hash("MultisigAddr" || version uint8 || threshold uint8 || PK1 || PK2 || ...) - * Encoding this output yields a human readable address. - * @param version - multisig version - * @param threshold - multisig threshold - * @param pks - array of typed array public keys - */ -export function fromMultisigPreImg({ - version, - threshold, - pks, -}: Omit & { - pks: Uint8Array[]; -}) { - if (version !== 1 || version > 255 || version < 0) { - // ^ a tad redundant, but in case in the future version != 1, still check for uint8 - throw new Error(INVALID_MSIG_VERSION_ERROR_MSG); - } - if ( - threshold === 0 || - pks.length === 0 || - threshold > pks.length || - threshold > 255 - ) { - throw new Error(INVALID_MSIG_THRESHOLD_ERROR_MSG); - } - const pkLen = ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH; - if (pkLen !== nacl.PUBLIC_KEY_LENGTH) { - throw new Error(UNEXPECTED_PK_LEN_ERROR_MSG); - } - const merged = new Uint8Array( - MULTISIG_PREIMG2ADDR_PREFIX.length + 2 + pkLen * pks.length - ); - merged.set(MULTISIG_PREIMG2ADDR_PREFIX, 0); - merged.set([version], MULTISIG_PREIMG2ADDR_PREFIX.length); - merged.set([threshold], MULTISIG_PREIMG2ADDR_PREFIX.length + 1); - for (let i = 0; i < pks.length; i++) { - if (pks[i].length !== pkLen) { - throw new Error(INVALID_MSIG_PK_ERROR_MSG); - } - merged.set(pks[i], MULTISIG_PREIMG2ADDR_PREFIX.length + 2 + i * pkLen); - } - return new Uint8Array(nacl.genericHash(merged)); -} - -/** - * fromMultisigPreImgAddrs takes multisig parameters and returns a human readable Algorand address. - * This is equivalent to fromMultisigPreImg, but interfaces with encoded addresses. - * @param version - multisig version - * @param threshold - multisig threshold - * @param addrs - array of encoded addresses - */ -export function fromMultisigPreImgAddrs({ - version, - threshold, - addrs, -}: { - version: number; - threshold: number; - addrs: string[]; -}) { - const pks = addrs.map((addr) => decodeAddress(addr).publicKey); - return encodeAddress(fromMultisigPreImg({ version, threshold, pks })); -} +const APP_ID_PREFIX = new TextEncoder().encode('appID'); /** * Get the escrow address of an application. * @param appID - The ID of the application. * @returns The address corresponding to that application's escrow account. */ -export function getApplicationAddress(appID: number | bigint): string { +export function getApplicationAddress(appID: number | bigint): Address { const toBeSigned = utils.concatArrays(APP_ID_PREFIX, encodeUint64(appID)); const hash = nacl.genericHash(toBeSigned); - return encodeAddress(new Uint8Array(hash)); + return new Address(Uint8Array.from(hash)); } diff --git a/src/logicsig.ts b/src/logicsig.ts index 1e02a4c6e..fd51d74d3 100644 --- a/src/logicsig.ts +++ b/src/logicsig.ts @@ -1,17 +1,20 @@ import * as nacl from './nacl/naclWrappers.js'; -import * as address from './encoding/address.js'; +import { Address, isValidAddress } from './encoding/address.js'; import * as encoding from './encoding/encoding.js'; -import { verifyMultisig } from './multisig.js'; +import { + MultisigMetadata, + verifyMultisig, + addressFromMultisigPreImg, + pksFromAddresses, +} from './multisig.js'; import * as utils from './utils/utils.js'; import { Transaction } from './transaction.js'; -import { isValidAddress } from './encoding/address.js'; import { EncodedLogicSig, EncodedLogicSigAccount, EncodedMultisig, EncodedSignedTransaction, } from './types/transactions/encoded.js'; -import { MultisigMetadata } from './types/multisig.js'; interface LogicSigStorageStructure { logic: Uint8Array; @@ -147,10 +150,10 @@ export class LogicSig implements LogicSigStorageStructure { * Compute hash of the logic sig program (that is the same as escrow account address) as string address * @returns String representation of the address */ - address() { + address(): Address { const toBeSigned = utils.concatArrays(this.tag, this.logic); const hash = nacl.genericHash(toBeSigned); - return address.encodeAddress(new Uint8Array(hash)); + return new Address(Uint8Array.from(hash)); } /** @@ -162,9 +165,7 @@ export class LogicSig implements LogicSigStorageStructure { if (msig == null) { this.sig = this.signProgram(secretKey); } else { - const subsigs = msig.addrs.map((addr) => ({ - pk: address.decodeAddress(addr).publicKey, - })); + const subsigs = pksFromAddresses(msig.addrs).map((pk) => ({ pk })); this.msig = { v: msig.version, @@ -298,7 +299,7 @@ export class LogicSigAccount { */ verify() { const addr = this.address(); - return this.lsig.verify(address.decodeAddress(addr).publicKey); + return this.lsig.verify(addr.publicKey); } /** @@ -310,7 +311,7 @@ export class LogicSigAccount { * If the LogicSig is not delegated to another account, this will return an * escrow address that is the hash of the LogicSig's program code. */ - address() { + address(): Address { if (this.lsig.sig && this.lsig.msig) { throw new Error( 'LogicSig has too many signatures. At most one of sig or msig may be present' @@ -321,7 +322,7 @@ export class LogicSigAccount { if (!this.sigkey) { throw new Error('Signing key for delegated account is missing'); } - return address.encodeAddress(this.sigkey); + return new Address(this.sigkey); } if (this.lsig.msig) { @@ -330,7 +331,7 @@ export class LogicSigAccount { threshold: this.lsig.msig.thr, pks: this.lsig.msig.subsig.map((subsig) => subsig.pk), }; - return address.encodeAddress(address.fromMultisigPreImg(msigMetadata)); + return addressFromMultisigPreImg(msigMetadata); } return this.lsig.address(); @@ -379,9 +380,9 @@ export class LogicSigAccount { function signLogicSigTransactionWithAddress( txn: Transaction, lsig: LogicSig, - lsigAddress: Uint8Array + lsigAddress: Address ) { - if (!lsig.verify(lsigAddress)) { + if (!lsig.verify(lsigAddress.publicKey)) { throw new Error( 'Logic signature verification failed. Ensure the program and signature are valid.' ); @@ -389,11 +390,11 @@ function signLogicSigTransactionWithAddress( const signedTxn: EncodedSignedTransaction = { lsig: lsig.get_obj_for_encoding(), - txn: txn.get_obj_for_encoding()!, + txn: txn.get_obj_for_encoding(), }; - if (!nacl.bytesEqual(lsigAddress, txn.sender.publicKey)) { - signedTxn.sgnr = lsigAddress; + if (!nacl.bytesEqual(lsigAddress.publicKey, txn.sender.publicKey)) { + signedTxn.sgnr = lsigAddress.publicKey; } return { @@ -416,11 +417,11 @@ export function signLogicSigTransactionObject( lsigObject: LogicSig | LogicSigAccount ) { let lsig: LogicSig; - let lsigAddress: Uint8Array; + let lsigAddress: Address; if (lsigObject instanceof LogicSigAccount) { lsig = lsigObject.lsig; - lsigAddress = address.decodeAddress(lsigObject.address()).publicKey; + lsigAddress = lsigObject.address(); } else { lsig = lsigObject; @@ -429,16 +430,16 @@ export function signLogicSigTransactionObject( // the address of that account from only its signature, so assume the // delegating account is the sender. If that's not the case, the signing // will fail. - lsigAddress = txn.sender.publicKey; + lsigAddress = new Address(txn.sender.publicKey); } else if (lsig.msig) { const msigMetadata = { version: lsig.msig.v, threshold: lsig.msig.thr, pks: lsig.msig.subsig.map((subsig) => subsig.pk), }; - lsigAddress = address.fromMultisigPreImg(msigMetadata); + lsigAddress = addressFromMultisigPreImg(msigMetadata); } else { - lsigAddress = address.decodeAddress(lsig.address()).publicKey; + lsigAddress = lsig.address(); } } @@ -481,12 +482,13 @@ const SIGN_PROGRAM_DATA_PREFIX = new TextEncoder().encode('ProgData'); export function tealSign( sk: Uint8Array, data: Uint8Array, - programHash: string + programHash: string | Address ) { - const parts = utils.concatArrays( - address.decodeAddress(programHash).publicKey, - data - ); + const programAddr = + typeof programHash === 'string' + ? Address.fromString(programHash) + : programHash; + const parts = utils.concatArrays(programAddr.publicKey, data); const toBeSigned = utils.concatArrays(SIGN_PROGRAM_DATA_PREFIX, parts); return nacl.sign(toBeSigned, sk); } @@ -500,14 +502,15 @@ export function tealSign( */ export function verifyTealSign( data: Uint8Array, - programHash: string, + programHash: string | Address, sig: Uint8Array, pk: Uint8Array ) { - const parts = utils.concatArrays( - address.decodeAddress(programHash).publicKey, - data - ); + const programAddr = + typeof programHash === 'string' + ? Address.fromString(programHash) + : programHash; + const parts = utils.concatArrays(programAddr.publicKey, data); const toBeSigned = utils.concatArrays(SIGN_PROGRAM_DATA_PREFIX, parts); return nacl.verify(toBeSigned, sig, pk); } diff --git a/src/main.ts b/src/main.ts index 5783d37b7..4d86829e0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,5 @@ import * as nacl from './nacl/naclWrappers.js'; -import * as address from './encoding/address.js'; +import { Address } from './encoding/address.js'; import * as encoding from './encoding/encoding.js'; import { Transaction } from './transaction.js'; import * as convert from './convert.js'; @@ -57,11 +57,11 @@ export function signBytes(bytes: Uint8Array, sk: Uint8Array) { export function verifyBytes( bytes: Uint8Array, signature: Uint8Array, - addr: string + addr: string | Address ) { const toBeVerified = utils.concatArrays(SIGN_BYTES_PREFIX, bytes); - const pk = address.decodeAddress(addr).publicKey; - return nacl.verify(toBeVerified, signature, pk); + const addrObj = typeof addr === 'string' ? Address.fromString(addr) : addr; + return nacl.verify(toBeVerified, signature, addrObj.publicKey); } /** @@ -109,6 +109,7 @@ export { } from './client/urlTokenBaseHTTPClient.js'; export { waitForConfirmation } from './wait.js'; export { + Address, isValidAddress, encodeAddress, decodeAddress, @@ -152,6 +153,7 @@ export { verifyTealSign, } from './logicsig.js'; export { + MultisigMetadata, signMultisigTransaction, mergeMultisigTransactions, appendSignMultisigTransaction, @@ -171,5 +173,5 @@ export * from './makeTxn.js'; export * from './transaction.js'; export * from './signer.js'; export * from './composer.js'; -export * from './types/index.js'; +export * from './types/transactions/index.js'; export * from './abi/index.js'; diff --git a/src/makeTxn.ts b/src/makeTxn.ts index eacdab0b5..4102312c8 100644 --- a/src/makeTxn.ts +++ b/src/makeTxn.ts @@ -10,7 +10,7 @@ import { AssetFreezeTransactionParams, ApplicationCallTransactionParams, } from './types/transactions/base.js'; -import { Address } from './types/address.js'; +import { Address } from './encoding/address.js'; /** Contains parameters common to every transaction type */ export interface CommonTransactionParams { @@ -408,6 +408,9 @@ export function makeApplicationCallTxnFromObject({ rekeyTo, suggestedParams, }: ApplicationCallTransactionParams & CommonTransactionParams): Transaction { + if (onComplete == null) { + throw Error('onComplete must be provided'); + } return new Transaction({ type: TransactionType.appl, sender, @@ -458,8 +461,20 @@ export function makeApplicationCreateTxnFromObject({ lease, rekeyTo, suggestedParams, -}: Omit & // TODO: make programs required +}: Omit< + ApplicationCallTransactionParams, + 'appId' | 'approvalProgram' | 'clearProgram' +> & + Required< + Pick + > & CommonTransactionParams): Transaction { + if (!approvalProgram || !clearProgram) { + throw Error('approvalProgram and clearProgram must be provided'); + } + if (onComplete == null) { + throw Error('onComplete must be provided'); + } return makeApplicationCallTxnFromObject({ sender, appId: 0, @@ -510,11 +525,19 @@ export function makeApplicationUpdateTxnFromObject({ | 'numGlobalInts' | 'numGlobalByteSlices' | 'extraPages' -> & // TODO: make programs required + | 'approvalProgram' + | 'clearProgram' +> & + Required< + Pick + > & CommonTransactionParams): Transaction { if (!appId) { throw Error('appId must be provided'); } + if (!approvalProgram || !clearProgram) { + throw Error('approvalProgram and clearProgram must be provided'); + } return makeApplicationCallTxnFromObject({ sender, appId, diff --git a/src/mnemonic/mnemonic.ts b/src/mnemonic/mnemonic.ts index 6bf251e0c..0b9adadea 100644 --- a/src/mnemonic/mnemonic.ts +++ b/src/mnemonic/mnemonic.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import english from './wordlists/english.js'; import * as nacl from '../nacl/naclWrappers.js'; -import * as address from '../encoding/address.js'; +import { Address } from '../encoding/address.js'; import Account from '../types/account.js'; export const FAIL_TO_DECODE_MNEMONIC_ERROR_MSG = 'failed to decode mnemonic'; @@ -146,8 +146,8 @@ export function seedFromMnemonic(mnemonic: string) { export function mnemonicToSecretKey(mn: string): Account { const seed = seedFromMnemonic(mn); const keys = nacl.keyPairFromSeed(seed); - const encodedPk = address.encodeAddress(keys.publicKey); - return { addr: encodedPk, sk: keys.secretKey }; + const addr = new Address(keys.publicKey); + return { addr, sk: keys.secretKey }; } /** diff --git a/src/multisig.ts b/src/multisig.ts index 9ff0deb76..8863f08d0 100644 --- a/src/multisig.ts +++ b/src/multisig.ts @@ -1,9 +1,12 @@ import * as nacl from './nacl/naclWrappers.js'; -import * as address from './encoding/address.js'; +import { + Address, + ALGORAND_ADDRESS_BYTE_LENGTH, + ALGORAND_CHECKSUM_BYTE_LENGTH, +} from './encoding/address.js'; import * as encoding from './encoding/encoding.js'; import { Transaction } from './transaction.js'; import * as utils from './utils/utils.js'; -import { MultisigMetadata } from './types/multisig.js'; import { EncodedMultisig, EncodedSignedTransaction, @@ -31,6 +34,29 @@ export const MULTISIG_USE_PARTIAL_SIGN_ERROR_MSG = export const MULTISIG_SIGNATURE_LENGTH_ERROR_MSG = 'Cannot add multisig signature. Signature is not of the correct length.'; +/** + * Required options for creating a multisignature + * + * Documentation available at: https://developer.algorand.org/docs/get-details/transactions/signatures/#multisignatures + */ +export interface MultisigMetadata { + /** + * Multisig version + */ + version: number; + + /** + * Multisig threshold value. Authorization requires a subset of signatures, + * equal to or greater than the threshold value. + */ + threshold: number; + + /** + * A list of Algorand addresses representing possible signers for this multisig. Order is important. + */ + addrs: Array; +} + interface MultisigOptions { rawSig: Uint8Array; myPk: Uint8Array; @@ -40,6 +66,88 @@ interface MultisigMetadataWithPks extends Omit { pks: Uint8Array[]; } +// Convert "MultisigAddr" UTF-8 to byte array +const MULTISIG_PREIMG2ADDR_PREFIX = new Uint8Array([ + 77, 117, 108, 116, 105, 115, 105, 103, 65, 100, 100, 114, +]); + +const INVALID_MSIG_VERSION_ERROR_MSG = 'invalid multisig version'; +const INVALID_MSIG_THRESHOLD_ERROR_MSG = 'bad multisig threshold'; +const INVALID_MSIG_PK_ERROR_MSG = 'bad multisig public key - wrong length'; +const UNEXPECTED_PK_LEN_ERROR_MSG = 'nacl public key length is not 32 bytes'; + +export function pksFromAddresses(addrs: Array): Uint8Array[] { + return addrs.map((addr) => { + if (typeof addr === 'string') { + return Address.fromString(addr).publicKey; + } + return addr.publicKey; + }); +} + +/** + * fromMultisigPreImg takes multisig parameters and returns a 32 byte typed array public key, + * representing an address that identifies the "exact group, version, and public keys" that are required for signing. + * Hash("MultisigAddr" || version uint8 || threshold uint8 || PK1 || PK2 || ...) + * Encoding this output yields a human readable address. + * @param version - multisig version + * @param threshold - multisig threshold + * @param pks - array of typed array public keys + */ +export function addressFromMultisigPreImg({ + version, + threshold, + pks, +}: Omit & { + pks: Uint8Array[]; +}): Address { + if (version !== 1 || version > 255 || version < 0) { + // ^ a tad redundant, but in case in the future version != 1, still check for uint8 + throw new Error(INVALID_MSIG_VERSION_ERROR_MSG); + } + if ( + threshold === 0 || + pks.length === 0 || + threshold > pks.length || + threshold > 255 + ) { + throw new Error(INVALID_MSIG_THRESHOLD_ERROR_MSG); + } + const pkLen = ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH; + if (pkLen !== nacl.PUBLIC_KEY_LENGTH) { + throw new Error(UNEXPECTED_PK_LEN_ERROR_MSG); + } + const merged = new Uint8Array( + MULTISIG_PREIMG2ADDR_PREFIX.length + 2 + pkLen * pks.length + ); + merged.set(MULTISIG_PREIMG2ADDR_PREFIX, 0); + merged.set([version], MULTISIG_PREIMG2ADDR_PREFIX.length); + merged.set([threshold], MULTISIG_PREIMG2ADDR_PREFIX.length + 1); + for (let i = 0; i < pks.length; i++) { + if (pks[i].length !== pkLen) { + throw new Error(INVALID_MSIG_PK_ERROR_MSG); + } + merged.set(pks[i], MULTISIG_PREIMG2ADDR_PREFIX.length + 2 + i * pkLen); + } + return new Address(Uint8Array.from(nacl.genericHash(merged))); +} + +/** + * fromMultisigPreImgAddrs takes multisig parameters and returns a human readable Algorand address. + * This is equivalent to fromMultisigPreImg, but interfaces with encoded addresses. + * @param version - multisig version + * @param threshold - multisig threshold + * @param addrs - array of encoded addresses + */ +export function addressFromMultisigPreImgAddrs({ + version, + threshold, + addrs, +}: MultisigMetadata): Address { + const pks = pksFromAddresses(addrs); + return addressFromMultisigPreImg({ version, threshold, pks }); +} + /** * createMultisigTransaction creates a raw, unsigned multisig transaction blob. * @param txn - the actual transaction. @@ -53,7 +161,7 @@ export function createMultisigTransaction( { version, threshold, addrs }: MultisigMetadata ) { // construct the appendable multisigned transaction format - const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey); + const pks = pksFromAddresses(addrs); const subsigs = pks.map((pk) => ({ pk })); const msig: EncodedMultisig = { @@ -61,7 +169,7 @@ export function createMultisigTransaction( thr: threshold, subsig: subsigs, }; - const txnForEncoding = txn.get_obj_for_encoding()!; + const txnForEncoding = txn.get_obj_for_encoding(); const signedTxn: EncodedSignedTransaction = { msig, txn: txnForEncoding, @@ -69,16 +177,16 @@ export function createMultisigTransaction( // if the address of this multisig is different from the transaction sender, // we need to add the auth-addr field - const msigAddr = address.fromMultisigPreImg({ + const msigAddr = addressFromMultisigPreImg({ version, threshold, pks, }); const senderAddr = txnForEncoding.snd - ? address.encodeAddress(txnForEncoding.snd) - : address.ALGORAND_ZERO_ADDRESS_STRING; - if (senderAddr !== address.encodeAddress(msigAddr)) { - signedTxn.sgnr = msigAddr; + ? new Address(txnForEncoding.snd) + : Address.zeroAddress(); + if (!senderAddr.equals(msigAddr)) { + signedTxn.sgnr = msigAddr.publicKey; } return new Uint8Array(encoding.encode(signedTxn)); @@ -103,7 +211,7 @@ function createMultisigTransactionWithSignature( const encodedMsig = createMultisigTransaction(txn, { version, threshold, - addrs: pks.map((pk) => address.encodeAddress(pk)), + addrs: pks.map((pk) => new Address(pk)), }); // note: this is not signed yet, but will be shortly const signedTxn = encoding.decode(encodedMsig) as EncodedSignedTransaction; @@ -122,16 +230,16 @@ function createMultisigTransactionWithSignature( // if the address of this multisig is different from the transaction sender, // we need to add the auth-addr field - const msigAddr = address.fromMultisigPreImg({ + const msigAddr = addressFromMultisigPreImg({ version, threshold, pks, }); const senderAddr = signedTxn.txn.snd - ? address.encodeAddress(signedTxn.txn.snd) - : address.ALGORAND_ZERO_ADDRESS_STRING; - if (senderAddr !== address.encodeAddress(msigAddr)) { - signedTxn.sgnr = msigAddr; + ? new Address(signedTxn.txn.snd) + : Address.zeroAddress(); + if (!senderAddr.equals(msigAddr)) { + signedTxn.sgnr = msigAddr.publicKey; } return new Uint8Array(encoding.encode(signedTxn)); @@ -173,17 +281,21 @@ function partialSignTxn( function partialSignWithMultisigSignature( transaction: Transaction, metadata: MultisigMetadataWithPks, - signerAddr: string, + signerAddr: string | Address, signature: Uint8Array ) { if (!nacl.isValidSignatureLength(signature.length)) { throw new Error(MULTISIG_SIGNATURE_LENGTH_ERROR_MSG); } + const signerAddressObj = + typeof signerAddr === 'string' + ? Address.fromString(signerAddr) + : signerAddr; return createMultisigTransactionWithSignature( transaction, { rawSig: signature, - myPk: address.decodeAddress(signerAddr).publicKey, + myPk: signerAddressObj.publicKey, }, metadata ); @@ -208,16 +320,14 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { } const refTxID = Transaction.from_obj_for_encoding(refSigTx.txn).txID(); const refAuthAddr = refSigTx.sgnr - ? address.encodeAddress(refSigTx.sgnr) + ? new Address(refSigTx.sgnr).toString() : undefined; const refPreImage = { version: refSigTx.msig.v, threshold: refSigTx.msig.thr, pks: refSigTx.msig.subsig.map((subsig) => subsig.pk), }; - const refMsigAddr = address.encodeAddress( - address.fromMultisigPreImg(refPreImage) - ); + const refMsigAddr = addressFromMultisigPreImg(refPreImage); const newSubsigs = refSigTx.msig.subsig.map((sig) => ({ ...sig })); for (let i = 1; i < multisigTxnBlobs.length; i++) { @@ -236,7 +346,7 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { } const authAddr = unisig.sgnr - ? address.encodeAddress(unisig.sgnr) + ? new Address(unisig.sgnr).toString() : undefined; if (refAuthAddr !== authAddr) { throw new Error(MULTISIG_MERGE_MISMATCH_AUTH_ADDR_MSG); @@ -251,8 +361,8 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { threshold: unisig.msig.thr, pks: unisig.msig.subsig.map((subsig) => subsig.pk), }; - const msgigAddr = address.encodeAddress(address.fromMultisigPreImg(preimg)); - if (refMsigAddr !== msgigAddr) { + const msgigAddr = addressFromMultisigPreImg(preimg); + if (!refMsigAddr.equals(msgigAddr)) { throw new Error(MULTISIG_MERGE_WRONG_PREIMAGE_ERROR_MSG); } @@ -277,7 +387,7 @@ export function mergeMultisigTransactions(multisigTxnBlobs: Uint8Array[]) { txn: refSigTx.txn, }; if (typeof refAuthAddr !== 'undefined') { - signedTxn.sgnr = address.decodeAddress(refAuthAddr).publicKey; + signedTxn.sgnr = Address.fromString(refAuthAddr).publicKey; } return new Uint8Array(encoding.encode(signedTxn)); } @@ -298,7 +408,7 @@ export function verifyMultisig( let pk: Uint8Array; try { - pk = address.fromMultisigPreImg({ version, threshold, pks }); + pk = addressFromMultisigPreImg({ version, threshold, pks }).publicKey; } catch (e) { return false; } @@ -351,7 +461,7 @@ export function signMultisigTransaction( sk: Uint8Array ) { // build pks for partialSign - const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey); + const pks = pksFromAddresses(addrs); const blob = partialSignTxn(txn, { version, threshold, pks }, sk); return { txID: txn.txID(), @@ -375,7 +485,7 @@ export function appendSignMultisigTransaction( { version, threshold, addrs }: MultisigMetadata, sk: Uint8Array ) { - const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey); + const pks = pksFromAddresses(addrs); // obtain underlying txn, sign it, and merge it const multisigTxObj = encoding.decode( multisigTxnBlob @@ -406,10 +516,10 @@ export function appendSignMultisigTransaction( export function appendSignRawMultisigSignature( multisigTxnBlob: Uint8Array, { version, threshold, addrs }: MultisigMetadata, - signerAddr: string, + signerAddr: string | Address, signature: Uint8Array ) { - const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey); + const pks = pksFromAddresses(addrs); // obtain underlying txn, sign it, and merge it const multisigTxObj = encoding.decode( multisigTxnBlob @@ -437,6 +547,6 @@ export function multisigAddress({ version, threshold, addrs, -}: MultisigMetadata) { - return address.fromMultisigPreImgAddrs({ version, threshold, addrs }); +}: MultisigMetadata): Address { + return addressFromMultisigPreImgAddrs({ version, threshold, addrs }); } diff --git a/src/signer.ts b/src/signer.ts index bc1945625..037b3fe4b 100644 --- a/src/signer.ts +++ b/src/signer.ts @@ -4,8 +4,8 @@ import { } from './transaction.js'; import Account from './types/account.js'; import { LogicSigAccount, signLogicSigTransactionObject } from './logicsig.js'; -import { MultisigMetadata } from './types/multisig.js'; import { + MultisigMetadata, signMultisigTransaction, mergeMultisigTransactions, } from './multisig.js'; diff --git a/src/transaction.ts b/src/transaction.ts index 8b06ab6e7..82110aefc 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -1,11 +1,10 @@ // @ts-nocheck // Temporary type fix, will be unnecessary in following PR import base32 from 'hi-base32'; import { translateBoxReferences } from './boxStorage.js'; -import * as address from './encoding/address.js'; +import { Address } from './encoding/address.js'; import { base64ToBytes, bytesToBase64 } from './encoding/binarydata.js'; import * as encoding from './encoding/encoding.js'; import * as nacl from './nacl/naclWrappers.js'; -import { Address } from './types/address.js'; import { EncodedLogicSig, EncodedMultisig, @@ -19,6 +18,7 @@ import { SuggestedParams, BoxReference, OnApplicationComplete, + isOnApplicationComplete, TransactionParams, TransactionType, isTransactionType, @@ -77,14 +77,10 @@ function ensureAddress(input: unknown): Address { throw new Error('Address must not be null or undefined'); } if (typeof input === 'string') { - return address.decodeAddress(input); + return Address.fromString(input); } - if ( - typeof input === 'object' && - (input as Record).publicKey instanceof Uint8Array && - (input as Record).checksum instanceof Uint8Array - ) { - return input as Address; + if (input instanceof Address) { + return input; } throw new Error(`Not an address: ${input}`); } @@ -94,14 +90,10 @@ function optionalAddress(input: unknown): Address | undefined { return undefined; } let addr: Address; - if ( - typeof input === 'object' && - (input as Record).publicKey instanceof Uint8Array && - (input as Record).checksum instanceof Uint8Array - ) { - addr = input as Address; + if (input instanceof Address) { + addr = input; } else if (typeof input === 'string') { - addr = address.decodeAddress(input); + addr = Address.fromString(input); } else { throw new Error(`Not an address: ${input}`); } @@ -468,9 +460,13 @@ export class Transaction { } if (params.appCallParams) { + const { onComplete } = params.appCallParams; + if (!isOnApplicationComplete(onComplete)) { + throw new Error(`Invalid onCompletion value: ${onComplete}`); + } this.applicationCall = { appId: utils.ensureUint64(params.appCallParams.appId), - appOnComplete: params.appCallParams.onComplete, // TODO: verify + appOnComplete: onComplete, appLocalInts: utils.ensureSafeUnsignedInteger( params.appCallParams.numLocalInts ?? 0 ), @@ -783,26 +779,26 @@ export class Transaction { const params: TransactionParams = { type: txnForEnc.type, sender: txnForEnc.snd - ? address.encodeAddress(txnForEnc.snd) - : address.ALGORAND_ZERO_ADDRESS_STRING, + ? new Address(txnForEnc.snd) + : Address.zeroAddress(), note: txnForEnc.note, lease: txnForEnc.lx, suggestedParams, }; if (txnForEnc.rekey) { - params.rekeyTo = address.encodeAddress(txnForEnc.rekey); + params.rekeyTo = new Address(txnForEnc.rekey); } if (params.type === TransactionType.pay) { const paymentParams: PaymentTransactionParams = { amount: txnForEnc.amt ?? 0, receiver: txnForEnc.rcv - ? address.encodeAddress(txnForEnc.rcv) - : address.ALGORAND_ZERO_ADDRESS_STRING, + ? new Address(txnForEnc.rcv) + : Address.zeroAddress(), }; if (txnForEnc.close) { - paymentParams.closeRemainderTo = address.encodeAddress(txnForEnc.close); + paymentParams.closeRemainderTo = new Address(txnForEnc.close); } params.paymentParams = paymentParams; } else if (params.type === TransactionType.keyreg) { @@ -829,16 +825,16 @@ export class Transaction { assetConfigParams.assetURL = txnForEnc.apar.au; assetConfigParams.assetMetadataHash = txnForEnc.apar.am; if (txnForEnc.apar.m) { - assetConfigParams.manager = address.encodeAddress(txnForEnc.apar.m); + assetConfigParams.manager = new Address(txnForEnc.apar.m); } if (txnForEnc.apar.r) { - assetConfigParams.reserve = address.encodeAddress(txnForEnc.apar.r); + assetConfigParams.reserve = new Address(txnForEnc.apar.r); } if (txnForEnc.apar.f) { - assetConfigParams.freeze = address.encodeAddress(txnForEnc.apar.f); + assetConfigParams.freeze = new Address(txnForEnc.apar.f); } if (txnForEnc.apar.c) { - assetConfigParams.clawback = address.encodeAddress(txnForEnc.apar.c); + assetConfigParams.clawback = new Address(txnForEnc.apar.c); } } params.assetConfigParams = assetConfigParams; @@ -847,24 +843,22 @@ export class Transaction { assetIndex: txnForEnc.xaid ?? 0, amount: txnForEnc.aamt ?? 0, receiver: txnForEnc.arcv - ? address.encodeAddress(txnForEnc.arcv) - : address.ALGORAND_ZERO_ADDRESS_STRING, + ? new Address(txnForEnc.arcv) + : Address.zeroAddress(), }; if (txnForEnc.aclose) { - assetTransferParams.closeRemainderTo = address.encodeAddress( - txnForEnc.aclose - ); + assetTransferParams.closeRemainderTo = new Address(txnForEnc.aclose); } if (txnForEnc.asnd) { - assetTransferParams.assetSender = address.encodeAddress(txnForEnc.asnd); + assetTransferParams.assetSender = new Address(txnForEnc.asnd); } params.assetTransferParams = assetTransferParams; } else if (params.type === TransactionType.afrz) { const assetFreezeParams: AssetFreezeTransactionParams = { assetIndex: txnForEnc.faid ?? 0, freezeTarget: txnForEnc.fadd - ? address.encodeAddress(txnForEnc.fadd) - : address.ALGORAND_ZERO_ADDRESS_STRING, + ? new Address(txnForEnc.fadd) + : Address.zeroAddress(), assetFrozen: txnForEnc.afrz ?? false, }; params.assetFreezeParams = assetFreezeParams; @@ -873,7 +867,7 @@ export class Transaction { appId: txnForEnc.apid ?? 0, onComplete: utils.ensureSafeUnsignedInteger(txnForEnc.apan ?? 0), appArgs: txnForEnc.apaa, - accounts: (txnForEnc.apat ?? []).map(address.encodeAddress), + accounts: (txnForEnc.apat ?? []).map((pk) => new Address(pk)), foreignAssets: txnForEnc.apas, foreignApps: txnForEnc.apfa, approvalProgram: txnForEnc.apap, @@ -967,16 +961,16 @@ export class Transaction { // add AuthAddr if signing with a different key than sender indicates const keypair = nacl.keyPairFromSecretKey(sk); const pubKeyFromSk = keypair.publicKey; - if ( - address.encodeAddress(pubKeyFromSk) !== - address.encodeAddress(this.sender.publicKey) - ) { + if (!utils.arrayEqual(pubKeyFromSk, this.sender.publicKey)) { sTxn.sgnr = pubKeyFromSk; } return new Uint8Array(encoding.encode(sTxn)); } - attachSignature(signerAddr: string, signature: Uint8Array): Uint8Array { + attachSignature( + signerAddr: string | Address, + signature: Uint8Array + ): Uint8Array { if (!nacl.isValidSignatureLength(signature.length)) { throw new Error('Invalid signature length'); } @@ -984,10 +978,10 @@ export class Transaction { sig: signature, txn: this.get_obj_for_encoding(), }; + const signerAddrObj = ensureAddress(signerAddr); // add AuthAddr if signing with a different key than From indicates - if (signerAddr !== address.encodeAddress(this.sender.publicKey)) { - const signerPublicKey = address.decodeAddress(signerAddr).publicKey; - sTxn.sgnr = signerPublicKey; + if (!this.sender.equals(signerAddrObj)) { + sTxn.sgnr = signerAddrObj.publicKey; } return new Uint8Array(encoding.encode(sTxn)); } diff --git a/src/types/account.ts b/src/types/account.ts index d8bd7063c..209f3968f 100644 --- a/src/types/account.ts +++ b/src/types/account.ts @@ -1,3 +1,5 @@ +import { Address } from '../encoding/address.js'; + /** * An Algorand account object. * @@ -7,7 +9,7 @@ export default interface Account { /** * Algorand address */ - addr: string; + addr: Address; /** * Secret key belonging to the Algorand address diff --git a/src/types/address.ts b/src/types/address.ts deleted file mode 100644 index 364e0307a..000000000 --- a/src/types/address.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Decoded Algorand address. Includes public key and checksum. - */ -export interface Address { - publicKey: Uint8Array; - checksum: Uint8Array; -} diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index 85be57a3a..000000000 --- a/src/types/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './transactions/index.js'; -export * from './multisig.js'; -export * from './address.js'; diff --git a/src/types/multisig.ts b/src/types/multisig.ts deleted file mode 100644 index c05e86b3b..000000000 --- a/src/types/multisig.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Required options for creating a multisignature - * - * Documentation available at: https://developer.algorand.org/docs/features/transactions/signatures/#multisignatures - */ -export interface MultisigMetadata { - /** - * Multisig version - */ - version: number; - - /** - * Multisig threshold value. Authorization requires a subset of signatures, - * equal to or greater than the threshold value. - */ - threshold: number; - - /** - * A list of Algorand addresses representing possible signers for this multisig. Order is important. - */ - addrs: string[]; -} diff --git a/src/types/transactions/base.ts b/src/types/transactions/base.ts index 81d1ab8f1..dfb4acd0a 100644 --- a/src/types/transactions/base.ts +++ b/src/types/transactions/base.ts @@ -1,4 +1,4 @@ -import { Address } from '../address.js'; +import { Address } from '../../encoding/address.js'; /** * Enum for application transaction types. @@ -99,6 +99,24 @@ export enum OnApplicationComplete { DeleteApplicationOC, } +/** + * Check if a value is a valid OnApplicationComplete value + * @param v - value to check + * @returns true if v is a valid OnApplicationComplete value + */ +export function isOnApplicationComplete( + v: unknown +): v is OnApplicationComplete { + return ( + v === OnApplicationComplete.NoOpOC || + v === OnApplicationComplete.OptInOC || + v === OnApplicationComplete.CloseOutOC || + v === OnApplicationComplete.ClearStateOC || + v === OnApplicationComplete.UpdateApplicationOC || + v === OnApplicationComplete.DeleteApplicationOC + ); +} + /** * Contains parameters relevant to the creation of a new transaction in a specific network at a specific time */ diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 57c8a389c..4b085b644 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -71,7 +71,7 @@ export function parseJSON(str: string, options?: JSONOptions) { /** * ArrayEqual takes two arrays and return true if equal, false otherwise */ -export function arrayEqual(a: ArrayLike, b: ArrayLike) { +export function arrayEqual(a: ArrayLike, b: ArrayLike): boolean { if (a.length !== b.length) { return false; } diff --git a/tests/2.Encoding.ts b/tests/2.Encoding.ts index 796aed6a9..08c7ade19 100644 --- a/tests/2.Encoding.ts +++ b/tests/2.Encoding.ts @@ -401,6 +401,31 @@ describe('encoding', () => { } }); + it('should parse string', () => { + const input = + '"IRK7XSCO7LPIBQKIUJXTQ5I7XBP3362ACPME5SOUUIJXN77T44RG4FZSLI"'; + + for (const intDecoding of [ + algosdk.IntDecoding.DEFAULT, + algosdk.IntDecoding.SAFE, + algosdk.IntDecoding.MIXED, + algosdk.IntDecoding.BIGINT, + ]) { + const actual = utils.parseJSON(input, { intDecoding }); + + assert.strictEqual(typeof actual, 'string'); + + const expected = + 'IRK7XSCO7LPIBQKIUJXTQ5I7XBP3362ACPME5SOUUIJXN77T44RG4FZSLI'; + + assert.strictEqual( + actual, + expected, + `Error when intDecoding = ${intDecoding}` + ); + } + }); + it('should parse empty object', () => { const input = '{}'; diff --git a/tests/3.Address.ts b/tests/3.Address.ts index 0ebc8ad4e..71f261eca 100644 --- a/tests/3.Address.ts +++ b/tests/3.Address.ts @@ -23,16 +23,24 @@ describe('address', () => { it('should verify a valid Algorand address', () => { const decodedAddress = algosdk.decodeAddress(correctCase); assert.deepStrictEqual(decodedAddress.publicKey, correctPublicKey); - assert.deepStrictEqual(decodedAddress.checksum, correctChecksum); + assert.deepStrictEqual(decodedAddress.checksum(), correctChecksum); }); it('should fail to verify a malformed Algorand address', () => { - assert.throws(() => { - algosdk.decodeAddress(malformedAddress1); - }, new Error(address.MALFORMED_ADDRESS_ERROR_MSG)); - assert.throws(() => { - algosdk.decodeAddress(malformedAddress2); - }, new Error(address.MALFORMED_ADDRESS_ERROR_MSG)); + assert.throws( + () => { + algosdk.decodeAddress(malformedAddress1); + }, + (err: Error) => + err.message.includes(address.MALFORMED_ADDRESS_ERROR_MSG) + ); + assert.throws( + () => { + algosdk.decodeAddress(malformedAddress2); + }, + (err: Error) => + err.message.includes(address.MALFORMED_ADDRESS_ERROR_MSG) + ); // Catch an exception possibly thrown by base32 decoding function assert.throws(() => { algosdk.decodeAddress(malformedAddress3); @@ -94,10 +102,10 @@ describe('address', () => { threshold: 2, addrs: [addr1, addr2, addr3], }; - const expectAddr = - 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM'; + const expectAddr = algosdk.Address.fromString( + 'UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM' + ); const actualAddr = algosdk.multisigAddress(params); - assert.ok(algosdk.isValidAddress(actualAddr)); assert.deepStrictEqual(actualAddr, expectAddr); }); }); @@ -105,10 +113,11 @@ describe('address', () => { describe('#getApplicationAddress', () => { it('should produce the correct address', () => { const appID = 77; - const expected = - 'PCYUFPA2ZTOYWTP43MX2MOX2OWAIAXUDNC2WFCXAGMRUZ3DYD6BWFDL5YM'; + const expected = algosdk.Address.fromString( + 'PCYUFPA2ZTOYWTP43MX2MOX2OWAIAXUDNC2WFCXAGMRUZ3DYD6BWFDL5YM' + ); const actual = algosdk.getApplicationAddress(appID); - assert.strictEqual(actual, expected); + assert.deepStrictEqual(actual, expected); }); }); diff --git a/tests/6.Multisig.ts b/tests/6.Multisig.ts index 1bd9ae24e..b30c271c5 100644 --- a/tests/6.Multisig.ts +++ b/tests/6.Multisig.ts @@ -24,21 +24,29 @@ const sampleMultisigAddr = algosdk.multisigAddress(sampleMultisigParams); describe('Sample Multisig Info', () => { it('is correct', () => { - assert.strictEqual( + assert.deepStrictEqual( sampleAccount1.addr, - 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA' + algosdk.Address.fromString( + 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA' + ) ); - assert.strictEqual( + assert.deepStrictEqual( sampleAccount2.addr, - 'BFRTECKTOOE7A5LHCF3TTEOH2A7BW46IYT2SX5VP6ANKEXHZYJY77SJTVM' + algosdk.Address.fromString( + 'BFRTECKTOOE7A5LHCF3TTEOH2A7BW46IYT2SX5VP6ANKEXHZYJY77SJTVM' + ) ); - assert.strictEqual( + assert.deepStrictEqual( sampleAccount3.addr, - '47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU' + algosdk.Address.fromString( + '47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU' + ) ); - assert.strictEqual( + assert.deepStrictEqual( sampleMultisigAddr, - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' + algosdk.Address.fromString( + 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' + ) ); }); }); @@ -272,7 +280,7 @@ describe('Multisig Functionality', () => { ) as ExpectedMultisigTxStructure; assert.deepStrictEqual( unsignedMultisigTxBlob.msig.subsig[0].pk, - algosdk.decodeAddress(sampleAccount1.addr).publicKey + sampleAccount1.addr.publicKey ); assert.strictEqual(unsignedMultisigTxBlob.msig.subsig[1].s, undefined); diff --git a/tests/7.AlgoSDK.ts b/tests/7.AlgoSDK.ts index 0cad7910c..af8ca3ad3 100644 --- a/tests/7.AlgoSDK.ts +++ b/tests/7.AlgoSDK.ts @@ -321,16 +321,13 @@ describe('Algosdk (AKA end to end)', () => { // Attach the signature to the transaction indirectly, and compare const signedWithSignature = txn.attachSignature(signer.addr, signature); - assert.deepEqual(signedWithSk, signedWithSignature); + assert.deepStrictEqual(signedWithSk, signedWithSignature); // Check that signer was set const decodedWithSigner = algosdk.decodeObj( signedWithSignature ) as algosdk.EncodedSignedTransaction; - assert.deepEqual( - decodedWithSigner.sgnr, - algosdk.decodeAddress(signer.addr).publicKey - ); + assert.deepStrictEqual(decodedWithSigner.sgnr, signer.addr.publicKey); }); it('should not attach signature with incorrect length', () => { @@ -460,7 +457,9 @@ describe('Algosdk (AKA end to end)', () => { const outAddr = algosdk.multisigAddress(params); assert.deepStrictEqual( outAddr, - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' + algosdk.Address.fromString( + 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' + ) ); }); }); @@ -806,7 +805,7 @@ describe('Algosdk (AKA end to end)', () => { const keys = algosdk.generateAccount(); const lsig = new algosdk.LogicSig(program); lsig.sign(keys.sk); - const verified = lsig.verify(algosdk.decodeAddress(keys.addr).publicKey); + const verified = lsig.verify(keys.addr.publicKey); assert.equal(verified, true); // check serialization @@ -833,7 +832,7 @@ describe('Algosdk (AKA end to end)', () => { ], }; const outAddr = algosdk.multisigAddress(params); - const msigPk = algosdk.decodeAddress(outAddr).publicKey; + const msigPk = outAddr.publicKey; const mn1 = 'auction inquiry lava second expand liberty glass involve ginger illness length room item discover ahead table doctor term tackle cement bonus profit right above catch'; const mn2 = @@ -937,10 +936,7 @@ describe('Algosdk (AKA end to end)', () => { it('should produce a verifiable signature', () => { const sig = algosdk.tealSign(sk, data, addr); - const parts = utils.concatArrays( - algosdk.decodeAddress(addr).publicKey, - data - ); + const parts = utils.concatArrays(addr.publicKey, data); const toBeVerified = utils.concatArrays( new TextEncoder().encode('ProgData'), parts diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts index 78441b6d6..029ae3dbd 100644 --- a/tests/8.LogicSig.ts +++ b/tests/8.LogicSig.ts @@ -13,11 +13,11 @@ const sampleAccount3 = algosdk.mnemonicToSecretKey( ); // Multisig Golden Params -const sampleMultisigParams: algosdk.MultisigMetadata = { +const sampleMultisigParams = { version: 1, threshold: 2, addrs: [sampleAccount1.addr, sampleAccount2.addr, sampleAccount3.addr], -}; +} satisfies algosdk.MultisigMetadata; const sampleMultisigAddr = algosdk.multisigAddress(sampleMultisigParams); @@ -25,15 +25,16 @@ describe('LogicSig', () => { describe('makeLogicSig', () => { it('should work on valid program', () => { const program = Uint8Array.from([1, 32, 1, 1, 34]); - const programHash = - '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; - const pk = algosdk.decodeAddress(programHash).publicKey; + const programHash = algosdk.Address.fromString( + '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY' + ); + const pk = programHash.publicKey; let lsig = new algosdk.LogicSig(program); assert.strictEqual(lsig.logic, program); assert.strictEqual(lsig.args, undefined); assert.strictEqual(lsig.sig, undefined); assert.strictEqual(lsig.msig, undefined); - assert.strictEqual(lsig.address(), programHash); + assert.deepStrictEqual(lsig.address(), programHash); let verified = lsig.verify(pk); assert.strictEqual(verified, true); @@ -75,7 +76,8 @@ describe('LogicSig', () => { const lsig = new algosdk.LogicSig(program); const address = lsig.address(); - assert.deepStrictEqual(address, programHash); + assert.strictEqual(address.toString(), programHash); + assert.ok(address.equals(algosdk.Address.fromString(programHash))); }); }); }); @@ -138,9 +140,7 @@ describe('LogicSigAccount', () => { 'SRO4BdGefywQgPYzfhhUp87q7hDdvRNlhL+Tt18wYxWRyiMM7e8j0XQbUp2w/+83VNZG9LVh/Iu8LXtOY1y9Ag==' ) ); - const expectedSigKey = algosdk.decodeAddress( - sampleAccount1.addr - ).publicKey; + const expectedSigKey = sampleAccount1.addr.publicKey; assert.deepStrictEqual(lsigAccount.lsig.logic, program); assert.deepStrictEqual(lsigAccount.lsig.args, args); @@ -179,7 +179,7 @@ describe('LogicSigAccount', () => { v: sampleMultisigParams.version, thr: sampleMultisigParams.threshold, subsig: sampleMultisigParams.addrs.map((addr) => ({ - pk: algosdk.decodeAddress(addr).publicKey, + pk: addr.publicKey, })), }; expectedMsig.subsig[0].s = expectedSig; @@ -229,7 +229,7 @@ describe('LogicSigAccount', () => { v: sampleMultisigParams.version, thr: sampleMultisigParams.threshold, subsig: sampleMultisigParams.addrs.map((addr) => ({ - pk: algosdk.decodeAddress(addr).publicKey, + pk: addr.publicKey, })), }; expectedMsig.subsig[0].s = expectedSig1; @@ -369,10 +369,11 @@ describe('LogicSigAccount', () => { const addr = lsigAccount.address(); - const expectedAddr = - '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY'; + const expectedAddr = algosdk.Address.fromString( + '6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY' + ); - assert.strictEqual(addr, expectedAddr); + assert.deepStrictEqual(addr, expectedAddr); }); it('should be correct for single sig', () => { @@ -385,10 +386,11 @@ describe('LogicSigAccount', () => { const addr = lsigAccount.address(); - const expectedAddr = - 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA'; + const expectedAddr = algosdk.Address.fromString( + 'DN7MBMCL5JQ3PFUQS7TMX5AH4EEKOBJVDUF4TCV6WERATKFLQF4MQUPZTA' + ); - assert.strictEqual(addr, expectedAddr); + assert.deepStrictEqual(addr, expectedAddr); }); it('should be correct for multisig', () => { @@ -401,10 +403,11 @@ describe('LogicSigAccount', () => { const addr = lsigAccount.address(); - const expectedAddr = - 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM'; + const expectedAddr = algosdk.Address.fromString( + 'RWJLJCMQAFZ2ATP2INM2GZTKNL6OULCCUBO5TQPXH3V2KR4AG7U5UA5JNM' + ); - assert.strictEqual(addr, expectedAddr); + assert.deepStrictEqual(addr, expectedAddr); }); }); }); @@ -418,7 +421,7 @@ describe('signLogicSigTransaction', () => { function testSign( lsigObject: algosdk.LogicSig | algosdk.LogicSigAccount, - sender: string, + sender: string | algosdk.Address, expected: { txID: string; blob: Uint8Array } ) { const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index fa14c7498..5416d608b 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -376,7 +376,8 @@ module.exports = function getSteps(options) { Then( 'the multisig address should equal the golden {string}', function (golden) { - assert.deepStrictEqual(algosdk.multisigAddress(this.msig), golden); + const goldenAddr = algosdk.Address.fromString(golden); + assert.deepStrictEqual(algosdk.multisigAddress(this.msig), goldenAddr); } ); @@ -393,7 +394,7 @@ module.exports = function getSteps(options) { await this.kcl.deleteMultisig( this.handle, this.wallet_pswd, - algosdk.multisigAddress(this.msig) + algosdk.multisigAddress(this.msig).toString() ); const s = algosdk.decodeObj(this.stx); const m = algosdk.encodeObj(s.msig); @@ -453,7 +454,7 @@ module.exports = function getSteps(options) { When('I generate a key', function () { const result = algosdk.generateAccount(); - this.pk = result.addr; + this.pk = result.addr.toString(); this.sk = result.sk; }); @@ -557,12 +558,10 @@ module.exports = function getSteps(options) { }); Then('the multisig should be in the wallet', async function () { - let keys = await this.kcl.listMultisig(this.handle); - keys = keys.addresses; - assert.deepStrictEqual( - true, - keys.indexOf(algosdk.multisigAddress(this.msig)) >= 0 - ); + const resp = await this.kcl.listMultisig(this.handle); + const keys = resp.addresses; + const addr = algosdk.multisigAddress(this.msig).toString(); + assert.ok(keys.indexOf(addr) >= 0, `Can't find address ${addr} in ${keys}`); return keys; }); @@ -2876,7 +2875,7 @@ module.exports = function getSteps(options) { 'a signing account with address {string} and mnemonic {string}', function (address, mnemonic) { this.signingAccount = algosdk.mnemonicToSecretKey(mnemonic); - if (this.signingAccount.addr !== address) { + if (this.signingAccount.addr.toString() !== address) { throw new Error( `Address does not match mnemonic: ${this.signingAccount.addr} !== ${address}` ); @@ -2916,11 +2915,9 @@ module.exports = function getSteps(options) { new TextEncoder().encode('appID'), algosdk.encodeUint64(appID) ); - const expected = algosdk.encodeAddress( - makeUint8Array(genericHash(toSign)) - ); + const expected = new algosdk.Address(makeUint8Array(genericHash(toSign))); const actual = algosdk.getApplicationAddress(appID); - assert.strictEqual(expected, actual); + assert.deepStrictEqual(expected, actual); } ); From 63990dcc0fdd407e375697b7c127662c4298f5d5 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 26 Jan 2024 16:11:43 -0500 Subject: [PATCH 04/17] export multisig step fix --- tests/cucumber/steps/steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 5416d608b..a5ec94351 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -582,7 +582,7 @@ module.exports = function getSteps(options) { When('I export the multisig', async function () { this.msigExp = await this.kcl.exportMultisig( this.handle, - algosdk.multisigAddress(this.msig) + algosdk.multisigAddress(this.msig).toString() ); return this.msigExp; }); From 790e95f42c519a123f7eaf4082a71186f6409c0b Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 26 Jan 2024 16:31:20 -0500 Subject: [PATCH 05/17] other multisig step fix --- tests/cucumber/steps/steps.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index a5ec94351..1e1574dca 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -437,7 +437,7 @@ module.exports = function getSteps(options) { Then('the key should be in the wallet', async function () { let keys = await this.kcl.listKeys(this.handle); keys = keys.addresses; - assert.deepStrictEqual(true, keys.indexOf(this.pk) >= 0); + assert.ok(keys.indexOf(this.pk) >= 0); return keys; }); @@ -448,7 +448,7 @@ module.exports = function getSteps(options) { Then('the key should not be in the wallet', async function () { let keys = await this.kcl.listKeys(this.handle); keys = keys.addresses; - assert.deepStrictEqual(false, keys.indexOf(this.pk) >= 0); + assert.ok(keys.indexOf(this.pk) < 0); return keys; }); @@ -572,10 +572,7 @@ module.exports = function getSteps(options) { } keys = keys.addresses; - assert.deepStrictEqual( - false, - keys.indexOf(algosdk.multisigAddress(this.msig)) >= 0 - ); + assert.ok(keys.indexOf(algosdk.multisigAddress(this.msig).toString()) < 0); return keys; }); @@ -591,7 +588,7 @@ module.exports = function getSteps(options) { return this.kcl.deleteMultisig( this.handle, this.wallet_pswd, - algosdk.multisigAddress(this.msig) + algosdk.multisigAddress(this.msig).toString() ); }); From e630d7b9dff68d6223edddd4449c820b8aae0f88 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 6 Feb 2024 13:20:48 -0500 Subject: [PATCH 06/17] Small tweaks, and add migrate guide --- examples/atomics.ts | 3 +- examples/codec.ts | 3 +- src/makeTxn.ts | 15 +- src/transaction.ts | 17 +-- tests/5.Transaction.ts | 26 ++-- tests/cucumber/steps/steps.js | 2 +- v2_TO_v3_MIGRATION_GUIDE.md | 266 ++++++++++++++++++++++++++++++++++ 7 files changed, 300 insertions(+), 32 deletions(-) create mode 100644 v2_TO_v3_MIGRATION_GUIDE.md diff --git a/examples/atomics.ts b/examples/atomics.ts index a86139e0c..0c0b1e40a 100644 --- a/examples/atomics.ts +++ b/examples/atomics.ts @@ -51,8 +51,7 @@ async function main() { // example: ATOMIC_GROUP_SEND // example: CONST_MIN_FEE - const minFee = algosdk.ALGORAND_MIN_TX_FEE; - console.log(minFee); + // This SDK does not expose a constant for the minimum fee // example: CONST_MIN_FEE // example: TRANSACTION_FEE_OVERRIDE diff --git a/examples/codec.ts b/examples/codec.ts index affa9e6b0..e92f7f4d9 100644 --- a/examples/codec.ts +++ b/examples/codec.ts @@ -14,8 +14,7 @@ async function main() { // example: CODEC_ADDRESS const address = '4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4'; - const pk = algosdk.decodeAddress(address); - const addr = algosdk.encodeAddress(pk.publicKey); + const addr = algosdk.Address.fromString(address); console.log(address, addr); // example: CODEC_ADDRESS diff --git a/src/makeTxn.ts b/src/makeTxn.ts index 4102312c8..b8204bb19 100644 --- a/src/makeTxn.ts +++ b/src/makeTxn.ts @@ -227,13 +227,13 @@ export interface AssetModificationTransactionParams { clawback?: string | Address; /** - * This is a safety flag to prevent unintentionally removing a role from an asset. If false or - * undefined, an error will be thrown if any of assetManager, assetReserve, assetFreeze, or + * This is a safety flag to prevent unintentionally removing a role from an asset. If undefined or + * true, an error will be thrown if any of assetManager, assetReserve, assetFreeze, or * assetClawback are empty. * - * Set this to true to allow removing roles by leaving the corresponding address empty. + * Set this to false to allow removing roles by leaving the corresponding address empty. */ - allowRoleRemoval?: boolean; + strictEmptyAddressChecking?: boolean; } /** @@ -252,7 +252,7 @@ export function makeAssetConfigTxnWithSuggestedParamsFromObject({ reserve, freeze, clawback, - allowRoleRemoval, + strictEmptyAddressChecking, note, lease, rekeyTo, @@ -261,12 +261,13 @@ export function makeAssetConfigTxnWithSuggestedParamsFromObject({ if (!assetIndex) { throw Error('assetIndex must be provided'); } + const strictChecking = strictEmptyAddressChecking ?? true; if ( - !allowRoleRemoval && + strictChecking && (manager == null || reserve == null || freeze == null || clawback == null) ) { throw Error( - 'allowRoleRemoval is not enabled, but an address is empty. If this is intentional, set allowRoleRemoval to true.' + 'strictEmptyAddressChecking is enabled, but an address is empty. If this is intentional, set strictEmptyAddressChecking to false.' ); } return makeBaseAssetConfigTxn({ diff --git a/src/transaction.ts b/src/transaction.ts index 82110aefc..8f1d46509 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -33,7 +33,6 @@ import { import * as utils from './utils/utils.js'; const ALGORAND_TRANSACTION_LENGTH = 52; -export const ALGORAND_MIN_TX_FEE = 1000; // version v5 const ALGORAND_TRANSACTION_LEASE_LENGTH = 32; const NUM_ADDL_BYTES_AFTER_SIGNING = 75; // NUM_ADDL_BYTES_AFTER_SIGNING is the number of bytes added to a txn after signing it const ASSET_METADATA_HASH_LENGTH = 32; @@ -165,7 +164,7 @@ function optionalFixedLengthByteArray( return bytes; } -interface TransactionBoxReference { +export interface TransactionBoxReference { readonly appIndex: bigint; readonly name: Uint8Array; } @@ -183,13 +182,13 @@ function ensureBoxReference(input: unknown): TransactionBoxReference { const TX_TAG = new TextEncoder().encode('TX'); -interface PaymentTransactionFields { +export interface PaymentTransactionFields { readonly receiver: Address; readonly amount: bigint; readonly closeRemainderTo?: Address; } -interface KeyRegistrationTransactionFields { +export interface KeyRegistrationTransactionFields { readonly voteKey?: Uint8Array; readonly selectionKey?: Uint8Array; readonly stateProofKey?: Uint8Array; @@ -199,7 +198,7 @@ interface KeyRegistrationTransactionFields { readonly nonParticipation: boolean; } -interface AssetConfigTransactionFields { +export interface AssetConfigTransactionFields { readonly assetId: bigint; readonly assetTotal: bigint; readonly assetDecimals: number; @@ -214,7 +213,7 @@ interface AssetConfigTransactionFields { readonly assetMetadataHash?: Uint8Array; } -interface AssetTransferTransactionFields { +export interface AssetTransferTransactionFields { readonly assetId: bigint; readonly amount: bigint; readonly sender?: Address; @@ -222,13 +221,13 @@ interface AssetTransferTransactionFields { readonly closeRemainderTo?: Address; } -interface AssetFreezeTransactionFields { +export interface AssetFreezeTransactionFields { readonly assetId: bigint; readonly freezeAccount: Address; readonly assetFrozen: boolean; } -interface ApplicationTransactionFields { +export interface ApplicationTransactionFields { readonly appId: bigint; readonly appOnComplete: OnApplicationComplete; readonly appLocalInts: number; @@ -245,7 +244,7 @@ interface ApplicationTransactionFields { readonly boxes: ReadonlyArray; } -interface StateProofTransactionFields { +export interface StateProofTransactionFields { readonly stateProofType: number; readonly stateProof: Uint8Array; readonly stateProofMessage: Uint8Array; diff --git a/tests/5.Transaction.ts b/tests/5.Transaction.ts index 74657b77d..a47421bfc 100644 --- a/tests/5.Transaction.ts +++ b/tests/5.Transaction.ts @@ -1790,16 +1790,20 @@ describe('Sign', () => { firstValid: 51, lastValid: 61, }; - assert.throws(() => - algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ - sender: addr, - assetIndex, - manager: addr, - reserve: addr, - freeze: undefined, - clawback: undefined, - suggestedParams, - }) + assert.throws( + () => + algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ + sender: addr, + assetIndex, + manager: addr, + reserve: addr, + freeze: undefined, + clawback: undefined, + suggestedParams, + }), + new Error( + 'strictEmptyAddressChecking is enabled, but an address is empty. If this is intentional, set strictEmptyAddressChecking to false.' + ) ); // does not throw when flag enabled @@ -1811,7 +1815,7 @@ describe('Sign', () => { freeze: undefined, clawback: undefined, suggestedParams, - allowRoleRemoval: true, + strictEmptyAddressChecking: false, }); }); diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 1e1574dca..75d9d08fe 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -1172,7 +1172,7 @@ module.exports = function getSteps(options) { clawback, assetIndex: parseInt(this.assetTestFixture.index), suggestedParams: this.params, - allowRoleRemoval: true, + strictEmptyAddressChecking: false, }); // update vars used by other helpers this.assetTestFixture.expectedParams.reserve = undefined; diff --git a/v2_TO_v3_MIGRATION_GUIDE.md b/v2_TO_v3_MIGRATION_GUIDE.md new file mode 100644 index 000000000..8c8d8bd2f --- /dev/null +++ b/v2_TO_v3_MIGRATION_GUIDE.md @@ -0,0 +1,266 @@ +# Migration guide from v2 to v3 + +In 2024 we will release version 3 of the JavaScript Algorand SDK. This version includes a number of changes and improvements over the previous version. This guide is intended to help you migrate your existing code from version 2 to version 3. + +## API Changes + +### Addresses + +The v2 `Address` interface has been upgraded to a class in v3. + +In v2, you would use the `decodeAddress` and `encodeAddress` functions to convert between a 32-byte public key and an address string. These functions still exist in v3, but `decodeAddress` now returns an instance of the `Address` class. + +For new code, it's probably more natural to use the `Address` class directly, rather than the `decodeAddress` and `encodeAddress` functions. The static method `Address.fromString` can be used to create an `Address` instance from a string, and the `toString` method can be used to convert an `Address` instance to a string. + +```typescript +// v2 +const address = algosdk.decodeAddress( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' +); +console.log('Address 32-byte public key component:', address.publicKey); +console.log('Address checksum:', address.checksum); +console.log('Address string:', algosdk.encodeAddress(address.publicKey)); + +// v3 +const address = algosdk.Address.fromString( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' +); +console.log('Address 32-byte public key component:', address.publicKey); +console.log('Address checksum:', address.checksum()); +console.log('Address string:', address.toString()); +``` + +In most places where you passed addresses as strings in v2, you can now choose to pass either a string or an instance of the `Address` class in v3. Internally, the SDK will convert the string to an `Address` instance if necessary. + +### Transactions + +In v3, the `Transaction` class underwent a significant internal refactor. For the most part, this class will behave the same as it did in v2, but there are a few changes to be aware of. + +#### Construction + +First, we will focus on the changes to the `make*` functions, which remain the recommended way to construct a `Transaction` instance in v3. + +Since v1.8.1 was released in 2020, this library has supported two general ways of creating transactions: + +```typescript +// Warning: This is v2 code. Code for v3 is shown later in this section. +const suggestedParams = await client.getTransactionParams().do(); + +// Method 1: Using the "standard" version of the maker function for a specific transaction type. +// This standard version passes parameters as individual arguments. +const txn = algosdk.makePaymentTxnWithSuggestedParams( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI', + '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + 1000, + undefined, + Uint8Array.from([1, 2, 3]), + suggestedParams, + undefined +); + +// Method 2: Using the "FromObject" variant of the function, which takes a single parameter object. +const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + from: 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI', + to: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 1000, + note: Uint8Array.from([1, 2, 3]), + suggestedParams, +}); +``` + +We believe the second method, using the `FromObject` variant which takes a parameter object, is significantly more readable and maintainable, so we have removed the other variants in v3. + +Specifically, these functions were removed in v3: + +| v2 Function | v3 Replacement | +| ------------------------------------------- | ----------------------------------------------------- | +| `makePaymentTxnWithSuggestedParams` | `makePaymentTxnWithSuggestedParamsFromObject` | +| `makeKeyRegistrationTxnWithSuggestedParams` | `makeKeyRegistrationTxnWithSuggestedParamsFromObject` | +| `makeAssetCreateTxnWithSuggestedParams` | `makeAssetCreateTxnWithSuggestedParamsFromObject` | +| `makeAssetConfigTxnWithSuggestedParams` | `makeAssetConfigTxnWithSuggestedParamsFromObject` | +| `makeAssetDestroyTxnWithSuggestedParams` | `makeAssetDestroyTxnWithSuggestedParamsFromObject` | +| `makeAssetFreezeTxnWithSuggestedParams` | `makeAssetFreezeTxnWithSuggestedParamsFromObject` | +| `makeAssetTransferTxnWithSuggestedParams` | `makeAssetTransferTxnWithSuggestedParamsFromObject` | +| `makeApplicationCreateTxn` | `makeApplicationCreateTxnFromObject` | +| `makeApplicationUpdateTxn` | `makeApplicationUpdateTxnFromObject` | +| `makeApplicationDeleteTxn` | `makeApplicationDeleteTxnFromObject` | +| `makeApplicationOptInTxn` | `makeApplicationOptInTxnFromObject` | +| `makeApplicationCloseOutTxn` | `makeApplicationCloseOutTxnFromObject` | +| `makeApplicationClearStateTxn` | `makeApplicationClearStateTxnFromObject` | +| `makeApplicationNoOpTxn` | `makeApplicationNoOpTxnFromObject` | + +In addition to removing these functions, we have also changed some inputs to the remaining functions. In v2, the argument types of the `make*FromObject` functions were a rather complicated union of many derived types. In the interest of simplicity and ease of use, we have greatly simplified these types in v3. Specifically, these types and interfaces were removed in v2: + +- `PaymentTxn` +- `KeyRegistrationTxn` +- `AssetCreateTxn` +- `AssetConfigTxn` +- `AssetDestroyTxn` +- `AssetFreezeTxn` +- `AssetTransferTxn` +- `AppCreateTxn` +- `AppUpdateTxn` +- `AppDeleteTxn` +- `AppOptInTxn` +- `AppCloseOutTxn` +- `AppClearStateTxn` +- `AppNoOpTxn` +- `StateProofTxn` +- `AnyTransaction` +- `MustHaveSuggestedParams` +- `MustHaveSuggestedParamsInline` + +To replace them, these interfaces are introduced in v3: + +- `CommonTransactionParams`: Contains parameters common to every transaction type +- `PaymentTransactionParams`: Contains payment transaction parameters +- `KeyRegistrationTransactionParams`: Contains key registration transaction parameters +- `AssetConfigurationTransactionParams`: Contains asset configuration transaction parameters +- `AssetTransferTransactionParams`: Contains asset transfer transaction parameters +- `AssetFreezeTransactionParams`: Contains asset freeze transaction parameters +- `ApplicationCallTransactionParams`: Contains application call transaction parameters +- `TransactionParams`: Contains all necessary parameters to construct a transaction of any type + +Every transaction type has a base `make*` function whose single parameter object is a union of its specific transaction parameter type outlined above, and `CommonTransactionParams`. For example, the `makePaymentTxnWithSuggestedParamsFromObject` function takes a parameter object of type `PaymentTransactionParams & CommonTransactionParams`. + +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 | +| ---------------- | ------------------- | ---------------------- | ------------------- | ------------------- | ------------------------------------------ | +| All | `from` | `string` | `sender` | `string \| Address` | | +| Payment | `to` | `string` | `receiver` | `string \| Address` | | +| Key Registration | `voteKey` | `string \| Uint8Array` | `voteKey` | `Uint8Array` | Base64 encoded value is no longer accepted | +| " | `selectionKey` | `string \| Uint8Array` | `selectionKey` | `Uint8Array` | Base64 encoded value is no longer accepted | +| " | `stateProofKey` | `string \| Uint8Array` | `stateProofKey` | `Uint8Array` | Base64 encoded value is no longer accepted | +| Asset Config | `assetMetadataHash` | `string \| Uint8Array` | `assetMetadataHash` | `Uint8Array` | Base64 encoded value is no longer accepted | +| Asset Freeze | `freezeState` | `boolean` | `assetFrozen` | `boolean` | | +| Asset Transfer | `to` | `string` | `receiver` | `string \| Address` | | +| " | `revocationTarget` | `string` | `assetSender` | `string \| Address` | | +| Application Call | `appIndex` | `number` | `appId` | `number \| bigint` | | + +Given these changes, the earlier v2 example would be equivalent to the following in v3: + +```typescript +const suggestedParams = await client.getTransactionParams().do(); + +const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ + sender: 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI', + receiver: '7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q', + amount: 1000, + note: Uint8Array.from([1, 2, 3]), + suggestedParams, +}); +``` + +Similar to the input types from the `make*` functions, the input type to the `Transaction` constructor has changed. This type is now the `TransactionParams` interface, which contains each of the above type-specific transaction parameter types as optional fields. For example, it has a field called `paymentParams` with the type `PaymentTransactionParams`. The same changes to field names and types that affected the `make*` functions also affect the `Transaction` constructor. + +#### Fields + +The following table shows the correspondence between v2 and v3 fields in the `Transaction` class. The most significant change is that fields specific to a transaction type are now nested under an object corresponding to that transaction type. For example, the `amount` field is now `payment.amount` or `assetTransfer.amount`, depending on the transaction type. The object for that transaction type will only exist if the transaction is that type, otherwise it will be undefined. + +| v2 Field | v2 Field Type | v3 Field | v3 Field Type | Notes | +| ----------------------- | ----------------------- | ------------------------------------- | --------------------------- | ----------------------------------------- | +| `type` | `TransactionType` | `type` | `TransactionType` | | +| `fee` | `number` | `fee` | `bigint` | | +| `flatFee` | `boolean` | | | No longer exists | +| `firstRound` | `number` | `firstValid` | `bigint` | | +| `lastRound` | `number` | `lastValid` | `bigint` | | +| `genesisID` | `string` | `genesisID` | `string` | | +| `genesisHash` | `Buffer` | `genesisHash` | `Uint8Array` | | +| `note` | `Uint8Array` | `note` | `Uint8Array` | | +| `reKeyTo` | `Address` | `rekeyTo` | `Address` | | +| `lease` | `Uint8Array` | `lease` | `Uint8Array` | | +| `group` | `Buffer` | `group` | `Uint8Array` | | +| `from` | `Address` | `sender` | `Address` | | +| `to` | `Address` | `payment.receiver` | `Address` | If the transaction type is payment | +| " | " | `assetTransfer.receiver` | `Address` | If the transaction type is asset transfer | +| `amount` | `number \| bigint` | `payment.amount` | `bigint` | If the transaction type is payment | +| " | " | `assetTransfer.amount` | `bigint` | If the transaction type is asset transfer | +| `closeRemainderTo` | `Address` | `payment.closeRemainderTo` | `Address` | If the transaction type is payment | +| " | " | `assetTransfer.closeRemainderTo` | `Address` | If the transaction type is asset transfer | +| `voteKey` | `Buffer` | `keyreg.voteKey` | `Uint8Array` | | +| `selectionKey` | `Buffer` | `keyreg.selectionKey` | `Uint8Array` | | +| `stateProofKey` | `Buffer` | `keyreg.stateProofKey` | `Uint8Array` | | +| `voteFirst` | `number` | `keyreg.voteFirst` | `bigint` | | +| `voteLast` | `number` | `keyreg.voteLast` | `bigint` | | +| `voteKeyDilution` | `number` | `keyreg.voteKeyDilution` | `bigint` | | +| `nonParticipation` | `boolean` | `keyreg.nonParticipation` | `boolean` | | +| `assetIndex` | `number` | `assetConfig.assetId` | `bigint` | If the transaction type is asset config | +| " | " | `assetTransfer.assetId` | `bigint` | If the transaction type is asset transfer | +| " | " | `assetFreeze.assetId` | `bigint` | If the transaction type is asset freeze | +| `assetTotal` | `number \| bigint` | `assetConfig.assetTotal` | `bigint` | | +| `assetDecimals` | `number` | `assetConfig.assetDecimals` | `number` | | +| `assetDefaultFrozen` | `boolean` | `assetConfig.assetDefaultFrozen` | `boolean` | | +| `assetManager` | `Address` | `assetConfig.assetManager` | `Address` | | +| `assetReserve` | `Address` | `assetConfig.assetReserve` | `Address` | | +| `assetFreeze` | `Address` | `assetConfig.assetFreeze` | `Address` | | +| `assetClawback` | `Address` | `assetConfig.assetClawback` | `Address` | | +| `assetUnitName` | `string` | `assetConfig.assetUnitName` | `string` | | +| `assetName` | `string` | `assetConfig.assetName` | `string` | | +| `assetURL` | `string` | `assetConfig.assetURL` | `string` | | +| `assetMetadataHash` | `Uint8Array` | `assetConfig.assetMetadataHash` | `Uint8Array` | | +| `freezeAccount` | `Address` | `assetFreeze.freezeAccount` | `Address` | | +| `freezeState` | `boolean` | `assetFreeze.assetFrozen` | `boolean` | | +| `assetRevocationTarget` | `Address` | `assetTransfer.sender` | `Address` | | +| `appIndex` | `number` | `applicationCall.appId` | `bigint` | | +| `appOnComplete` | `OnApplicationComplete` | `applicationCall.appOnComplete` | `OnApplicationComplete` | | +| `appLocalInts` | `number` | `applicationCall.appLocalInts` | `number` | | +| `appLocalByteSlices` | `number` | `applicationCall.appLocalByteSlices` | `number` | | +| `appGlobalInts` | `number` | `applicationCall.appGlobalInts` | `number` | | +| `appGlobalByteSlices` | `number` | `applicationCall.appGlobalByteSlices` | `number` | | +| `extraPages` | `number` | `applicationCall.extraPages` | `number` | | +| `appApprovalProgram` | `Uint8Array` | `applicationCall.appApprovalProgram` | `Uint8Array` | | +| `appClearProgram` | `Uint8Array` | `applicationCall.appClearProgram` | `Uint8Array` | | +| `appArgs` | `Uint8Array[]` | `applicationCall.appArgs` | `Uint8Array[]` | | +| `appAccounts` | `Address[]` | `applicationCall.appAccounts` | `Address[]` | | +| `appForeignApps` | `number[]` | `applicationCall.appForeignApps` | `bigint[]` | | +| `appForeignAssets` | `number[]` | `applicationCall.appForeignAssets` | `bigint[]` | | +| `boxes` | `BoxReference[]` | `applicationCall.boxes` | `TransactionBoxReference[]` | | +| `stateProofType` | `number \| bigint` | `stateProof.stateProofType` | `number` | | +| `stateProof` | `Uint8Array` | `stateProof.stateProof` | `Uint8Array` | | +| `stateProofMessage` | `Uint8Array` | `stateProof.stateProofMessage` | `Uint8Array` | | +| `name` | `string` | | | No longer exists | +| `tag` | `Buffer` | | | No longer exists | + +Note that for v2, the `Address` type indicates the v2 `Address` interface, while for v3, the `Address` type indicates the v3 `Address` class. See the [Addresses](#addresses) section for more information. + +#### Methods + +The following methods have been removed from the public `Transaction` class API in v3: + +- `_getDictForDisplay` +- `toString` +- `prettyPrint` +- `addLease` +- `addRekey` +- `estimateSize` + +We believe these methods were not useful to most users, and we have removed them to simplify the API. Specifically, the `addLease` and `addRekey` options are no longer necessary, since it's possible to include these fields in the transaction parameter object when creating the transaction. + +#### Passing Transactions to Functions + +In v2, it was possible to pass a raw object with the same properties that the `Transaction` constructor would accept to many functions that expected a `Transaction` instance. This was made possible with the `TransactionLike` type. + +That type has been removed and this behavior is no longer possible in v3. Instead, you must explicitly construct `Transaction` instances, preferably using the `make*` functions. + +#### Multisig Transaction Class + +The `MultisigTransaction` class have been removed, as it was unnecessary. + +#### Transaction Group Class + +The `TxGroup` class has been removed, as it was unnecessary. + +### Suggested Transaction Parameters + +The `SuggestedParams` interface plays an important role in creating new transactions. In v2, there were `SuggestedParams` and `SuggestedParamsWithMinFee` interfaces, but the `minFee` field was never used by the transaction construction functions; instead, they used the hardcoded constant `ALGORAND_MIN_TX_FEE`, which was 1000 microAlgos. + +In v3, we've removed the need for `SuggestedParamsWithMinFee` and added the `minFee` field to the `SuggestedParams` interface. This field is now required, and the `ALGORAND_MIN_TX_FEE` constant has been removed. + +This allows the SDK to use the min fee information provided by the node, which has the potential to change over time or for different networks. + +If you manually constructed `SuggestedParams` objects in v2, you will need to add a `minFee` field to those objects in v3. We expect most users to not be affected by this, since if you use Algod to get suggested parameters, it will include the `minFee` field. + +### Auction Bids + +Auction bids have been removed from the library in v3, as they were only relevant to the launch of MainNet. From 034a7c4d59978e7a356ef5876b5e6cdfdf5b4765 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 6 Feb 2024 13:41:02 -0500 Subject: [PATCH 07/17] missed change from before --- examples/asa.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/asa.ts b/examples/asa.ts index 9eaa64966..93855ac45 100644 --- a/examples/asa.ts +++ b/examples/asa.ts @@ -65,7 +65,7 @@ async function main() { suggestedParams, assetIndex, // don't throw error if freeze, clawback, or manager are empty - allowRoleRemoval: true, + strictEmptyAddressChecking: false, }); const signedConfigTxn = configTxn.signTxn(creator.privateKey); From 11331aaf3695af03066444de2c5cc0f48e255670 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 6 Feb 2024 17:21:00 -0500 Subject: [PATCH 08/17] Remove unnecessary renames --- examples/asa.ts | 8 +- src/composer.ts | 2 +- src/dryrun.ts | 30 ++--- src/makeTxn.ts | 64 ++++----- src/transaction.ts | 214 +++++++++++++++--------------- src/types/transactions/base.ts | 8 +- src/types/transactions/encoded.ts | 4 +- tests/10.ABI.ts | 14 +- tests/5.Transaction.ts | 30 ++--- tests/7.AlgoSDK.ts | 4 +- tests/cucumber/steps/steps.js | 20 +-- v2_TO_v3_MIGRATION_GUIDE.md | 51 ++++--- 12 files changed, 224 insertions(+), 225 deletions(-) diff --git a/examples/asa.ts b/examples/asa.ts index 93855ac45..62dee7c88 100644 --- a/examples/asa.ts +++ b/examples/asa.ts @@ -114,8 +114,8 @@ async function main() { sender: manager.addr, suggestedParams, assetIndex, - // assetFrozen: false would unfreeze the account's asset holding - assetFrozen: true, + // freezeState: false would unfreeze the account's asset holding + freezeState: true, // freezeTarget is the account that is being frozen or unfrozen freezeTarget: receiver.addr, }); @@ -130,8 +130,8 @@ async function main() { { sender: manager.addr, receiver: creator.addr, - // assetSender is the account that is being clawed back from - assetSender: receiver.addr, + // revocationTarget is the account that is being clawed back from + revocationTarget: receiver.addr, suggestedParams, assetIndex, amount: 1, diff --git a/src/composer.ts b/src/composer.ts index a77f6d4e2..854988d9f 100644 --- a/src/composer.ts +++ b/src/composer.ts @@ -478,7 +478,7 @@ export class AtomicTransactionComposer { const appCall = { txn: makeApplicationCallTxnFromObject({ sender, - appId: appID, + appIndex: appID, appArgs: appArgsEncoded, accounts: foreignAccounts, foreignApps, diff --git a/src/dryrun.ts b/src/dryrun.ts index bbf97c381..18ee02253 100644 --- a/src/dryrun.ts +++ b/src/dryrun.ts @@ -55,43 +55,41 @@ export async function createDryrun({ if (t.txn.type === TransactionType.appl) { accts.push(t.txn.sender.toString()); - accts.push( - ...t.txn.applicationCall!.appAccounts.map((a) => a.toString()) - ); + accts.push(...t.txn.applicationCall!.accounts.map((a) => a.toString())); - apps.push(...t.txn.applicationCall!.appForeignApps); + apps.push(...t.txn.applicationCall!.foreignApps); accts.push( ...t.txn - .applicationCall!.appForeignApps.map(getApplicationAddress) + .applicationCall!.foreignApps.map(getApplicationAddress) .map((a) => a.toString()) ); - assets.push(...t.txn.applicationCall!.appForeignAssets); + assets.push(...t.txn.applicationCall!.foreignAssets); // Create application, - if (t.txn.applicationCall!.appId === BigInt(0)) { + if (t.txn.applicationCall!.appIndex === BigInt(0)) { appInfos.push( new Application({ id: defaultAppId, params: new ApplicationParams({ creator: t.txn.sender.toString(), - approvalProgram: t.txn.applicationCall!.appApprovalProgram, - clearStateProgram: t.txn.applicationCall!.appClearProgram, + approvalProgram: t.txn.applicationCall!.approvalProgram, + clearStateProgram: t.txn.applicationCall!.clearProgram, localStateSchema: new ApplicationStateSchema({ - numUint: t.txn.applicationCall!.appLocalInts, - numByteSlice: t.txn.applicationCall!.appLocalByteSlices, + numUint: t.txn.applicationCall!.numLocalInts, + numByteSlice: t.txn.applicationCall!.numLocalByteSlices, }), globalStateSchema: new ApplicationStateSchema({ - numUint: t.txn.applicationCall!.appGlobalInts, - numByteSlice: t.txn.applicationCall!.appGlobalByteSlices, + numUint: t.txn.applicationCall!.numGlobalInts, + numByteSlice: t.txn.applicationCall!.numGlobalByteSlices, }), }), }) ); } else { - const { appId } = t.txn.applicationCall!; - apps.push(appId); - accts.push(getApplicationAddress(appId).toString()); + const { appIndex } = t.txn.applicationCall!; + apps.push(appIndex); + accts.push(getApplicationAddress(appIndex).toString()); } } } diff --git a/src/makeTxn.ts b/src/makeTxn.ts index b8204bb19..19cb692df 100644 --- a/src/makeTxn.ts +++ b/src/makeTxn.ts @@ -323,7 +323,7 @@ export function makeAssetFreezeTxnWithSuggestedParamsFromObject({ sender, assetIndex, freezeTarget, - assetFrozen, + freezeState, suggestedParams, note, lease, @@ -339,7 +339,7 @@ export function makeAssetFreezeTxnWithSuggestedParamsFromObject({ assetFreezeParams: { assetIndex, freezeTarget, - assetFrozen, + freezeState, }, }); } @@ -356,7 +356,7 @@ export function makeAssetTransferTxnWithSuggestedParamsFromObject({ receiver, amount, closeRemainderTo, - assetSender, + revocationTarget, note, assetIndex, suggestedParams, @@ -377,7 +377,7 @@ export function makeAssetTransferTxnWithSuggestedParamsFromObject({ assetIndex, receiver, amount, - assetSender, + revocationTarget, closeRemainderTo, }, }); @@ -390,7 +390,7 @@ export function makeAssetTransferTxnWithSuggestedParamsFromObject({ */ export function makeApplicationCallTxnFromObject({ sender, - appId, + appIndex, onComplete, appArgs, accounts, @@ -420,7 +420,7 @@ export function makeApplicationCallTxnFromObject({ rekeyTo, suggestedParams, appCallParams: { - appId, + appIndex, onComplete, appArgs, accounts, @@ -464,7 +464,7 @@ export function makeApplicationCreateTxnFromObject({ suggestedParams, }: Omit< ApplicationCallTransactionParams, - 'appId' | 'approvalProgram' | 'clearProgram' + 'appIndex' | 'approvalProgram' | 'clearProgram' > & Required< Pick @@ -478,7 +478,7 @@ export function makeApplicationCreateTxnFromObject({ } return makeApplicationCallTxnFromObject({ sender, - appId: 0, + appIndex: 0, onComplete, appArgs, accounts, @@ -506,7 +506,7 @@ export function makeApplicationCreateTxnFromObject({ */ export function makeApplicationUpdateTxnFromObject({ sender, - appId, + appIndex, appArgs, accounts, foreignApps, @@ -533,15 +533,15 @@ export function makeApplicationUpdateTxnFromObject({ Pick > & CommonTransactionParams): Transaction { - if (!appId) { - throw Error('appId must be provided'); + if (!appIndex) { + throw Error('appIndex must be provided'); } if (!approvalProgram || !clearProgram) { throw Error('approvalProgram and clearProgram must be provided'); } return makeApplicationCallTxnFromObject({ sender, - appId, + appIndex, onComplete: OnApplicationComplete.UpdateApplicationOC, appArgs, accounts, @@ -564,7 +564,7 @@ export function makeApplicationUpdateTxnFromObject({ */ export function makeApplicationDeleteTxnFromObject({ sender, - appId, + appIndex, appArgs, accounts, foreignApps, @@ -586,12 +586,12 @@ export function makeApplicationDeleteTxnFromObject({ | 'clearProgram' > & CommonTransactionParams): Transaction { - if (!appId) { - throw Error('appId must be provided'); + if (!appIndex) { + throw Error('appIndex must be provided'); } return makeApplicationCallTxnFromObject({ sender, - appId, + appIndex, onComplete: OnApplicationComplete.DeleteApplicationOC, appArgs, accounts, @@ -612,7 +612,7 @@ export function makeApplicationDeleteTxnFromObject({ */ export function makeApplicationOptInTxnFromObject({ sender, - appId, + appIndex, appArgs, accounts, foreignApps, @@ -634,12 +634,12 @@ export function makeApplicationOptInTxnFromObject({ | 'clearProgram' > & CommonTransactionParams): Transaction { - if (!appId) { - throw Error('appId must be provided'); + if (!appIndex) { + throw Error('appIndex must be provided'); } return makeApplicationCallTxnFromObject({ sender, - appId, + appIndex, onComplete: OnApplicationComplete.OptInOC, appArgs, accounts, @@ -660,7 +660,7 @@ export function makeApplicationOptInTxnFromObject({ */ export function makeApplicationCloseOutTxnFromObject({ sender, - appId, + appIndex, appArgs, accounts, foreignApps, @@ -682,12 +682,12 @@ export function makeApplicationCloseOutTxnFromObject({ | 'clearProgram' > & CommonTransactionParams): Transaction { - if (!appId) { - throw Error('appId must be provided'); + if (!appIndex) { + throw Error('appIndex must be provided'); } return makeApplicationCallTxnFromObject({ sender, - appId, + appIndex, onComplete: OnApplicationComplete.CloseOutOC, appArgs, accounts, @@ -708,7 +708,7 @@ export function makeApplicationCloseOutTxnFromObject({ */ export function makeApplicationClearStateTxnFromObject({ sender, - appId, + appIndex, appArgs, accounts, foreignApps, @@ -730,12 +730,12 @@ export function makeApplicationClearStateTxnFromObject({ | 'clearProgram' > & CommonTransactionParams): Transaction { - if (!appId) { - throw Error('appId must be provided'); + if (!appIndex) { + throw Error('appIndex must be provided'); } return makeApplicationCallTxnFromObject({ sender, - appId, + appIndex, onComplete: OnApplicationComplete.ClearStateOC, appArgs, accounts, @@ -756,7 +756,7 @@ export function makeApplicationClearStateTxnFromObject({ */ export function makeApplicationNoOpTxnFromObject({ sender, - appId, + appIndex, appArgs, accounts, foreignApps, @@ -778,12 +778,12 @@ export function makeApplicationNoOpTxnFromObject({ | 'clearProgram' > & CommonTransactionParams): Transaction { - if (!appId) { - throw Error('appId must be provided'); + if (!appIndex) { + throw Error('appIndex must be provided'); } return makeApplicationCallTxnFromObject({ sender, - appId, + appIndex, onComplete: OnApplicationComplete.NoOpOC, appArgs, accounts, diff --git a/src/transaction.ts b/src/transaction.ts index 8f1d46509..8748d6649 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -199,48 +199,48 @@ export interface KeyRegistrationTransactionFields { } export interface AssetConfigTransactionFields { - readonly assetId: bigint; - readonly assetTotal: bigint; - readonly assetDecimals: number; - readonly assetDefaultFrozen: boolean; - readonly assetManager?: Address; - readonly assetReserve?: Address; - readonly assetFreeze?: Address; - readonly assetClawback?: Address; - readonly assetUnitName?: string; + readonly assetIndex: bigint; + readonly total: bigint; + readonly decimals: number; + readonly defaultFrozen: boolean; + readonly manager?: Address; + readonly reserve?: Address; + readonly freeze?: Address; + readonly clawback?: Address; + readonly unitName?: string; readonly assetName?: string; readonly assetURL?: string; readonly assetMetadataHash?: Uint8Array; } export interface AssetTransferTransactionFields { - readonly assetId: bigint; + readonly assetIndex: bigint; readonly amount: bigint; - readonly sender?: Address; + readonly revocationTarget?: Address; readonly receiver: Address; readonly closeRemainderTo?: Address; } export interface AssetFreezeTransactionFields { - readonly assetId: bigint; + readonly assetIndex: bigint; readonly freezeAccount: Address; - readonly assetFrozen: boolean; + readonly freezeState: boolean; } export interface ApplicationTransactionFields { - readonly appId: bigint; - readonly appOnComplete: OnApplicationComplete; - readonly appLocalInts: number; - readonly appLocalByteSlices: number; - readonly appGlobalInts: number; - readonly appGlobalByteSlices: number; + readonly appIndex: bigint; + readonly onComplete: OnApplicationComplete; + readonly numLocalInts: number; + readonly numLocalByteSlices: number; + readonly numGlobalInts: number; + readonly numGlobalByteSlices: number; readonly extraPages: number; - readonly appApprovalProgram: Uint8Array; - readonly appClearProgram: Uint8Array; + readonly approvalProgram: Uint8Array; + readonly clearProgram: Uint8Array; readonly appArgs: ReadonlyArray; - readonly appAccounts: ReadonlyArray
; - readonly appForeignApps: ReadonlyArray; - readonly appForeignAssets: ReadonlyArray; + readonly accounts: ReadonlyArray
; + readonly foreignApps: ReadonlyArray; + readonly foreignAssets: ReadonlyArray; readonly boxes: ReadonlyArray; } @@ -415,19 +415,21 @@ export class Transaction { if (params.assetConfigParams) { this.assetConfig = { - assetId: utils.ensureUint64(params.assetConfigParams.assetIndex ?? 0), - assetTotal: utils.ensureUint64(params.assetConfigParams.total ?? 0), - assetDecimals: utils.ensureSafeUnsignedInteger( + assetIndex: utils.ensureUint64( + params.assetConfigParams.assetIndex ?? 0 + ), + total: utils.ensureUint64(params.assetConfigParams.total ?? 0), + decimals: utils.ensureSafeUnsignedInteger( params.assetConfigParams.decimals ?? 0 ), - assetDefaultFrozen: ensureBoolean( + defaultFrozen: ensureBoolean( params.assetConfigParams.defaultFrozen ?? false ), - assetManager: optionalAddress(params.assetConfigParams.manager), - assetReserve: optionalAddress(params.assetConfigParams.reserve), - assetFreeze: optionalAddress(params.assetConfigParams.freeze), - assetClawback: optionalAddress(params.assetConfigParams.clawback), - assetUnitName: params.assetConfigParams.unitName ?? '', + manager: optionalAddress(params.assetConfigParams.manager), + reserve: optionalAddress(params.assetConfigParams.reserve), + freeze: optionalAddress(params.assetConfigParams.freeze), + clawback: optionalAddress(params.assetConfigParams.clawback), + unitName: params.assetConfigParams.unitName ?? '', assetName: params.assetConfigParams.assetName ?? '', assetURL: params.assetConfigParams.assetURL ?? '', assetMetadataHash: optionalFixedLengthByteArray( @@ -440,9 +442,11 @@ export class Transaction { if (params.assetTransferParams) { this.assetTransfer = { - assetId: utils.ensureUint64(params.assetTransferParams.assetIndex), + assetIndex: utils.ensureUint64(params.assetTransferParams.assetIndex), amount: utils.ensureUint64(params.assetTransferParams.amount), - sender: optionalAddress(params.assetTransferParams.assetSender), + revocationTarget: optionalAddress( + params.assetTransferParams.revocationTarget + ), receiver: ensureAddress(params.assetTransferParams.receiver), closeRemainderTo: optionalAddress( params.assetTransferParams.closeRemainderTo @@ -452,9 +456,9 @@ export class Transaction { if (params.assetFreezeParams) { this.assetFreeze = { - assetId: utils.ensureUint64(params.assetFreezeParams.assetIndex), + assetIndex: utils.ensureUint64(params.assetFreezeParams.assetIndex), freezeAccount: ensureAddress(params.assetFreezeParams.freezeTarget), - assetFrozen: ensureBoolean(params.assetFreezeParams.assetFrozen), + freezeState: ensureBoolean(params.assetFreezeParams.freezeState), }; } @@ -464,39 +468,39 @@ export class Transaction { throw new Error(`Invalid onCompletion value: ${onComplete}`); } this.applicationCall = { - appId: utils.ensureUint64(params.appCallParams.appId), - appOnComplete: onComplete, - appLocalInts: utils.ensureSafeUnsignedInteger( + appIndex: utils.ensureUint64(params.appCallParams.appIndex), + onComplete, + numLocalInts: utils.ensureSafeUnsignedInteger( params.appCallParams.numLocalInts ?? 0 ), - appLocalByteSlices: utils.ensureSafeUnsignedInteger( + numLocalByteSlices: utils.ensureSafeUnsignedInteger( params.appCallParams.numLocalByteSlices ?? 0 ), - appGlobalInts: utils.ensureSafeUnsignedInteger( + numGlobalInts: utils.ensureSafeUnsignedInteger( params.appCallParams.numGlobalInts ?? 0 ), - appGlobalByteSlices: utils.ensureSafeUnsignedInteger( + numGlobalByteSlices: utils.ensureSafeUnsignedInteger( params.appCallParams.numGlobalByteSlices ?? 0 ), extraPages: utils.ensureSafeUnsignedInteger( params.appCallParams.extraPages ?? 0 ), - appApprovalProgram: ensureUint8Array( + approvalProgram: ensureUint8Array( params.appCallParams.approvalProgram ?? new Uint8Array() ), - appClearProgram: ensureUint8Array( + clearProgram: ensureUint8Array( params.appCallParams.clearProgram ?? new Uint8Array() ), appArgs: ensureArray(params.appCallParams.appArgs ?? []).map( ensureUint8Array ), - appAccounts: ensureArray(params.appCallParams.accounts ?? []).map( + accounts: ensureArray(params.appCallParams.accounts ?? []).map( ensureAddress ), - appForeignApps: ensureArray(params.appCallParams.foreignApps ?? []).map( + foreignApps: ensureArray(params.appCallParams.foreignApps ?? []).map( utils.ensureUint64 ), - appForeignAssets: ensureArray( + foreignAssets: ensureArray( params.appCallParams.foreignAssets ?? [] ).map(utils.ensureUint64), boxes: ensureArray(params.appCallParams.boxes ?? []).map( @@ -606,33 +610,33 @@ export class Transaction { } if (this.assetConfig) { - if (this.assetConfig.assetId) { - forEncoding.caid = this.assetConfig.assetId; + if (this.assetConfig.assetIndex) { + forEncoding.caid = this.assetConfig.assetIndex; } const assetParams: EncodedAssetParams = {}; - if (this.assetConfig.assetTotal) { - assetParams.t = this.assetConfig.assetTotal; + if (this.assetConfig.total) { + assetParams.t = this.assetConfig.total; } - if (this.assetConfig.assetDecimals) { - assetParams.dc = this.assetConfig.assetDecimals; + if (this.assetConfig.decimals) { + assetParams.dc = this.assetConfig.decimals; } - if (this.assetConfig.assetDefaultFrozen) { - assetParams.df = this.assetConfig.assetDefaultFrozen; + if (this.assetConfig.defaultFrozen) { + assetParams.df = this.assetConfig.defaultFrozen; } - if (this.assetConfig.assetManager) { - assetParams.m = this.assetConfig.assetManager.publicKey; + if (this.assetConfig.manager) { + assetParams.m = this.assetConfig.manager.publicKey; } - if (this.assetConfig.assetReserve) { - assetParams.r = this.assetConfig.assetReserve.publicKey; + if (this.assetConfig.reserve) { + assetParams.r = this.assetConfig.reserve.publicKey; } - if (this.assetConfig.assetFreeze) { - assetParams.f = this.assetConfig.assetFreeze.publicKey; + if (this.assetConfig.freeze) { + assetParams.f = this.assetConfig.freeze.publicKey; } - if (this.assetConfig.assetClawback) { - assetParams.c = this.assetConfig.assetClawback.publicKey; + if (this.assetConfig.clawback) { + assetParams.c = this.assetConfig.clawback.publicKey; } - if (this.assetConfig.assetUnitName) { - assetParams.un = this.assetConfig.assetUnitName; + if (this.assetConfig.unitName) { + assetParams.un = this.assetConfig.unitName; } if (this.assetConfig.assetName) { assetParams.an = this.assetConfig.assetName; @@ -650,8 +654,8 @@ export class Transaction { } if (this.assetTransfer) { - if (this.assetTransfer.assetId) { - forEncoding.xaid = this.assetTransfer.assetId; + if (this.assetTransfer.assetIndex) { + forEncoding.xaid = this.assetTransfer.assetIndex; } if (this.assetTransfer.amount) { forEncoding.aamt = this.assetTransfer.amount; @@ -662,18 +666,18 @@ export class Transaction { if (this.assetTransfer.closeRemainderTo) { forEncoding.aclose = this.assetTransfer.closeRemainderTo.publicKey; } - if (this.assetTransfer.sender) { - forEncoding.asnd = this.assetTransfer.sender.publicKey; + if (this.assetTransfer.revocationTarget) { + forEncoding.asnd = this.assetTransfer.revocationTarget.publicKey; } return forEncoding; } if (this.assetFreeze) { - if (this.assetFreeze.assetId) { - forEncoding.faid = this.assetFreeze.assetId; + if (this.assetFreeze.assetIndex) { + forEncoding.faid = this.assetFreeze.assetIndex; } - if (this.assetFreeze.assetFrozen) { - forEncoding.afrz = this.assetFreeze.assetFrozen; + if (this.assetFreeze.freezeState) { + forEncoding.afrz = this.assetFreeze.freezeState; } if (!uint8ArrayIsEmpty(this.assetFreeze.freezeAccount.publicKey)) { forEncoding.fadd = this.assetFreeze.freezeAccount.publicKey; @@ -682,62 +686,62 @@ export class Transaction { } if (this.applicationCall) { - if (this.applicationCall.appId) { - forEncoding.apid = this.applicationCall.appId; + if (this.applicationCall.appIndex) { + forEncoding.apid = this.applicationCall.appIndex; } - if (this.applicationCall.appOnComplete) { - forEncoding.apan = this.applicationCall.appOnComplete; + if (this.applicationCall.onComplete) { + forEncoding.apan = this.applicationCall.onComplete; } if (this.applicationCall.appArgs.length) { forEncoding.apaa = this.applicationCall.appArgs.slice(); } - if (this.applicationCall.appAccounts.length) { - forEncoding.apat = this.applicationCall.appAccounts.map( + if (this.applicationCall.accounts.length) { + forEncoding.apat = this.applicationCall.accounts.map( (decodedAddress) => decodedAddress.publicKey ); } - if (this.applicationCall.appForeignAssets.length) { - forEncoding.apas = this.applicationCall.appForeignAssets.slice(); + if (this.applicationCall.foreignAssets.length) { + forEncoding.apas = this.applicationCall.foreignAssets.slice(); } - if (this.applicationCall.appForeignApps.length) { - forEncoding.apfa = this.applicationCall.appForeignApps.slice(); + if (this.applicationCall.foreignApps.length) { + forEncoding.apfa = this.applicationCall.foreignApps.slice(); } if (this.applicationCall.boxes.length) { forEncoding.apbx = translateBoxReferences( this.applicationCall.boxes, - this.applicationCall.appForeignApps, - this.applicationCall.appId + this.applicationCall.foreignApps, + this.applicationCall.appIndex ); } - if (this.applicationCall.appApprovalProgram.length) { - forEncoding.apap = this.applicationCall.appApprovalProgram; + if (this.applicationCall.approvalProgram.length) { + forEncoding.apap = this.applicationCall.approvalProgram; } - if (this.applicationCall.appClearProgram.length) { - forEncoding.apsu = this.applicationCall.appClearProgram; + if (this.applicationCall.clearProgram.length) { + forEncoding.apsu = this.applicationCall.clearProgram; } if ( - this.applicationCall.appLocalInts || - this.applicationCall.appLocalByteSlices + this.applicationCall.numLocalInts || + this.applicationCall.numLocalByteSlices ) { const localSchema: EncodedLocalStateSchema = {}; - if (this.applicationCall.appLocalInts) { - localSchema.nui = this.applicationCall.appLocalInts; + if (this.applicationCall.numLocalInts) { + localSchema.nui = this.applicationCall.numLocalInts; } - if (this.applicationCall.appLocalByteSlices) { - localSchema.nbs = this.applicationCall.appLocalByteSlices; + if (this.applicationCall.numLocalByteSlices) { + localSchema.nbs = this.applicationCall.numLocalByteSlices; } forEncoding.apls = localSchema; } if ( - this.applicationCall.appGlobalInts || - this.applicationCall.appGlobalByteSlices + this.applicationCall.numGlobalInts || + this.applicationCall.numGlobalByteSlices ) { const globalSchema: EncodedGlobalStateSchema = {}; - if (this.applicationCall.appGlobalInts) { - globalSchema.nui = this.applicationCall.appGlobalInts; + if (this.applicationCall.numGlobalInts) { + globalSchema.nui = this.applicationCall.numGlobalInts; } - if (this.applicationCall.appGlobalByteSlices) { - globalSchema.nbs = this.applicationCall.appGlobalByteSlices; + if (this.applicationCall.numGlobalByteSlices) { + globalSchema.nbs = this.applicationCall.numGlobalByteSlices; } forEncoding.apgs = globalSchema; } @@ -849,7 +853,7 @@ export class Transaction { assetTransferParams.closeRemainderTo = new Address(txnForEnc.aclose); } if (txnForEnc.asnd) { - assetTransferParams.assetSender = new Address(txnForEnc.asnd); + assetTransferParams.revocationTarget = new Address(txnForEnc.asnd); } params.assetTransferParams = assetTransferParams; } else if (params.type === TransactionType.afrz) { @@ -858,12 +862,12 @@ export class Transaction { freezeTarget: txnForEnc.fadd ? new Address(txnForEnc.fadd) : Address.zeroAddress(), - assetFrozen: txnForEnc.afrz ?? false, + freezeState: txnForEnc.afrz ?? false, }; params.assetFreezeParams = assetFreezeParams; } else if (params.type === TransactionType.appl) { const appCallParams: ApplicationCallTransactionParams = { - appId: txnForEnc.apid ?? 0, + appIndex: txnForEnc.apid ?? 0, onComplete: utils.ensureSafeUnsignedInteger(txnForEnc.apan ?? 0), appArgs: txnForEnc.apaa, accounts: (txnForEnc.apat ?? []).map((pk) => new Address(pk)), diff --git a/src/types/transactions/base.ts b/src/types/transactions/base.ts index dfb4acd0a..113533ab6 100644 --- a/src/types/transactions/base.ts +++ b/src/types/transactions/base.ts @@ -324,9 +324,9 @@ export interface AssetTransferTransactionParams { /** * String representation of Algorand address – if provided, and if "sender" is - * the asset's revocation manager, then deduct from "assetSender" rather than "sender" + * the asset's revocation manager, then deduct from "revocationTarget" rather than "sender" */ - assetSender?: string | Address; + revocationTarget?: string | Address; /** * The Algorand address of recipient @@ -364,7 +364,7 @@ export interface AssetFreezeTransactionParams { /** * true if freezeTarget should be frozen, false if freezeTarget should be allowed to transact */ - assetFrozen: boolean; + freezeState: boolean; } /** @@ -377,7 +377,7 @@ export interface ApplicationCallTransactionParams { /** * A unique application ID */ - appId: number | bigint; + appIndex: number | bigint; /** * What application should do once the program has been run diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts index 45afff12d..f0f729db8 100644 --- a/src/types/transactions/encoded.ts +++ b/src/types/transactions/encoded.ts @@ -235,7 +235,7 @@ export interface EncodedTransaction { faid?: number | bigint; /** - * assetFrozen + * freezeState */ afrz?: boolean; @@ -245,7 +245,7 @@ export interface EncodedTransaction { fadd?: Uint8Array; /** - * assetSender + * assetRevocationTarget */ asnd?: Uint8Array; diff --git a/tests/10.ABI.ts b/tests/10.ABI.ts index 0e8d5d2a3..68f309b6e 100644 --- a/tests/10.ABI.ts +++ b/tests/10.ABI.ts @@ -492,16 +492,16 @@ describe('ABI encoding', () => { const txn = txns[0].txn; // Assert that foreign objects were passed in and ordering was correct. - assert.deepStrictEqual(txn.applicationCall?.appForeignApps?.length, 2); - assert.deepStrictEqual(txn.applicationCall?.appForeignApps[0], 1n); - assert.deepStrictEqual(txn.applicationCall?.appForeignApps[1], 2n); + assert.deepStrictEqual(txn.applicationCall?.foreignApps?.length, 2); + assert.deepStrictEqual(txn.applicationCall?.foreignApps[0], 1n); + assert.deepStrictEqual(txn.applicationCall?.foreignApps[1], 2n); - assert.deepStrictEqual(txn.applicationCall?.appForeignAssets?.length, 1); - assert.deepStrictEqual(txn.applicationCall?.appForeignAssets[0], 124n); + assert.deepStrictEqual(txn.applicationCall?.foreignAssets?.length, 1); + assert.deepStrictEqual(txn.applicationCall?.foreignAssets[0], 124n); - assert.deepStrictEqual(txn.applicationCall?.appAccounts?.length, 1); + assert.deepStrictEqual(txn.applicationCall?.accounts?.length, 1); assert.deepStrictEqual( - txn.applicationCall?.appAccounts[0], + txn.applicationCall?.accounts[0], decodeAddress(foreignAcct) ); }); diff --git a/tests/5.Transaction.ts b/tests/5.Transaction.ts index a47421bfc..a7e4affa0 100644 --- a/tests/5.Transaction.ts +++ b/tests/5.Transaction.ts @@ -26,7 +26,7 @@ describe('Sign', () => { genesisID: 'mock-network', }, appCallParams: { - appId: 5, + appIndex: 5, onComplete: algosdk.OnApplicationComplete.NoOpOC, appArgs, accounts, @@ -47,9 +47,9 @@ describe('Sign', () => { assert.deepStrictEqual(foreignAssets, [7, 8, 9]); assert.ok(txn.applicationCall); assert.ok(txn.applicationCall.appArgs !== appArgs); - assert.ok((txn.applicationCall.appAccounts as any) !== accounts); - assert.ok((txn.applicationCall.appForeignApps as any) !== foreignApps); - assert.ok((txn.applicationCall.appForeignAssets as any) !== foreignAssets); + assert.ok((txn.applicationCall.accounts as any) !== accounts); + assert.ok((txn.applicationCall.foreignApps as any) !== foreignApps); + assert.ok((txn.applicationCall.foreignAssets as any) !== foreignAssets); assert.ok((txn.applicationCall.boxes as any) !== boxes); }); @@ -432,7 +432,7 @@ describe('Sign', () => { assetIndex: 9999, receiver: sender, amount: 0, - assetSender: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + revocationTarget: algosdk.ALGORAND_ZERO_ADDRESS_STRING, }, suggestedParams, }), @@ -814,7 +814,7 @@ describe('Sign', () => { receiver: address, amount: 100, closeRemainderTo: address, - assetSender: address, + revocationTarget: address, }, suggestedParams: { minFee: 1000, @@ -886,7 +886,7 @@ describe('Sign', () => { sender: address, assetFreezeParams: { assetIndex: 1, - assetFrozen: true, + freezeState: true, freezeTarget: address, }, suggestedParams: { @@ -990,7 +990,7 @@ describe('Sign', () => { assetFreezeParams: { assetIndex: 9999, freezeTarget: algosdk.ALGORAND_ZERO_ADDRESS_STRING, - assetFrozen: true, + freezeState: true, }, suggestedParams: { minFee: 1000, @@ -1028,7 +1028,7 @@ describe('Sign', () => { sender: address, assetFreezeParams: { assetIndex: 1, - assetFrozen: true, + freezeState: true, freezeTarget: address, }, suggestedParams: { @@ -1858,7 +1858,7 @@ describe('Sign', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; const sender = addr; const receiver = addr; - const assetSender = addr; + const revocationTarget = addr; const closeRemainderTo = addr; const assetIndex = 1234; const amount = 100; @@ -1878,7 +1878,7 @@ describe('Sign', () => { sender, assetTransferParams: { receiver, - assetSender, + revocationTarget, closeRemainderTo, assetIndex, amount, @@ -1892,7 +1892,7 @@ describe('Sign', () => { sender, receiver, closeRemainderTo, - assetSender, + revocationTarget, amount, note, assetIndex, @@ -1906,7 +1906,7 @@ describe('Sign', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; const assetIndex = 1234; const freezeTarget = addr; - const assetFrozen = true; + const freezeState = true; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; @@ -1923,7 +1923,7 @@ describe('Sign', () => { sender: addr, assetFreezeParams: { freezeTarget, - assetFrozen, + freezeState, assetIndex, }, suggestedParams, @@ -1936,7 +1936,7 @@ describe('Sign', () => { note, assetIndex, freezeTarget, - assetFrozen, + freezeState, suggestedParams, rekeyTo, } diff --git a/tests/7.AlgoSDK.ts b/tests/7.AlgoSDK.ts index af8ca3ad3..4eb6e9235 100644 --- a/tests/7.AlgoSDK.ts +++ b/tests/7.AlgoSDK.ts @@ -677,7 +677,7 @@ describe('Algosdk (AKA end to end)', () => { sender: addr, freezeTarget: addr, assetIndex: 1, - assetFrozen: true, + freezeState: true, suggestedParams: { minFee: 1000, fee: 10, @@ -758,7 +758,7 @@ describe('Algosdk (AKA end to end)', () => { const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ sender: addr, receiver: addr, - assetSender: addr, + revocationTarget: addr, amount: 1, assetIndex: 1, suggestedParams: { diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 75d9d08fe..89eff5767 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -1334,7 +1334,7 @@ module.exports = function getSteps(options) { sender: freezer, freezeTarget: this.accounts[1], assetIndex: parseInt(this.assetTestFixture.index), - assetFrozen: false, + freezeState: false, note: this.note, suggestedParams: this.params, }); @@ -1361,7 +1361,7 @@ module.exports = function getSteps(options) { sender: freezer, freezeTarget: this.accounts[1], assetIndex: parseInt(this.assetTestFixture.index), - assetFrozen: true, + freezeState: true, note: this.note, suggestedParams: this.params, }); @@ -1386,7 +1386,7 @@ module.exports = function getSteps(options) { algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ sender: this.assetTestFixture.creator, receiver: this.assetTestFixture.creator, - assetSender: this.accounts[1], + revocationTarget: this.accounts[1], amount: parseInt(amount), note: this.note, genesisHash: this.gh, @@ -3127,7 +3127,7 @@ module.exports = function getSteps(options) { case 'call': this.txn = algosdk.makeApplicationNoOpTxnFromObject({ sender, - appId: appIndex, + appIndex, appArgs, accounts: appAccounts, foreignApps, @@ -3158,7 +3158,7 @@ module.exports = function getSteps(options) { case 'update': this.txn = algosdk.makeApplicationUpdateTxnFromObject({ sender, - appId: appIndex, + appIndex, approvalProgram: approvalProgramBytes, clearProgram: clearProgramBytes, appArgs, @@ -3172,7 +3172,7 @@ module.exports = function getSteps(options) { case 'optin': this.txn = algosdk.makeApplicationOptInTxnFromObject({ sender, - appId: appIndex, + appIndex, appArgs, accounts: appAccounts, foreignApps, @@ -3184,7 +3184,7 @@ module.exports = function getSteps(options) { case 'delete': this.txn = algosdk.makeApplicationDeleteTxnFromObject({ sender, - appId: appIndex, + appIndex, appArgs, accounts: appAccounts, foreignApps, @@ -3196,7 +3196,7 @@ module.exports = function getSteps(options) { case 'clear': this.txn = algosdk.makeApplicationClearStateTxnFromObject({ sender, - appId: appIndex, + appIndex, appArgs, accounts: appAccounts, foreignApps, @@ -3208,7 +3208,7 @@ module.exports = function getSteps(options) { case 'closeout': this.txn = algosdk.makeApplicationCloseOutTxnFromObject({ sender, - appId: appIndex, + appIndex, appArgs, accounts: appAccounts, foreignApps, @@ -3394,7 +3394,7 @@ module.exports = function getSteps(options) { if (sp.firstValid === 0) sp.firstValid = 1; this.txn = algosdk.makeApplicationCallTxnFromObject({ sender: this.transientAccount.addr, - appId: this.currentApplicationIndex, + appIndex: this.currentApplicationIndex, onComplete: operation, numLocalInts, numLocalByteSlices, diff --git a/v2_TO_v3_MIGRATION_GUIDE.md b/v2_TO_v3_MIGRATION_GUIDE.md index 8c8d8bd2f..10c62aa25 100644 --- a/v2_TO_v3_MIGRATION_GUIDE.md +++ b/v2_TO_v3_MIGRATION_GUIDE.md @@ -133,10 +133,7 @@ These interfaces differ slightly from the v2 types. Some field names have change | " | `selectionKey` | `string \| Uint8Array` | `selectionKey` | `Uint8Array` | Base64 encoded value is no longer accepted | | " | `stateProofKey` | `string \| Uint8Array` | `stateProofKey` | `Uint8Array` | Base64 encoded value is no longer accepted | | Asset Config | `assetMetadataHash` | `string \| Uint8Array` | `assetMetadataHash` | `Uint8Array` | Base64 encoded value is no longer accepted | -| Asset Freeze | `freezeState` | `boolean` | `assetFrozen` | `boolean` | | | Asset Transfer | `to` | `string` | `receiver` | `string \| Address` | | -| " | `revocationTarget` | `string` | `assetSender` | `string \| Address` | | -| Application Call | `appIndex` | `number` | `appId` | `number \| bigint` | | Given these changes, the earlier v2 example would be equivalent to the following in v3: @@ -185,36 +182,36 @@ The following table shows the correspondence between v2 and v3 fields in the `Tr | `voteLast` | `number` | `keyreg.voteLast` | `bigint` | | | `voteKeyDilution` | `number` | `keyreg.voteKeyDilution` | `bigint` | | | `nonParticipation` | `boolean` | `keyreg.nonParticipation` | `boolean` | | -| `assetIndex` | `number` | `assetConfig.assetId` | `bigint` | If the transaction type is asset config | -| " | " | `assetTransfer.assetId` | `bigint` | If the transaction type is asset transfer | -| " | " | `assetFreeze.assetId` | `bigint` | If the transaction type is asset freeze | -| `assetTotal` | `number \| bigint` | `assetConfig.assetTotal` | `bigint` | | -| `assetDecimals` | `number` | `assetConfig.assetDecimals` | `number` | | -| `assetDefaultFrozen` | `boolean` | `assetConfig.assetDefaultFrozen` | `boolean` | | -| `assetManager` | `Address` | `assetConfig.assetManager` | `Address` | | -| `assetReserve` | `Address` | `assetConfig.assetReserve` | `Address` | | -| `assetFreeze` | `Address` | `assetConfig.assetFreeze` | `Address` | | -| `assetClawback` | `Address` | `assetConfig.assetClawback` | `Address` | | -| `assetUnitName` | `string` | `assetConfig.assetUnitName` | `string` | | +| `assetIndex` | `number` | `assetConfig.assetIndex` | `bigint` | If the transaction type is asset config | +| " | " | `assetTransfer.assetIndex` | `bigint` | If the transaction type is asset transfer | +| " | " | `assetFreeze.assetIndex` | `bigint` | If the transaction type is asset freeze | +| `assetTotal` | `number \| bigint` | `assetConfig.total` | `bigint` | | +| `assetDecimals` | `number` | `assetConfig.decimals` | `number` | | +| `assetDefaultFrozen` | `boolean` | `assetConfig.defaultFrozen` | `boolean` | | +| `assetManager` | `Address` | `assetConfig.manager` | `Address` | | +| `assetReserve` | `Address` | `assetConfig.reserve` | `Address` | | +| `assetFreeze` | `Address` | `assetConfig.freeze` | `Address` | | +| `assetClawback` | `Address` | `assetConfig.clawback` | `Address` | | +| `assetUnitName` | `string` | `assetConfig.unitName` | `string` | | | `assetName` | `string` | `assetConfig.assetName` | `string` | | | `assetURL` | `string` | `assetConfig.assetURL` | `string` | | | `assetMetadataHash` | `Uint8Array` | `assetConfig.assetMetadataHash` | `Uint8Array` | | | `freezeAccount` | `Address` | `assetFreeze.freezeAccount` | `Address` | | -| `freezeState` | `boolean` | `assetFreeze.assetFrozen` | `boolean` | | -| `assetRevocationTarget` | `Address` | `assetTransfer.sender` | `Address` | | -| `appIndex` | `number` | `applicationCall.appId` | `bigint` | | -| `appOnComplete` | `OnApplicationComplete` | `applicationCall.appOnComplete` | `OnApplicationComplete` | | -| `appLocalInts` | `number` | `applicationCall.appLocalInts` | `number` | | -| `appLocalByteSlices` | `number` | `applicationCall.appLocalByteSlices` | `number` | | -| `appGlobalInts` | `number` | `applicationCall.appGlobalInts` | `number` | | -| `appGlobalByteSlices` | `number` | `applicationCall.appGlobalByteSlices` | `number` | | +| `freezeState` | `boolean` | `assetFreeze.freezeState` | `boolean` | | +| `assetRevocationTarget` | `Address` | `assetTransfer.revocationTarget` | `Address` | | +| `appIndex` | `number` | `applicationCall.appIndex` | `bigint` | | +| `appOnComplete` | `OnApplicationComplete` | `applicationCall.onComplete` | `OnApplicationComplete` | | +| `appLocalInts` | `number` | `applicationCall.numLocalInts` | `number` | | +| `appLocalByteSlices` | `number` | `applicationCall.numLocalByteSlices` | `number` | | +| `appGlobalInts` | `number` | `applicationCall.numGlobalInts` | `number` | | +| `appGlobalByteSlices` | `number` | `applicationCall.numGlobalByteSlices` | `number` | | | `extraPages` | `number` | `applicationCall.extraPages` | `number` | | -| `appApprovalProgram` | `Uint8Array` | `applicationCall.appApprovalProgram` | `Uint8Array` | | -| `appClearProgram` | `Uint8Array` | `applicationCall.appClearProgram` | `Uint8Array` | | +| `appApprovalProgram` | `Uint8Array` | `applicationCall.approvalProgram` | `Uint8Array` | | +| `appClearProgram` | `Uint8Array` | `applicationCall.clearProgram` | `Uint8Array` | | | `appArgs` | `Uint8Array[]` | `applicationCall.appArgs` | `Uint8Array[]` | | -| `appAccounts` | `Address[]` | `applicationCall.appAccounts` | `Address[]` | | -| `appForeignApps` | `number[]` | `applicationCall.appForeignApps` | `bigint[]` | | -| `appForeignAssets` | `number[]` | `applicationCall.appForeignAssets` | `bigint[]` | | +| `appAccounts` | `Address[]` | `applicationCall.accounts` | `Address[]` | | +| `appForeignApps` | `number[]` | `applicationCall.foreignApps` | `bigint[]` | | +| `appForeignAssets` | `number[]` | `applicationCall.foreignAssets` | `bigint[]` | | | `boxes` | `BoxReference[]` | `applicationCall.boxes` | `TransactionBoxReference[]` | | | `stateProofType` | `number \| bigint` | `stateProof.stateProofType` | `number` | | | `stateProof` | `Uint8Array` | `stateProof.stateProof` | `Uint8Array` | | From 254a9a5ca863e0da8e0139f0b59b8297a733a476 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 6 Feb 2024 17:47:10 -0500 Subject: [PATCH 09/17] Fix examples --- examples/app.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/app.ts b/examples/app.ts index 555285963..f9408951d 100644 --- a/examples/app.ts +++ b/examples/app.ts @@ -79,7 +79,7 @@ async function main() { // example: APP_OPTIN const appOptInTxn = algosdk.makeApplicationOptInTxnFromObject({ sender: caller.addr, - appId, + appIndex: appId, suggestedParams, }); @@ -92,7 +92,7 @@ async function main() { // example: APP_NOOP const appNoOpTxn = algosdk.makeApplicationNoOpTxnFromObject({ sender: caller.addr, - appId, + appIndex: appId, suggestedParams, }); @@ -106,7 +106,7 @@ async function main() { const anotherAppOptInTxn = algosdk.makeApplicationOptInTxnFromObject({ sender: anotherCaller.addr, - appId, + appIndex: appId, suggestedParams, }); @@ -120,7 +120,7 @@ async function main() { const simpleAddTxn = algosdk.makeApplicationNoOpTxnFromObject({ sender: caller.addr, suggestedParams, - appId, + appIndex: appId, appArgs: [new TextEncoder().encode(now)], }); @@ -160,7 +160,7 @@ async function main() { // example: APP_CLOSEOUT const appCloseOutTxn = algosdk.makeApplicationCloseOutTxnFromObject({ sender: caller.addr, - appId, + appIndex: appId, suggestedParams, }); @@ -180,7 +180,7 @@ async function main() { const appUpdateTxn = algosdk.makeApplicationUpdateTxnFromObject({ sender: creator.addr, suggestedParams, - appId, + appIndex: appId, // updates must define both approval and clear programs, even if unchanged approvalProgram: new Uint8Array(compiledNewProgram), clearProgram: new Uint8Array(compiledClearProgram), @@ -196,7 +196,7 @@ async function main() { const appClearTxn = algosdk.makeApplicationClearStateTxnFromObject({ sender: anotherCaller.addr, suggestedParams, - appId, + appIndex: appId, }); await algodClient @@ -209,7 +209,7 @@ async function main() { const appDeleteTxn = algosdk.makeApplicationDeleteTxnFromObject({ sender: creator.addr, suggestedParams, - appId, + appIndex: appId, }); await algodClient From 6d9b5aede0b50207f351211d3d8f98cba3f94ab2 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 7 Feb 2024 09:53:01 -0500 Subject: [PATCH 10/17] Update circle browser tools --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5801ad9cb..daeb2e60a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2.1 orbs: node: circleci/node@5.0.0 slack: circleci/slack@4.4.2 - browser-tools: circleci/browser-tools@1.4.3 + browser-tools: circleci/browser-tools@1.4.6 gh-pages: sugarshin/gh-pages@1.0.0 parameters: From 804ccf2347ac1f0466b80333bdc4981a8c361023 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 7 Feb 2024 10:01:26 -0500 Subject: [PATCH 11/17] Replace existing chrome --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index daeb2e60a..30bc303fc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,7 +89,8 @@ jobs: $(lsb_release -cs) stable" | $SUDO tee /etc/apt/sources.list.d/docker.list > /dev/null $SUDO apt update $SUDO apt -y install docker-ce docker-ce-cli containerd.io - - browser-tools/install-browser-tools + - browser-tools/install-browser-tools: + replace-existing-chrome: true - run: name: << parameters.browser >> test command: | From 2e51a5db6eab0797b2c11d166d2ecc09e9819f2e Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 7 Feb 2024 10:57:14 -0500 Subject: [PATCH 12/17] Redo parameter rename --- examples/asa.ts | 8 ++++---- src/makeTxn.ts | 8 ++++---- src/transaction.ts | 22 ++++++++++------------ src/types/transactions/base.ts | 6 +++--- src/types/transactions/encoded.ts | 4 ++-- tests/5.Transaction.ts | 22 +++++++++++----------- tests/7.AlgoSDK.ts | 4 ++-- tests/cucumber/steps/steps.js | 6 +++--- v2_TO_v3_MIGRATION_GUIDE.md | 6 ++++-- 9 files changed, 43 insertions(+), 43 deletions(-) diff --git a/examples/asa.ts b/examples/asa.ts index 62dee7c88..64b4ffffb 100644 --- a/examples/asa.ts +++ b/examples/asa.ts @@ -114,8 +114,8 @@ async function main() { sender: manager.addr, suggestedParams, assetIndex, - // freezeState: false would unfreeze the account's asset holding - freezeState: true, + // frozen: false would unfreeze the account's asset holding + frozen: true, // freezeTarget is the account that is being frozen or unfrozen freezeTarget: receiver.addr, }); @@ -130,8 +130,8 @@ async function main() { { sender: manager.addr, receiver: creator.addr, - // revocationTarget is the account that is being clawed back from - revocationTarget: receiver.addr, + // assetSender is the account that is being clawed back from + assetSender: receiver.addr, suggestedParams, assetIndex, amount: 1, diff --git a/src/makeTxn.ts b/src/makeTxn.ts index 19cb692df..00bd733ff 100644 --- a/src/makeTxn.ts +++ b/src/makeTxn.ts @@ -323,7 +323,7 @@ export function makeAssetFreezeTxnWithSuggestedParamsFromObject({ sender, assetIndex, freezeTarget, - freezeState, + frozen, suggestedParams, note, lease, @@ -339,7 +339,7 @@ export function makeAssetFreezeTxnWithSuggestedParamsFromObject({ assetFreezeParams: { assetIndex, freezeTarget, - freezeState, + frozen, }, }); } @@ -356,7 +356,7 @@ export function makeAssetTransferTxnWithSuggestedParamsFromObject({ receiver, amount, closeRemainderTo, - revocationTarget, + assetSender, note, assetIndex, suggestedParams, @@ -377,7 +377,7 @@ export function makeAssetTransferTxnWithSuggestedParamsFromObject({ assetIndex, receiver, amount, - revocationTarget, + assetSender, closeRemainderTo, }, }); diff --git a/src/transaction.ts b/src/transaction.ts index 8748d6649..876feb4d2 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -216,7 +216,7 @@ export interface AssetConfigTransactionFields { export interface AssetTransferTransactionFields { readonly assetIndex: bigint; readonly amount: bigint; - readonly revocationTarget?: Address; + readonly assetSender?: Address; readonly receiver: Address; readonly closeRemainderTo?: Address; } @@ -224,7 +224,7 @@ export interface AssetTransferTransactionFields { export interface AssetFreezeTransactionFields { readonly assetIndex: bigint; readonly freezeAccount: Address; - readonly freezeState: boolean; + readonly frozen: boolean; } export interface ApplicationTransactionFields { @@ -444,9 +444,7 @@ export class Transaction { this.assetTransfer = { assetIndex: utils.ensureUint64(params.assetTransferParams.assetIndex), amount: utils.ensureUint64(params.assetTransferParams.amount), - revocationTarget: optionalAddress( - params.assetTransferParams.revocationTarget - ), + assetSender: optionalAddress(params.assetTransferParams.assetSender), receiver: ensureAddress(params.assetTransferParams.receiver), closeRemainderTo: optionalAddress( params.assetTransferParams.closeRemainderTo @@ -458,7 +456,7 @@ export class Transaction { this.assetFreeze = { assetIndex: utils.ensureUint64(params.assetFreezeParams.assetIndex), freezeAccount: ensureAddress(params.assetFreezeParams.freezeTarget), - freezeState: ensureBoolean(params.assetFreezeParams.freezeState), + frozen: ensureBoolean(params.assetFreezeParams.frozen), }; } @@ -666,8 +664,8 @@ export class Transaction { if (this.assetTransfer.closeRemainderTo) { forEncoding.aclose = this.assetTransfer.closeRemainderTo.publicKey; } - if (this.assetTransfer.revocationTarget) { - forEncoding.asnd = this.assetTransfer.revocationTarget.publicKey; + if (this.assetTransfer.assetSender) { + forEncoding.asnd = this.assetTransfer.assetSender.publicKey; } return forEncoding; } @@ -676,8 +674,8 @@ export class Transaction { if (this.assetFreeze.assetIndex) { forEncoding.faid = this.assetFreeze.assetIndex; } - if (this.assetFreeze.freezeState) { - forEncoding.afrz = this.assetFreeze.freezeState; + if (this.assetFreeze.frozen) { + forEncoding.afrz = this.assetFreeze.frozen; } if (!uint8ArrayIsEmpty(this.assetFreeze.freezeAccount.publicKey)) { forEncoding.fadd = this.assetFreeze.freezeAccount.publicKey; @@ -853,7 +851,7 @@ export class Transaction { assetTransferParams.closeRemainderTo = new Address(txnForEnc.aclose); } if (txnForEnc.asnd) { - assetTransferParams.revocationTarget = new Address(txnForEnc.asnd); + assetTransferParams.assetSender = new Address(txnForEnc.asnd); } params.assetTransferParams = assetTransferParams; } else if (params.type === TransactionType.afrz) { @@ -862,7 +860,7 @@ export class Transaction { freezeTarget: txnForEnc.fadd ? new Address(txnForEnc.fadd) : Address.zeroAddress(), - freezeState: txnForEnc.afrz ?? false, + frozen: txnForEnc.afrz ?? false, }; params.assetFreezeParams = assetFreezeParams; } else if (params.type === TransactionType.appl) { diff --git a/src/types/transactions/base.ts b/src/types/transactions/base.ts index 113533ab6..f69c6354d 100644 --- a/src/types/transactions/base.ts +++ b/src/types/transactions/base.ts @@ -324,9 +324,9 @@ export interface AssetTransferTransactionParams { /** * String representation of Algorand address – if provided, and if "sender" is - * the asset's revocation manager, then deduct from "revocationTarget" rather than "sender" + * the asset's revocation manager, then deduct from "assetSender" rather than "sender" */ - revocationTarget?: string | Address; + assetSender?: string | Address; /** * The Algorand address of recipient @@ -364,7 +364,7 @@ export interface AssetFreezeTransactionParams { /** * true if freezeTarget should be frozen, false if freezeTarget should be allowed to transact */ - freezeState: boolean; + frozen: boolean; } /** diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts index f0f729db8..0fe23e98d 100644 --- a/src/types/transactions/encoded.ts +++ b/src/types/transactions/encoded.ts @@ -235,7 +235,7 @@ export interface EncodedTransaction { faid?: number | bigint; /** - * freezeState + * frozen */ afrz?: boolean; @@ -245,7 +245,7 @@ export interface EncodedTransaction { fadd?: Uint8Array; /** - * assetRevocationTarget + * assetSender */ asnd?: Uint8Array; diff --git a/tests/5.Transaction.ts b/tests/5.Transaction.ts index a7e4affa0..bcd555c1a 100644 --- a/tests/5.Transaction.ts +++ b/tests/5.Transaction.ts @@ -432,7 +432,7 @@ describe('Sign', () => { assetIndex: 9999, receiver: sender, amount: 0, - revocationTarget: algosdk.ALGORAND_ZERO_ADDRESS_STRING, + assetSender: algosdk.ALGORAND_ZERO_ADDRESS_STRING, }, suggestedParams, }), @@ -814,7 +814,7 @@ describe('Sign', () => { receiver: address, amount: 100, closeRemainderTo: address, - revocationTarget: address, + assetSender: address, }, suggestedParams: { minFee: 1000, @@ -886,7 +886,7 @@ describe('Sign', () => { sender: address, assetFreezeParams: { assetIndex: 1, - freezeState: true, + frozen: true, freezeTarget: address, }, suggestedParams: { @@ -990,7 +990,7 @@ describe('Sign', () => { assetFreezeParams: { assetIndex: 9999, freezeTarget: algosdk.ALGORAND_ZERO_ADDRESS_STRING, - freezeState: true, + frozen: true, }, suggestedParams: { minFee: 1000, @@ -1028,7 +1028,7 @@ describe('Sign', () => { sender: address, assetFreezeParams: { assetIndex: 1, - freezeState: true, + frozen: true, freezeTarget: address, }, suggestedParams: { @@ -1858,7 +1858,7 @@ describe('Sign', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; const sender = addr; const receiver = addr; - const revocationTarget = addr; + const assetSender = addr; const closeRemainderTo = addr; const assetIndex = 1234; const amount = 100; @@ -1878,7 +1878,7 @@ describe('Sign', () => { sender, assetTransferParams: { receiver, - revocationTarget, + assetSender, closeRemainderTo, assetIndex, amount, @@ -1892,7 +1892,7 @@ describe('Sign', () => { sender, receiver, closeRemainderTo, - revocationTarget, + assetSender, amount, note, assetIndex, @@ -1906,7 +1906,7 @@ describe('Sign', () => { const addr = 'BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4'; const assetIndex = 1234; const freezeTarget = addr; - const freezeState = true; + const frozen = true; const note = new Uint8Array([123, 12, 200]); const rekeyTo = 'GAQVB24XEPYOPBQNJQAE4K3OLNYTRYD65ZKR3OEW5TDOOGL7MDKABXHHTM'; @@ -1923,7 +1923,7 @@ describe('Sign', () => { sender: addr, assetFreezeParams: { freezeTarget, - freezeState, + frozen, assetIndex, }, suggestedParams, @@ -1936,7 +1936,7 @@ describe('Sign', () => { note, assetIndex, freezeTarget, - freezeState, + frozen, suggestedParams, rekeyTo, } diff --git a/tests/7.AlgoSDK.ts b/tests/7.AlgoSDK.ts index 4eb6e9235..9d0404081 100644 --- a/tests/7.AlgoSDK.ts +++ b/tests/7.AlgoSDK.ts @@ -677,7 +677,7 @@ describe('Algosdk (AKA end to end)', () => { sender: addr, freezeTarget: addr, assetIndex: 1, - freezeState: true, + frozen: true, suggestedParams: { minFee: 1000, fee: 10, @@ -758,7 +758,7 @@ describe('Algosdk (AKA end to end)', () => { const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ sender: addr, receiver: addr, - revocationTarget: addr, + assetSender: addr, amount: 1, assetIndex: 1, suggestedParams: { diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 89eff5767..e4e88e19c 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -1334,7 +1334,7 @@ module.exports = function getSteps(options) { sender: freezer, freezeTarget: this.accounts[1], assetIndex: parseInt(this.assetTestFixture.index), - freezeState: false, + frozen: false, note: this.note, suggestedParams: this.params, }); @@ -1361,7 +1361,7 @@ module.exports = function getSteps(options) { sender: freezer, freezeTarget: this.accounts[1], assetIndex: parseInt(this.assetTestFixture.index), - freezeState: true, + frozen: true, note: this.note, suggestedParams: this.params, }); @@ -1386,7 +1386,7 @@ module.exports = function getSteps(options) { algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ sender: this.assetTestFixture.creator, receiver: this.assetTestFixture.creator, - revocationTarget: this.accounts[1], + assetSender: this.accounts[1], amount: parseInt(amount), note: this.note, genesisHash: this.gh, diff --git a/v2_TO_v3_MIGRATION_GUIDE.md b/v2_TO_v3_MIGRATION_GUIDE.md index 10c62aa25..410cc5075 100644 --- a/v2_TO_v3_MIGRATION_GUIDE.md +++ b/v2_TO_v3_MIGRATION_GUIDE.md @@ -133,7 +133,9 @@ These interfaces differ slightly from the v2 types. Some field names have change | " | `selectionKey` | `string \| Uint8Array` | `selectionKey` | `Uint8Array` | Base64 encoded value is no longer accepted | | " | `stateProofKey` | `string \| Uint8Array` | `stateProofKey` | `Uint8Array` | Base64 encoded value is no longer accepted | | Asset Config | `assetMetadataHash` | `string \| Uint8Array` | `assetMetadataHash` | `Uint8Array` | Base64 encoded value is no longer accepted | +| Asset Freeze | `freezeState` | `boolean` | `frozen` | `boolean` | | | Asset Transfer | `to` | `string` | `receiver` | `string \| Address` | | +| " | `revocationTarget` | `string` | `assetSender` | `string \| Address` | | Given these changes, the earlier v2 example would be equivalent to the following in v3: @@ -197,8 +199,8 @@ The following table shows the correspondence between v2 and v3 fields in the `Tr | `assetURL` | `string` | `assetConfig.assetURL` | `string` | | | `assetMetadataHash` | `Uint8Array` | `assetConfig.assetMetadataHash` | `Uint8Array` | | | `freezeAccount` | `Address` | `assetFreeze.freezeAccount` | `Address` | | -| `freezeState` | `boolean` | `assetFreeze.freezeState` | `boolean` | | -| `assetRevocationTarget` | `Address` | `assetTransfer.revocationTarget` | `Address` | | +| `freezeState` | `boolean` | `assetFreeze.frozen` | `boolean` | | +| `assetRevocationTarget` | `Address` | `assetTransfer.assetSender` | `Address` | | | `appIndex` | `number` | `applicationCall.appIndex` | `bigint` | | | `appOnComplete` | `OnApplicationComplete` | `applicationCall.onComplete` | `OnApplicationComplete` | | | `appLocalInts` | `number` | `applicationCall.numLocalInts` | `number` | | From 93a7ea99c062651b37224f7cc0d953e0b72e419a Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 7 Feb 2024 11:52:54 -0500 Subject: [PATCH 13/17] Make genesis hash optional and Uint8Array --- src/client/v2/algod/suggestedParams.ts | 12 +- src/transaction.ts | 22 +-- src/types/transactions/base.ts | 2 +- src/types/transactions/encoded.ts | 2 +- tests/10.ABI.ts | 7 +- tests/5.Transaction.ts | 200 ++++++++++++++++++------- tests/6.Multisig.ts | 8 +- tests/7.AlgoSDK.ts | 76 +++++++--- tests/8.LogicSig.ts | 8 +- tests/cucumber/steps/steps.js | 8 +- v2_TO_v3_MIGRATION_GUIDE.md | 16 +- 11 files changed, 261 insertions(+), 100 deletions(-) diff --git a/src/client/v2/algod/suggestedParams.ts b/src/client/v2/algod/suggestedParams.ts index 6c0485015..cde86875a 100644 --- a/src/client/v2/algod/suggestedParams.ts +++ b/src/client/v2/algod/suggestedParams.ts @@ -1,6 +1,14 @@ import JSONRequest from '../jsonrequest.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 + * the algod REST API. + * + * This exists because the SuggestedParams interface is purposefully general (e.g. fee can be a + * number or a bigint), and compared to that the algod API returns a narrower type. + */ export interface SuggestedParamsFromAlgod extends SuggestedParams { flatFee: boolean; fee: bigint; @@ -8,7 +16,7 @@ export interface SuggestedParamsFromAlgod extends SuggestedParams { firstValid: bigint; lastValid: bigint; genesisID: string; - genesisHash: string; + genesisHash: Uint8Array; } /** @@ -30,7 +38,7 @@ export default class SuggestedParamsRequest extends JSONRequest< firstValid: BigInt(body['last-round']), lastValid: BigInt(body['last-round']) + BigInt(1000), genesisID: body['genesis-id'], - genesisHash: body['genesis-hash'], + genesisHash: base64ToBytes(body['genesis-hash']), minFee: BigInt(body['min-fee']), }; } diff --git a/src/transaction.ts b/src/transaction.ts index 876feb4d2..d73b3dab4 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -2,7 +2,6 @@ import base32 from 'hi-base32'; import { translateBoxReferences } from './boxStorage.js'; import { Address } from './encoding/address.js'; -import { base64ToBytes, bytesToBase64 } from './encoding/binarydata.js'; import * as encoding from './encoding/encoding.js'; import * as nacl from './nacl/naclWrappers.js'; import { @@ -56,16 +55,12 @@ function getKeyregKey( let inputBytes: Uint8Array | undefined; - if (typeof input === 'string') { - inputBytes = base64ToBytes(input); - } else if (input instanceof Uint8Array) { + if (input instanceof Uint8Array) { inputBytes = input; } if (inputBytes == null || inputBytes.byteLength !== length) { - throw Error( - `${inputName} must be a ${length} byte Uint8Array or base64 string.` - ); + throw Error(`${inputName} must be a ${length} byte Uint8Array`); } return inputBytes; @@ -269,7 +264,7 @@ export class Transaction { public readonly firstValid: bigint; public readonly lastValid: bigint; public readonly genesisID?: string; - public readonly genesisHash: Uint8Array; + public readonly genesisHash?: Uint8Array; /** type-specific fields */ public readonly payment?: PaymentTransactionFields; @@ -308,10 +303,7 @@ export class Transaction { } this.genesisID = params.suggestedParams.genesisID; } - if (!params.suggestedParams.genesisHash) { - throw new Error('Genesis hash must be specified'); - } - this.genesisHash = base64ToBytes(params.suggestedParams.genesisHash); + this.genesisHash = optionalUint8Array(params.suggestedParams.genesisHash); // Fee is handled at the end const fieldsPresent: TransactionType[] = []; @@ -541,7 +533,6 @@ export class Transaction { get_obj_for_encoding(): EncodedTransaction { const forEncoding: EncodedTransaction = { type: this.type, - gh: this.genesisHash, lv: this.lastValid, }; if (!uint8ArrayIsEmpty(this.sender.publicKey)) { @@ -550,6 +541,9 @@ export class Transaction { if (this.genesisID) { forEncoding.gen = this.genesisID; } + if (this.genesisHash) { + forEncoding.gh = this.genesisHash; + } if (this.fee) { forEncoding.fee = this.fee; } @@ -769,7 +763,7 @@ export class Transaction { fee: txnForEnc.fee ?? 0, firstValid: txnForEnc.fv ?? 0, lastValid: txnForEnc.lv, - genesisHash: bytesToBase64(txnForEnc.gh), // TODO: would like to avoid encoding/decoding here + genesisHash: txnForEnc.gh, genesisID: txnForEnc.gen, }; diff --git a/src/types/transactions/base.ts b/src/types/transactions/base.ts index f69c6354d..d77053807 100644 --- a/src/types/transactions/base.ts +++ b/src/types/transactions/base.ts @@ -155,7 +155,7 @@ export interface SuggestedParams { /** * Specifies hash genesis block of network in use */ - genesisHash: string; + genesisHash?: Uint8Array; } /** diff --git a/src/types/transactions/encoded.ts b/src/types/transactions/encoded.ts index 0fe23e98d..3e240f009 100644 --- a/src/types/transactions/encoded.ts +++ b/src/types/transactions/encoded.ts @@ -137,7 +137,7 @@ export interface EncodedTransaction { /** * genesisHash */ - gh: Uint8Array; + gh?: Uint8Array; /** * lease diff --git a/tests/10.ABI.ts b/tests/10.ABI.ts index 68f309b6e..8ac1e754f 100644 --- a/tests/10.ABI.ts +++ b/tests/10.ABI.ts @@ -9,6 +9,7 @@ import { makeBasicAccountTransactionSigner, makeMultiSigAccountTransactionSigner, makePaymentTxnWithSuggestedParamsFromObject, + base64ToBytes, } from '../src'; import { ABIAddressType, @@ -461,7 +462,7 @@ describe('ABI encoding', () => { firstValid: 1, lastValid: 1001, genesisID: 'gi', - genesisHash: 'gh', + genesisHash: new Uint8Array([1, 2]), }; const foreignAcct = 'E4VCHISDQPLIZWMALIGNPK2B2TERPDMR64MZJXE3UL75MUDXZMADX5OWXM'; @@ -521,7 +522,9 @@ describe('ABI encoding', () => { // Create a transaction const suggestedParams = { - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: '', firstValid: 0, lastValid: 1000, diff --git a/tests/5.Transaction.ts b/tests/5.Transaction.ts index bcd555c1a..759aae61a 100644 --- a/tests/5.Transaction.ts +++ b/tests/5.Transaction.ts @@ -22,7 +22,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, appCallParams: { @@ -68,7 +70,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note, @@ -92,7 +96,9 @@ describe('Sign', () => { fee: 0, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, }; @@ -123,7 +129,9 @@ describe('Sign', () => { flatFee: true, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, }); @@ -146,7 +154,9 @@ describe('Sign', () => { flatFee: true, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -169,7 +179,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, note: new Uint8Array([123, 12, 200]), }; @@ -190,7 +202,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: '', }, note: new Uint8Array([123, 12, 200]), @@ -212,7 +226,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: 'abcdefg' as any, @@ -236,7 +252,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array(32), @@ -254,7 +272,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, }); @@ -279,7 +299,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, lease: new Uint8Array(32), @@ -297,7 +319,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, lease: new Uint8Array(32), @@ -330,7 +354,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, }); @@ -350,7 +376,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, }); @@ -369,7 +397,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }; @@ -523,7 +553,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -554,7 +586,9 @@ describe('Sign', () => { flatFee: true, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -584,7 +618,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -629,7 +665,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -655,7 +693,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -683,7 +723,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -711,7 +753,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -745,7 +789,9 @@ describe('Sign', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -787,7 +833,9 @@ describe('Sign', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -821,7 +869,9 @@ describe('Sign', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -864,7 +914,9 @@ describe('Sign', () => { firstValid: 322575, lastValid: 323575, genesisID: 'testnet-v1.0', - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, }); const encRep = expectedTxn.get_obj_for_encoding(); @@ -894,7 +946,9 @@ describe('Sign', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -923,7 +977,9 @@ describe('Sign', () => { fee: 10, firstValid: 1, lastValid: 1001, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -960,7 +1016,9 @@ describe('Sign', () => { fee: 10, firstValid: 1, lastValid: 1001, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -997,7 +1055,9 @@ describe('Sign', () => { fee: 10, firstValid: 1, lastValid: 1001, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -1036,7 +1096,9 @@ describe('Sign', () => { fee: 10, firstValid: 0, lastValid: 1000, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -1066,7 +1128,9 @@ describe('Sign', () => { fee: 10, firstValid: 1, lastValid: 1001, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -1103,7 +1167,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, note: new Uint8Array([123, 12, 200]), }); @@ -1132,7 +1198,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -1162,7 +1230,9 @@ describe('Sign', () => { fee: 10, firstValid: 51, lastValid: 61, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'mock-network', }, note: new Uint8Array([123, 12, 200]), @@ -1196,7 +1266,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1239,7 +1311,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1282,7 +1356,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1326,7 +1402,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1371,7 +1449,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1424,7 +1504,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1498,7 +1580,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1566,7 +1650,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1634,7 +1720,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1690,7 +1778,9 @@ describe('Sign', () => { suggestedParams: { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1744,7 +1834,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1785,7 +1877,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1828,7 +1922,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1868,7 +1964,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, @@ -1913,7 +2011,9 @@ describe('Sign', () => { const suggestedParams: algosdk.SuggestedParams = { minFee: 1000, fee: 10, - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), genesisID: 'testnet-v1.0', firstValid: 51, lastValid: 61, diff --git a/tests/6.Multisig.ts b/tests/6.Multisig.ts index b30c271c5..9b5a53d5c 100644 --- a/tests/6.Multisig.ts +++ b/tests/6.Multisig.ts @@ -68,7 +68,9 @@ describe('Multisig Functionality', () => { firstValid: 62229, lastValid: 63229, genesisID: 'devnet-v38.0', - genesisHash: '/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp0=', + genesisHash: algosdk.base64ToBytes( + '/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp0=' + ), }, }); @@ -103,7 +105,9 @@ describe('Multisig Functionality', () => { firstValid: 62229, lastValid: 63229, genesisID: 'devnet-v38.0', - genesisHash: '/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp0=', + genesisHash: algosdk.base64ToBytes( + '/rNsORAUOQDD2lVCyhg2sA/S+BlZElfNI/YEL5jINp0=' + ), }, }); diff --git a/tests/7.AlgoSDK.ts b/tests/7.AlgoSDK.ts index 9d0404081..86d70cd5b 100644 --- a/tests/7.AlgoSDK.ts +++ b/tests/7.AlgoSDK.ts @@ -37,7 +37,9 @@ describe('Algosdk (AKA end to end)', () => { const firstValid = 12466; const lastValid = 13466; const genesisID = 'devnet-v33.0'; - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; + const genesisHash = algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ); const closeRemainderTo = 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA'; const note = algosdk.base64ToBytes('6gAVR0Nsv5Y='); @@ -84,7 +86,9 @@ describe('Algosdk (AKA end to end)', () => { const firstValid = 12466; const lastValid = 13466; const genesisID = 'devnet-v33.0'; - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; + const genesisHash = algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ); const closeRemainderTo = 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA'; const note = new Uint8Array(algosdk.base64ToBytes('6gAVR0Nsv5Y=')); @@ -142,7 +146,9 @@ describe('Algosdk (AKA end to end)', () => { firstValid: 12466, lastValid: 13466, genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, }); @@ -178,7 +184,9 @@ describe('Algosdk (AKA end to end)', () => { firstValid: 12466, lastValid: 13466, genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, }); @@ -210,7 +218,9 @@ describe('Algosdk (AKA end to end)', () => { firstValid: 12466, lastValid: 13466, genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, }); const signed = algosdk.signTransaction(txn, account.sk); @@ -244,7 +254,9 @@ describe('Algosdk (AKA end to end)', () => { const firstValid = 12466; const lastValid = 13466; const genesisID = 'devnet-v33.0'; - const genesisHash = 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI='; + const genesisHash = algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ); const closeRemainderTo = 'IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA'; const note = new Uint8Array(algosdk.base64ToBytes('6gAVR0Nsv5Y=')); @@ -306,7 +318,9 @@ describe('Algosdk (AKA end to end)', () => { firstValid: 12466, lastValid: 13466, genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), fee: 4, minFee: 1000, }, @@ -343,7 +357,9 @@ describe('Algosdk (AKA end to end)', () => { firstValid: 12466, lastValid: 13466, genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), fee: 4, minFee: 1000, }, @@ -394,7 +410,9 @@ describe('Algosdk (AKA end to end)', () => { firstValid: 12466, lastValid: 13466, genesisID: 'devnet-v33.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, }); @@ -473,7 +491,9 @@ describe('Algosdk (AKA end to end)', () => { const fee = 1000; const amount = 2000; const genesisID = 'devnet-v1.0'; - const genesisHash = 'sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E'; + const genesisHash = algosdk.base64ToBytes( + 'sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E' + ); const firstValid1 = 710399; const note1 = algosdk.base64ToBytes('wRKw5cJ0CMo='); const tx1 = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ @@ -577,7 +597,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, } ); @@ -614,7 +636,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, } ); @@ -642,7 +666,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, }); const jsDec = algosdk.signTransaction(txn, sk.sk); @@ -665,7 +691,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, }); const jsDec = algosdk.signTransaction(txn, sk.sk); @@ -683,7 +711,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323576, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, }); @@ -710,7 +740,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323576, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, }); @@ -736,7 +768,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, }); @@ -766,7 +800,9 @@ describe('Algosdk (AKA end to end)', () => { fee: 10, firstValid: 322575, lastValid: 323575, - genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + genesisHash: algosdk.base64ToBytes( + 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=' + ), }, }); @@ -880,7 +916,9 @@ describe('Algosdk (AKA end to end)', () => { const amount = 2000; const firstValid = 2063137; const genesisID = 'devnet-v1.0'; - const genesisHash = 'sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E='; + const genesisHash = algosdk.base64ToBytes( + 'sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E=' + ); const note = algosdk.base64ToBytes('8xMCTuLQ810='); const txn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({ diff --git a/tests/8.LogicSig.ts b/tests/8.LogicSig.ts index 029ae3dbd..394630bfc 100644 --- a/tests/8.LogicSig.ts +++ b/tests/8.LogicSig.ts @@ -435,7 +435,9 @@ describe('signLogicSigTransaction', () => { firstValid: 972508, lastValid: 973508, genesisID: 'testnet-v31.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, note: new Uint8Array([180, 81, 121, 57, 252, 250, 210, 113]), }); @@ -508,7 +510,9 @@ describe('signLogicSigTransaction', () => { firstValid: 972508, lastValid: 973508, genesisID: 'testnet-v31.0', - genesisHash: 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=', + genesisHash: algosdk.base64ToBytes( + 'JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI=' + ), }, note: new Uint8Array([180, 81, 121, 57, 252, 250, 210, 113]), }); diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index e4e88e19c..4085d38de 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -280,7 +280,7 @@ module.exports = function getSteps(options) { this.fee = parseInt(fee); this.fv = parseInt(fv); this.lv = parseInt(lv); - this.gh = gh; + this.gh = algosdk.base64ToBytes(gh); this.receiver = receiver; if (close !== 'none') { this.close = close; @@ -2947,7 +2947,7 @@ module.exports = function getSteps(options) { Given( 'suggested transaction parameters fee {int}, flat-fee {string}, first-valid {int}, last-valid {int}, genesis-hash {string}, genesis-id {string}', - function (fee, flatFee, firstValid, lastValid, genesisHash, genesisID) { + function (fee, flatFee, firstValid, lastValid, genesisHashB64, genesisID) { assert.ok(['true', 'false'].includes(flatFee)); this.suggestedParams = { @@ -2957,7 +2957,7 @@ module.exports = function getSteps(options) { firstValid, lastValid, genesisID, - genesisHash, + genesisHash: algosdk.base64ToBytes(genesisHashB64), }; } ); @@ -3115,7 +3115,7 @@ module.exports = function getSteps(options) { } // build suggested params object const sp = { - genesisHash: genesisHashBase64, + genesisHash: algosdk.base64ToBytes(genesisHashBase64), firstValid, lastValid, fee, diff --git a/v2_TO_v3_MIGRATION_GUIDE.md b/v2_TO_v3_MIGRATION_GUIDE.md index 410cc5075..83cb82751 100644 --- a/v2_TO_v3_MIGRATION_GUIDE.md +++ b/v2_TO_v3_MIGRATION_GUIDE.md @@ -164,8 +164,8 @@ The following table shows the correspondence between v2 and v3 fields in the `Tr | `flatFee` | `boolean` | | | No longer exists | | `firstRound` | `number` | `firstValid` | `bigint` | | | `lastRound` | `number` | `lastValid` | `bigint` | | -| `genesisID` | `string` | `genesisID` | `string` | | -| `genesisHash` | `Buffer` | `genesisHash` | `Uint8Array` | | +| `genesisID` | `string` | `genesisID` | `string` | Field is now optional | +| `genesisHash` | `Buffer` | `genesisHash` | `Uint8Array` | Field is now optional | | `note` | `Uint8Array` | `note` | `Uint8Array` | | | `reKeyTo` | `Address` | `rekeyTo` | `Address` | | | `lease` | `Uint8Array` | `lease` | `Uint8Array` | | @@ -258,7 +258,17 @@ In v3, we've removed the need for `SuggestedParamsWithMinFee` and added the `min This allows the SDK to use the min fee information provided by the node, which has the potential to change over time or for different networks. -If you manually constructed `SuggestedParams` objects in v2, you will need to add a `minFee` field to those objects in v3. We expect most users to not be affected by this, since if you use Algod to get suggested parameters, it will include the `minFee` field. +In total, these changes were made to the `SuggestedParams` fields: + +| Field | v2 Field Type | v3 Field Type | Notes | +| ------------- | ------------- | ------------------ | ----------------------------------------------- | +| `minFee` | Did not exist | `number \| bigint` | Introduced new required field | +| `genesisID` | `string` | `string` | Field is now optional | +| `genesisHash` | `string` | `Uint8Array` | Field is now optional, and must be a Uint8Array | + +If you manually constructed `SuggestedParams` objects in v2, you will need to add a `minFee` field to those objects in v3, and you will need to convert your `genesisHash` string to a `Uint8Array`. Consider using the new `base64ToBytes` function to do this. + +We expect most users to not be affected by this, since if you use Algod to get suggested parameters, it will include all parameters in the correct format. ### Auction Bids From ba432ca16677646f69fd92ed7ed7d7c2ebf1b4a3 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 8 Feb 2024 13:14:40 -0500 Subject: [PATCH 14/17] Code review feedback --- examples/accounts.ts | 2 +- examples/app.ts | 2 +- examples/overview.ts | 4 +--- examples/participation.ts | 4 ++-- .../v2/algod/accountApplicationInformation.ts | 8 +++++--- src/client/v2/algod/accountAssetInformation.ts | 8 +++++--- src/client/v2/algod/accountInformation.ts | 7 +++++-- src/client/v2/algod/algod.ts | 9 +++++---- src/client/v2/algod/compile.ts | 1 - src/client/v2/algod/disassemble.ts | 1 - src/client/v2/algod/getApplicationBoxByName.ts | 1 - src/client/v2/algod/getApplicationBoxes.ts | 1 - src/client/v2/algod/getApplicationByID.ts | 1 - src/client/v2/algod/getAssetByID.ts | 1 - src/client/v2/algod/getLedgerStateDelta.ts | 1 - .../getLedgerStateDeltaForTransactionGroup.ts | 1 - ...tTransactionGroupLedgerStateDeltasForRound.ts | 1 - src/client/v2/algod/getTransactionProof.ts | 3 --- src/client/v2/algod/lightBlockHeaderProof.ts | 2 -- .../v2/algod/pendingTransactionInformation.ts | 1 - .../v2/algod/pendingTransactionsByAddress.ts | 10 +++++----- src/client/v2/algod/setBlockOffsetTimestamp.ts | 2 -- src/client/v2/algod/setSyncRound.ts | 2 -- src/client/v2/algod/stateproof.ts | 2 -- src/client/v2/algod/statusAfterBlock.ts | 1 - src/client/v2/indexer/indexer.ts | 13 +++++++------ .../v2/indexer/lookupAccountAppLocalStates.ts | 7 +++++-- src/client/v2/indexer/lookupAccountAssets.ts | 7 +++++-- src/client/v2/indexer/lookupAccountByID.ts | 7 +++++-- .../indexer/lookupAccountCreatedApplications.ts | 7 +++++-- .../v2/indexer/lookupAccountCreatedAssets.ts | 7 +++++-- .../v2/indexer/lookupAccountTransactions.ts | 7 +++++-- .../indexer/lookupApplicationBoxByIDandName.ts | 1 - src/client/v2/indexer/lookupApplicationLogs.ts | 1 - src/client/v2/indexer/lookupApplications.ts | 1 - src/client/v2/indexer/lookupAssetBalances.ts | 1 - src/client/v2/indexer/lookupAssetByID.ts | 1 - src/client/v2/indexer/lookupAssetTransactions.ts | 6 +++--- src/client/v2/indexer/lookupBlock.ts | 1 - src/client/v2/indexer/lookupTransactionByID.ts | 1 - src/client/v2/indexer/searchAccounts.ts | 5 +++-- .../v2/indexer/searchForApplicationBoxes.ts | 1 - src/client/v2/indexer/searchForApplications.ts | 5 +++-- src/client/v2/indexer/searchForAssets.ts | 5 +++-- src/client/v2/indexer/searchForTransactions.ts | 5 +++-- src/composer.ts | 13 ++----------- src/dryrun.ts | 2 +- tests/cucumber/steps/steps.js | 16 ++++++++++++---- 48 files changed, 97 insertions(+), 99 deletions(-) diff --git a/examples/accounts.ts b/examples/accounts.ts index d1adb7384..30f959c88 100644 --- a/examples/accounts.ts +++ b/examples/accounts.ts @@ -98,7 +98,7 @@ async function main() { await client.sendRawTransaction(rekeyTxn.signTxn(acct1.privateKey)).do(); await algosdk.waitForConfirmation(client, rekeyTxn.txID(), 3); - const acctInfo = await client.accountInformation(acct1.addr.toString()).do(); + const acctInfo = await client.accountInformation(acct1.addr).do(); console.log( `Account Info: ${JSON.stringify(acctInfo)} Auth Addr: ${ diff --git a/examples/app.ts b/examples/app.ts index f9408951d..14999f0ef 100644 --- a/examples/app.ts +++ b/examples/app.ts @@ -143,7 +143,7 @@ async function main() { console.log(`Decoded global state - ${globalKey}: ${globalValue}`); const accountAppInfo = await algodClient - .accountApplicationInformation(caller.addr.toString(), appId) + .accountApplicationInformation(caller.addr, appId) .do(); const localState = accountAppInfo.appLocalState.keyValue[0]; diff --git a/examples/overview.ts b/examples/overview.ts index b05fa2557..a94d863ca 100644 --- a/examples/overview.ts +++ b/examples/overview.ts @@ -45,9 +45,7 @@ async function main() { // example: TRANSACTION_PAYMENT_SUBMIT // example: ALGOD_FETCH_ACCOUNT_INFO - const acctInfo = await algodClient - .accountInformation(acct.addr.toString()) - .do(); + const acctInfo = await algodClient.accountInformation(acct.addr).do(); console.log(`Account balance: ${acctInfo.amount} microAlgos`); // example: ALGOD_FETCH_ACCOUNT_INFO } diff --git a/examples/participation.ts b/examples/participation.ts index 6bf20143e..0bd36f572 100644 --- a/examples/participation.ts +++ b/examples/participation.ts @@ -23,8 +23,8 @@ async function main() { 'RpUpNWfZMjZ1zOOjv3MF2tjO714jsBt0GKnNsw0ihJ4HSZwci+d9zvUi3i67LwFUJgjQ5Dz4zZgHgGduElnmSA==' ); - // sets up keys for 100000 rounds - const numRounds = BigInt(100000); + // sets up keys for 100,000 rounds + const numRounds = BigInt(100_000); // dilution default is sqrt num rounds const keyDilution = BigInt(Math.floor(Math.sqrt(Number(numRounds)))); diff --git a/src/client/v2/algod/accountApplicationInformation.ts b/src/client/v2/algod/accountApplicationInformation.ts index 324622790..676c19177 100644 --- a/src/client/v2/algod/accountApplicationInformation.ts +++ b/src/client/v2/algod/accountApplicationInformation.ts @@ -2,20 +2,22 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; import { AccountApplicationResponse } from './models/types.js'; +import { Address } from '../../../encoding/address.js'; export default class AccountApplicationInformation extends JSONRequest< AccountApplicationResponse, Record > { + private account: string; + constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string, + account: string | Address, private applicationID: number ) { super(c, intDecoding); - this.account = account; - this.applicationID = applicationID; + this.account = account.toString(); } path() { diff --git a/src/client/v2/algod/accountAssetInformation.ts b/src/client/v2/algod/accountAssetInformation.ts index 0d20fe739..2d6cad527 100644 --- a/src/client/v2/algod/accountAssetInformation.ts +++ b/src/client/v2/algod/accountAssetInformation.ts @@ -2,20 +2,22 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; import { AccountAssetResponse } from './models/types.js'; +import { Address } from '../../../encoding/address.js'; export default class AccountAssetInformation extends JSONRequest< AccountAssetResponse, Record > { + private account: string; + constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string, + account: string | Address, private assetID: number ) { super(c, intDecoding); - this.account = account; - this.assetID = assetID; + this.account = account.toString(); } path() { diff --git a/src/client/v2/algod/accountInformation.ts b/src/client/v2/algod/accountInformation.ts index 536a0bf48..937186e37 100644 --- a/src/client/v2/algod/accountInformation.ts +++ b/src/client/v2/algod/accountInformation.ts @@ -2,18 +2,21 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; import { Account } from './models/types.js'; +import { Address } from '../../../encoding/address.js'; export default class AccountInformation extends JSONRequest< Account, Record > { + private account: string; + constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string + account: string | Address ) { super(c, intDecoding); - this.account = account; + this.account = account.toString(); } path() { diff --git a/src/client/v2/algod/algod.ts b/src/client/v2/algod/algod.ts index de1c642c4..0370572b6 100644 --- a/src/client/v2/algod/algod.ts +++ b/src/client/v2/algod/algod.ts @@ -44,6 +44,7 @@ import UnsetSyncRound from './unsetSyncRound.js'; import GetLedgerStateDeltaForTransactionGroup from './getLedgerStateDeltaForTransactionGroup.js'; import GetLedgerStateDelta from './getLedgerStateDelta.js'; import GetTransactionGroupLedgerStateDeltasForRound from './getTransactionGroupLedgerStateDeltasForRound.js'; +import { Address } from '../../../encoding/address.js'; /** * Algod client connects an application to the Algorand blockchain. The algod client requires a valid algod REST endpoint IP address and algod token from an Algorand node that is connected to the network you plan to interact with. @@ -153,7 +154,7 @@ export class AlgodClient extends ServiceClient { * @param account - The address of the account to look up. * @category GET */ - accountInformation(account: string) { + accountInformation(account: string | Address) { return new AccountInformation(this.c, this.intDecoding, account); } @@ -172,7 +173,7 @@ export class AlgodClient extends ServiceClient { * @param index - The asset ID to look up. * @category GET */ - accountAssetInformation(account: string, index: number) { + accountAssetInformation(account: string | Address, index: number) { return new AccountAssetInformation( this.c, this.intDecoding, @@ -196,7 +197,7 @@ export class AlgodClient extends ServiceClient { * @param index - The application ID to look up. * @category GET */ - accountApplicationInformation(account: string, index: number) { + accountApplicationInformation(account: string | Address, index: number) { return new AccountApplicationInformation( this.c, this.intDecoding, @@ -332,7 +333,7 @@ export class AlgodClient extends ServiceClient { * @param address - The address of the sender. * @category GET */ - pendingTransactionByAddress(address: string) { + pendingTransactionByAddress(address: string | Address) { return new PendingTransactionsByAddress(this.c, address); } diff --git a/src/client/v2/algod/compile.ts b/src/client/v2/algod/compile.ts index ccb62c6dc..dc004d8a4 100644 --- a/src/client/v2/algod/compile.ts +++ b/src/client/v2/algod/compile.ts @@ -28,7 +28,6 @@ export default class Compile extends JSONRequest< private source: string | Uint8Array ) { super(c); - this.source = source; } // eslint-disable-next-line class-methods-use-this diff --git a/src/client/v2/algod/disassemble.ts b/src/client/v2/algod/disassemble.ts index 1fc29b942..c9eb53f5f 100644 --- a/src/client/v2/algod/disassemble.ts +++ b/src/client/v2/algod/disassemble.ts @@ -28,7 +28,6 @@ export default class Disassemble extends JSONRequest< private source: string | Uint8Array ) { super(c); - this.source = source; } // eslint-disable-next-line class-methods-use-this diff --git a/src/client/v2/algod/getApplicationBoxByName.ts b/src/client/v2/algod/getApplicationBoxByName.ts index 4e3b92a9c..7e139f8d4 100644 --- a/src/client/v2/algod/getApplicationBoxByName.ts +++ b/src/client/v2/algod/getApplicationBoxByName.ts @@ -30,7 +30,6 @@ export default class GetApplicationBoxByName extends JSONRequest< name: Uint8Array ) { super(c, intDecoding); - this.index = index; // Encode name in base64 format and append the encoding prefix. const encodedName = bytesToBase64(name); this.query.name = encodeURI(`b64:${encodedName}`); diff --git a/src/client/v2/algod/getApplicationBoxes.ts b/src/client/v2/algod/getApplicationBoxes.ts index e8722383e..14bdcea3b 100644 --- a/src/client/v2/algod/getApplicationBoxes.ts +++ b/src/client/v2/algod/getApplicationBoxes.ts @@ -27,7 +27,6 @@ export default class GetApplicationBoxes extends JSONRequest< private index: number ) { super(c, intDecoding); - this.index = index; this.query.max = 0; } diff --git a/src/client/v2/algod/getApplicationByID.ts b/src/client/v2/algod/getApplicationByID.ts index 858719120..c9bbc1f9d 100644 --- a/src/client/v2/algod/getApplicationByID.ts +++ b/src/client/v2/algod/getApplicationByID.ts @@ -13,7 +13,6 @@ export default class GetApplicationByID extends JSONRequest< private index: number | bigint ) { super(c, intDecoding); - this.index = index; } path() { diff --git a/src/client/v2/algod/getAssetByID.ts b/src/client/v2/algod/getAssetByID.ts index ed4f1b3df..ffb89f6c7 100644 --- a/src/client/v2/algod/getAssetByID.ts +++ b/src/client/v2/algod/getAssetByID.ts @@ -13,7 +13,6 @@ export default class GetAssetByID extends JSONRequest< private index: number | bigint ) { super(c, intDecoding); - this.index = index; } path() { diff --git a/src/client/v2/algod/getLedgerStateDelta.ts b/src/client/v2/algod/getLedgerStateDelta.ts index e169ee1a5..168eb69f1 100644 --- a/src/client/v2/algod/getLedgerStateDelta.ts +++ b/src/client/v2/algod/getLedgerStateDelta.ts @@ -9,7 +9,6 @@ export default class GetLedgerStateDelta extends JSONRequest { private round: number ) { super(c, intDecoding); - this.round = round; this.query = { format: 'json' }; } diff --git a/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts b/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts index 2c9a13b0c..3a4e404cd 100644 --- a/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts +++ b/src/client/v2/algod/getLedgerStateDeltaForTransactionGroup.ts @@ -9,7 +9,6 @@ export default class GetLedgerStateDeltaForTransactionGroup extends JSONRequest private id: string ) { super(c, intDecoding); - this.id = id; this.query = { format: 'json' }; } diff --git a/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts b/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts index d3c4da6b3..2563b77a7 100644 --- a/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts +++ b/src/client/v2/algod/getTransactionGroupLedgerStateDeltasForRound.ts @@ -13,7 +13,6 @@ export default class GetTransactionGroupLedgerStateDeltasForRound extends JSONRe private round: number ) { super(c, intDecoding); - this.round = round; this.query = { format: 'json' }; } diff --git a/src/client/v2/algod/getTransactionProof.ts b/src/client/v2/algod/getTransactionProof.ts index 3b0869550..49122390a 100644 --- a/src/client/v2/algod/getTransactionProof.ts +++ b/src/client/v2/algod/getTransactionProof.ts @@ -14,9 +14,6 @@ export default class GetTransactionProof extends JSONRequest< private txID: string ) { super(c, intDecoding); - - this.round = round; - this.txID = txID; } path() { diff --git a/src/client/v2/algod/lightBlockHeaderProof.ts b/src/client/v2/algod/lightBlockHeaderProof.ts index 1900d287b..723f6ee67 100644 --- a/src/client/v2/algod/lightBlockHeaderProof.ts +++ b/src/client/v2/algod/lightBlockHeaderProof.ts @@ -13,8 +13,6 @@ export default class LightBlockHeaderProof extends JSONRequest< private round: number ) { super(c, intDecoding); - - this.round = round; } path() { diff --git a/src/client/v2/algod/pendingTransactionInformation.ts b/src/client/v2/algod/pendingTransactionInformation.ts index b7465ddf0..f455056fd 100644 --- a/src/client/v2/algod/pendingTransactionInformation.ts +++ b/src/client/v2/algod/pendingTransactionInformation.ts @@ -15,7 +15,6 @@ export default class PendingTransactionInformation extends JSONRequest< private txid: string ) { super(c); - this.txid = txid; this.query.format = 'msgpack'; } diff --git a/src/client/v2/algod/pendingTransactionsByAddress.ts b/src/client/v2/algod/pendingTransactionsByAddress.ts index 31a55557d..477ff2fec 100644 --- a/src/client/v2/algod/pendingTransactionsByAddress.ts +++ b/src/client/v2/algod/pendingTransactionsByAddress.ts @@ -2,6 +2,7 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import * as encoding 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. @@ -10,12 +11,11 @@ export default class PendingTransactionsByAddress extends JSONRequest< PendingTransactionsResponse, Uint8Array > { - constructor( - c: HTTPClient, - private address: string - ) { + private address: string; + + constructor(c: HTTPClient, address: string | Address) { super(c); - this.address = address; + this.address = address.toString(); this.query.format = 'msgpack'; } diff --git a/src/client/v2/algod/setBlockOffsetTimestamp.ts b/src/client/v2/algod/setBlockOffsetTimestamp.ts index 8bf428ea4..50b99f9cf 100644 --- a/src/client/v2/algod/setBlockOffsetTimestamp.ts +++ b/src/client/v2/algod/setBlockOffsetTimestamp.ts @@ -9,8 +9,6 @@ export default class SetBlockOffsetTimestamp extends JSONRequest { private offset: number ) { super(c, intDecoding); - - this.offset = offset; } path() { diff --git a/src/client/v2/algod/setSyncRound.ts b/src/client/v2/algod/setSyncRound.ts index e87b317a6..419987fad 100644 --- a/src/client/v2/algod/setSyncRound.ts +++ b/src/client/v2/algod/setSyncRound.ts @@ -9,8 +9,6 @@ export default class SetSyncRound extends JSONRequest { private round: number ) { super(c, intDecoding); - - this.round = round; } path() { diff --git a/src/client/v2/algod/stateproof.ts b/src/client/v2/algod/stateproof.ts index b99ee2d97..9ea048d4d 100644 --- a/src/client/v2/algod/stateproof.ts +++ b/src/client/v2/algod/stateproof.ts @@ -10,8 +10,6 @@ export default class StateProof extends JSONRequest> { private round: number ) { super(c, intDecoding); - - this.round = round; } path() { diff --git a/src/client/v2/algod/statusAfterBlock.ts b/src/client/v2/algod/statusAfterBlock.ts index 03570acfa..ed74730d5 100644 --- a/src/client/v2/algod/statusAfterBlock.ts +++ b/src/client/v2/algod/statusAfterBlock.ts @@ -14,7 +14,6 @@ export default class StatusAfterBlock extends JSONRequest< ) { super(c, intDecoding); if (!Number.isInteger(round)) throw Error('round should be an integer'); - this.round = round; } path() { diff --git a/src/client/v2/indexer/indexer.ts b/src/client/v2/indexer/indexer.ts index 614ba3c2c..e99522d36 100644 --- a/src/client/v2/indexer/indexer.ts +++ b/src/client/v2/indexer/indexer.ts @@ -24,6 +24,7 @@ import { CustomTokenHeader, IndexerTokenHeader, } from '../../urlTokenBaseHTTPClient.js'; +import { Address } from '../../../encoding/address.js'; /** * The Indexer provides a REST API interface of API calls to support searching the Algorand Blockchain. @@ -137,7 +138,7 @@ export class IndexerClient extends ServiceClient { * @param account - The address of the account. * @category GET */ - lookupAccountTransactions(account: string) { + lookupAccountTransactions(account: string | Address) { return new LookupAccountTransactions(this.c, this.intDecoding, account); } @@ -188,7 +189,7 @@ export class IndexerClient extends ServiceClient { * @param account - The address of the account to look up. * @category GET */ - lookupAccountByID(account: string) { + lookupAccountByID(account: string | Address) { return new LookupAccountByID(this.c, this.intDecoding, account); } @@ -205,7 +206,7 @@ export class IndexerClient extends ServiceClient { * @param account - The address of the account to look up. * @category GET */ - lookupAccountAssets(account: string) { + lookupAccountAssets(account: string | Address) { return new LookupAccountAssets(this.c, this.intDecoding, account); } @@ -222,7 +223,7 @@ export class IndexerClient extends ServiceClient { * @param account - The address of the account to look up. * @category GET */ - lookupAccountCreatedAssets(account: string) { + lookupAccountCreatedAssets(account: string | Address) { return new LookupAccountCreatedAssets(this.c, this.intDecoding, account); } @@ -239,7 +240,7 @@ export class IndexerClient extends ServiceClient { * @param account - The address of the account to look up. * @category GET */ - lookupAccountAppLocalStates(account: string) { + lookupAccountAppLocalStates(account: string | Address) { return new LookupAccountAppLocalStates(this.c, this.intDecoding, account); } @@ -256,7 +257,7 @@ export class IndexerClient extends ServiceClient { * @param account - The address of the account to look up. * @category GET */ - lookupAccountCreatedApplications(account: string) { + lookupAccountCreatedApplications(account: string | Address) { return new LookupAccountCreatedApplications( this.c, this.intDecoding, diff --git a/src/client/v2/indexer/lookupAccountAppLocalStates.ts b/src/client/v2/indexer/lookupAccountAppLocalStates.ts index 71aa2b096..7c916edbc 100644 --- a/src/client/v2/indexer/lookupAccountAppLocalStates.ts +++ b/src/client/v2/indexer/lookupAccountAppLocalStates.ts @@ -1,8 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; +import { Address } from '../../../encoding/address.js'; export default class LookupAccountAppLocalStates extends JSONRequest { + private account: string | Address; + /** * Returns application local state about the given account. * @@ -19,10 +22,10 @@ export default class LookupAccountAppLocalStates extends JSONRequest { constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string + account: string | Address ) { super(c, intDecoding); - this.account = account; + this.account = account.toString(); } /** diff --git a/src/client/v2/indexer/lookupAccountAssets.ts b/src/client/v2/indexer/lookupAccountAssets.ts index a39d28e18..3ef3a28c0 100644 --- a/src/client/v2/indexer/lookupAccountAssets.ts +++ b/src/client/v2/indexer/lookupAccountAssets.ts @@ -1,8 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; +import { Address } from '../../../encoding/address.js'; export default class LookupAccountAssets extends JSONRequest { + private account: string; + /** * Returns asset about the given account. * @@ -19,10 +22,10 @@ export default class LookupAccountAssets extends JSONRequest { constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string + account: string | Address ) { super(c, intDecoding); - this.account = account; + this.account = account.toString(); } /** diff --git a/src/client/v2/indexer/lookupAccountByID.ts b/src/client/v2/indexer/lookupAccountByID.ts index 06dbe0188..e821c028c 100644 --- a/src/client/v2/indexer/lookupAccountByID.ts +++ b/src/client/v2/indexer/lookupAccountByID.ts @@ -1,8 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; +import { Address } from '../../../encoding/address.js'; export default class LookupAccountByID extends JSONRequest { + private account: string; + /** * Returns information about the given account. * @@ -19,10 +22,10 @@ export default class LookupAccountByID extends JSONRequest { constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string + account: string | Address ) { super(c, intDecoding); - this.account = account; + this.account = account.toString(); } /** diff --git a/src/client/v2/indexer/lookupAccountCreatedApplications.ts b/src/client/v2/indexer/lookupAccountCreatedApplications.ts index 39ce4b146..2929ab8be 100644 --- a/src/client/v2/indexer/lookupAccountCreatedApplications.ts +++ b/src/client/v2/indexer/lookupAccountCreatedApplications.ts @@ -1,8 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; +import { Address } from '../../../encoding/address.js'; export default class LookupAccountCreatedApplications extends JSONRequest { + private account: string; + /** * Returns application information created by the given account. * @@ -19,10 +22,10 @@ export default class LookupAccountCreatedApplications extends JSONRequest { constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string + account: string | Address ) { super(c, intDecoding); - this.account = account; + this.account = account.toString(); } /** diff --git a/src/client/v2/indexer/lookupAccountCreatedAssets.ts b/src/client/v2/indexer/lookupAccountCreatedAssets.ts index b9e27f717..952f5c186 100644 --- a/src/client/v2/indexer/lookupAccountCreatedAssets.ts +++ b/src/client/v2/indexer/lookupAccountCreatedAssets.ts @@ -1,8 +1,11 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; +import { Address } from '../../../encoding/address.js'; export default class LookupAccountCreatedAssets extends JSONRequest { + private account: string; + /** * Returns asset information created by the given account. * @@ -19,10 +22,10 @@ export default class LookupAccountCreatedAssets extends JSONRequest { constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string + account: string | Address ) { super(c, intDecoding); - this.account = account; + this.account = account.toString(); } /** diff --git a/src/client/v2/indexer/lookupAccountTransactions.ts b/src/client/v2/indexer/lookupAccountTransactions.ts index d1b07de36..5c8ddf1c9 100644 --- a/src/client/v2/indexer/lookupAccountTransactions.ts +++ b/src/client/v2/indexer/lookupAccountTransactions.ts @@ -2,6 +2,7 @@ import { bytesToBase64 } from '../../../encoding/binarydata.js'; import IntDecoding from '../../../types/intDecoding.js'; import { HTTPClient } from '../../client.js'; import JSONRequest from '../jsonrequest.js'; +import { Address } from '../../../encoding/address.js'; /** * Accept base64 string or Uint8Array and output base64 string @@ -16,6 +17,8 @@ export function base64StringFunnel(data: Uint8Array | string) { } export default class LookupAccountTransactions extends JSONRequest { + private account: string; + /** * Returns transactions relating to the given account. * @@ -31,10 +34,10 @@ export default class LookupAccountTransactions extends JSONRequest { constructor( c: HTTPClient, intDecoding: IntDecoding, - private account: string + account: string | Address ) { super(c, intDecoding); - this.account = account; + this.account = account.toString(); } /** diff --git a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts index 8850b6c81..e9f68e4d6 100644 --- a/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts +++ b/src/client/v2/indexer/lookupApplicationBoxByIDandName.ts @@ -31,7 +31,6 @@ export default class LookupApplicationBoxByIDandName extends JSONRequest< boxName: Uint8Array ) { super(c, intDecoding); - this.index = index; // Encode query in base64 format and append the encoding prefix. const encodedName = bytesToBase64(boxName); this.query.name = encodeURI(`b64:${encodedName}`); diff --git a/src/client/v2/indexer/lookupApplicationLogs.ts b/src/client/v2/indexer/lookupApplicationLogs.ts index f4c241614..8c2c5cea2 100644 --- a/src/client/v2/indexer/lookupApplicationLogs.ts +++ b/src/client/v2/indexer/lookupApplicationLogs.ts @@ -22,7 +22,6 @@ export default class LookupApplicationLogs extends JSONRequest { private appID: number ) { super(c, intDecoding); - this.appID = appID; } /** diff --git a/src/client/v2/indexer/lookupApplications.ts b/src/client/v2/indexer/lookupApplications.ts index d400b5afc..0827bcb66 100644 --- a/src/client/v2/indexer/lookupApplications.ts +++ b/src/client/v2/indexer/lookupApplications.ts @@ -22,7 +22,6 @@ export default class LookupApplications extends JSONRequest { private index: number ) { super(c, intDecoding); - this.index = index; } /** diff --git a/src/client/v2/indexer/lookupAssetBalances.ts b/src/client/v2/indexer/lookupAssetBalances.ts index fcb6a817c..5c77508cf 100644 --- a/src/client/v2/indexer/lookupAssetBalances.ts +++ b/src/client/v2/indexer/lookupAssetBalances.ts @@ -21,7 +21,6 @@ export default class LookupAssetBalances extends JSONRequest { private index: number ) { super(c, intDecoding); - this.index = index; } /** diff --git a/src/client/v2/indexer/lookupAssetByID.ts b/src/client/v2/indexer/lookupAssetByID.ts index 796bf9715..c171cfd8f 100644 --- a/src/client/v2/indexer/lookupAssetByID.ts +++ b/src/client/v2/indexer/lookupAssetByID.ts @@ -21,7 +21,6 @@ export default class LookupAssetByID extends JSONRequest { private index: number ) { super(c, intDecoding); - this.index = index; } /** diff --git a/src/client/v2/indexer/lookupAssetTransactions.ts b/src/client/v2/indexer/lookupAssetTransactions.ts index bc7d90a55..daa883c27 100644 --- a/src/client/v2/indexer/lookupAssetTransactions.ts +++ b/src/client/v2/indexer/lookupAssetTransactions.ts @@ -2,6 +2,7 @@ import JSONRequest from '../jsonrequest.js'; import { HTTPClient } from '../../client.js'; import IntDecoding from '../../../types/intDecoding.js'; import { base64StringFunnel } from './lookupAccountTransactions.js'; +import { Address } from '../../../encoding/address.js'; export default class LookupAssetTransactions extends JSONRequest { /** @@ -22,7 +23,6 @@ export default class LookupAssetTransactions extends JSONRequest { private index: number ) { super(c, intDecoding); - this.index = index; } /** @@ -325,8 +325,8 @@ export default class LookupAssetTransactions extends JSONRequest { * @param address * @category query */ - address(address: string) { - this.query.address = address; + address(address: string | Address) { + this.query.address = address.toString(); return this; } diff --git a/src/client/v2/indexer/lookupBlock.ts b/src/client/v2/indexer/lookupBlock.ts index 7edb412e3..21c967e50 100644 --- a/src/client/v2/indexer/lookupBlock.ts +++ b/src/client/v2/indexer/lookupBlock.ts @@ -22,7 +22,6 @@ export default class LookupBlock extends JSONRequest { private round: number ) { super(c, intDecoding); - this.round = round; } /** diff --git a/src/client/v2/indexer/lookupTransactionByID.ts b/src/client/v2/indexer/lookupTransactionByID.ts index 82259c673..642f5b710 100644 --- a/src/client/v2/indexer/lookupTransactionByID.ts +++ b/src/client/v2/indexer/lookupTransactionByID.ts @@ -22,7 +22,6 @@ export default class LookupTransactionByID extends JSONRequest { private txID: string ) { super(c, intDecoding); - this.txID = txID; } /** diff --git a/src/client/v2/indexer/searchAccounts.ts b/src/client/v2/indexer/searchAccounts.ts index afb410f32..956e53a3c 100644 --- a/src/client/v2/indexer/searchAccounts.ts +++ b/src/client/v2/indexer/searchAccounts.ts @@ -1,4 +1,5 @@ import JSONRequest from '../jsonrequest.js'; +import { Address } from '../../../encoding/address.js'; /** * Returns information about indexed accounts. @@ -186,8 +187,8 @@ export default class SearchAccounts extends JSONRequest { * * @param authAddr */ - authAddr(authAddr: string) { - this.query['auth-addr'] = authAddr; + authAddr(authAddr: string | Address) { + this.query['auth-addr'] = authAddr.toString(); return this; } diff --git a/src/client/v2/indexer/searchForApplicationBoxes.ts b/src/client/v2/indexer/searchForApplicationBoxes.ts index e65be92e1..4eee8de22 100644 --- a/src/client/v2/indexer/searchForApplicationBoxes.ts +++ b/src/client/v2/indexer/searchForApplicationBoxes.ts @@ -39,7 +39,6 @@ export default class SearchForApplicationBoxes extends JSONRequest< private index: number ) { super(c, intDecoding); - this.index = index; } /** diff --git a/src/client/v2/indexer/searchForApplications.ts b/src/client/v2/indexer/searchForApplications.ts index a082fc29a..b971a0673 100644 --- a/src/client/v2/indexer/searchForApplications.ts +++ b/src/client/v2/indexer/searchForApplications.ts @@ -1,4 +1,5 @@ import JSONRequest from '../jsonrequest.js'; +import { Address } from '../../../encoding/address.js'; /** * Returns information about indexed applications. @@ -54,8 +55,8 @@ export default class SearchForApplications extends JSONRequest { * @param creator * @category query */ - creator(creator: string) { - this.query.creator = creator; + creator(creator: string | Address) { + this.query.creator = creator.toString(); return this; } diff --git a/src/client/v2/indexer/searchForAssets.ts b/src/client/v2/indexer/searchForAssets.ts index 271ec0621..c232db531 100644 --- a/src/client/v2/indexer/searchForAssets.ts +++ b/src/client/v2/indexer/searchForAssets.ts @@ -1,4 +1,5 @@ import JSONRequest from '../jsonrequest.js'; +import { Address } from '../../../encoding/address.js'; /** * Returns information about indexed assets. @@ -55,8 +56,8 @@ export default class SearchForAssets extends JSONRequest { * @param creator * @category query */ - creator(creator: string) { - this.query.creator = creator; + creator(creator: string | Address) { + this.query.creator = creator.toString(); return this; } diff --git a/src/client/v2/indexer/searchForTransactions.ts b/src/client/v2/indexer/searchForTransactions.ts index 832bdc27e..d96ba3945 100644 --- a/src/client/v2/indexer/searchForTransactions.ts +++ b/src/client/v2/indexer/searchForTransactions.ts @@ -1,5 +1,6 @@ import JSONRequest from '../jsonrequest.js'; import { base64StringFunnel } from './lookupAccountTransactions.js'; +import { Address } from '../../../encoding/address.js'; /** * Returns information about indexed transactions. @@ -279,8 +280,8 @@ export default class SearchForTransactions extends JSONRequest { * @param address * @category query */ - address(address: string) { - this.query.address = address; + address(address: string | Address) { + this.query.address = address.toString(); return this; } diff --git a/src/composer.ts b/src/composer.ts index 854988d9f..12f12dad4 100644 --- a/src/composer.ts +++ b/src/composer.ts @@ -391,14 +391,7 @@ export class AtomicTransactionComposer { const resolvedRefIndexes: number[] = []; // Converting addresses to string form for easier comparison const foreignAccounts: string[] = - appAccounts == null - ? [] - : appAccounts.map((addr) => { - if (typeof addr === 'string') { - return addr; - } - return addr.toString(); - }); + appAccounts == null ? [] : appAccounts.map((addr) => addr.toString()); const foreignApps: bigint[] = appForeignApps == null ? [] : appForeignApps.map(ensureUint64); const foreignAssets: bigint[] = @@ -412,12 +405,10 @@ export class AtomicTransactionComposer { case ABIReferenceType.account: { const addressType = new ABIAddressType(); const address = addressType.decode(addressType.encode(refValue)); - const senderString = - typeof sender === 'string' ? sender : sender.toString(); resolved = populateForeignArray( address, foreignAccounts, - senderString + sender.toString() ); break; } diff --git a/src/dryrun.ts b/src/dryrun.ts index 18ee02253..04f971ae4 100644 --- a/src/dryrun.ts +++ b/src/dryrun.ts @@ -125,7 +125,7 @@ export async function createDryrun({ await Promise.all(appPromises); const acctPromises = []; - for (const acct of [...new Set(accts)]) { + for (const acct of new Set(accts)) { acctPromises.push( client .accountInformation(acct) diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 4085d38de..a1f06889e 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -1817,7 +1817,9 @@ module.exports = function getSteps(options) { When('we make any Pending Transactions By Address call', async function () { anyPendingTransactionsByAddressResponse = await doOrDoRaw( - this.v2Client.pendingTransactionByAddress() + this.v2Client.pendingTransactionByAddress( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' + ) ); }); @@ -1896,7 +1898,9 @@ module.exports = function getSteps(options) { When('we make any Account Information call', async function () { anyAccountInformationResponse = await doOrDoRaw( - this.v2Client.accountInformation() + this.v2Client.accountInformation( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' + ) ); }); @@ -2508,7 +2512,9 @@ module.exports = function getSteps(options) { When('we make any LookupAccountTransactions call', async function () { anyLookupAccountTransactionsResponse = await this.indexerClient - .lookupAccountTransactions() + .lookupAccountTransactions( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' + ) .do(); }); @@ -2553,7 +2559,9 @@ module.exports = function getSteps(options) { When('we make any LookupAccountByID call', async function () { anyLookupAccountByIDResponse = await this.indexerClient - .lookupAccountByID() + .lookupAccountByID( + 'MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI' + ) .do(); }); From 9d205bd3e7be1c4d97190446b0b746f0d6f2296c Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 8 Feb 2024 13:57:31 -0500 Subject: [PATCH 15/17] Add unit tests for new utils functions --- tests/4.Utils.ts | 380 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) diff --git a/tests/4.Utils.ts b/tests/4.Utils.ts index a2b589d4b..f478c0a1a 100644 --- a/tests/4.Utils.ts +++ b/tests/4.Utils.ts @@ -34,6 +34,386 @@ describe('utils', () => { assert.deepStrictEqual(expected, actual); }); }); + + describe('ensureSafeInteger', () => { + it('should error on undefined', () => { + assert.throws( + () => utils.ensureSafeInteger(undefined), + new Error('Value is undefined') + ); + }); + + it('should accept bigints in range', () => { + assert.strictEqual( + utils.ensureSafeInteger(BigInt(Number.MIN_SAFE_INTEGER)), + Number.MIN_SAFE_INTEGER + ); + assert.strictEqual(utils.ensureSafeInteger(BigInt(-100)), -100); + assert.strictEqual(utils.ensureSafeInteger(BigInt(0)), 0); + assert.strictEqual(utils.ensureSafeInteger(BigInt(7)), 7); + assert.strictEqual( + utils.ensureSafeInteger(BigInt(Number.MAX_SAFE_INTEGER)), + Number.MAX_SAFE_INTEGER + ); + }); + + it('should error on bigints outside of range', () => { + assert.throws( + () => + utils.ensureSafeInteger(BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1)), + new Error('BigInt value -9007199254740992 is not a safe integer') + ); + assert.throws( + () => + utils.ensureSafeInteger(BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)), + new Error('BigInt value 9007199254740992 is not a safe integer') + ); + }); + + it('should accept safe integers', () => { + assert.strictEqual( + utils.ensureSafeInteger(Number.MIN_SAFE_INTEGER), + Number.MIN_SAFE_INTEGER + ); + assert.strictEqual(utils.ensureSafeInteger(-100), -100); + assert.strictEqual(utils.ensureSafeInteger(0), 0); + assert.strictEqual(utils.ensureSafeInteger(7), 7); + assert.strictEqual( + utils.ensureSafeInteger(Number.MAX_SAFE_INTEGER), + Number.MAX_SAFE_INTEGER + ); + }); + + it('should error on unsafe integers', () => { + assert.throws( + () => utils.ensureSafeInteger(0.5), + new Error('Value 0.5 is not a safe integer') + ); + assert.throws( + () => utils.ensureSafeInteger(Number.MIN_SAFE_INTEGER - 1), + new Error('Value -9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureSafeInteger(Number.MAX_SAFE_INTEGER + 1), + new Error('Value 9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureSafeInteger(NaN), + new Error('Value NaN is not a safe integer') + ); + }); + + it('should error on unexpected types', () => { + assert.throws( + () => utils.ensureSafeInteger('0'), + new Error('Unexpected type string, 0') + ); + + assert.throws( + () => utils.ensureSafeInteger(true), + new Error('Unexpected type boolean, true') + ); + + assert.throws( + () => utils.ensureSafeInteger(null), + new Error('Unexpected type object, null') + ); + }); + }); + + describe('ensureSafeUnsignedInteger', () => { + it('should error on undefined', () => { + assert.throws( + () => utils.ensureSafeUnsignedInteger(undefined), + new Error('Value is undefined') + ); + }); + + it('should accept positive bigints in range', () => { + assert.strictEqual(utils.ensureSafeUnsignedInteger(BigInt(0)), 0); + assert.strictEqual(utils.ensureSafeUnsignedInteger(BigInt(7)), 7); + assert.strictEqual( + utils.ensureSafeUnsignedInteger(BigInt(Number.MAX_SAFE_INTEGER)), + Number.MAX_SAFE_INTEGER + ); + }); + + it('should error on negative bigints in range', () => { + assert.throws( + () => utils.ensureSafeUnsignedInteger(BigInt(Number.MIN_SAFE_INTEGER)), + new Error('Value -9007199254740991 is negative') + ); + assert.throws( + () => utils.ensureSafeUnsignedInteger(BigInt(-100)), + new Error('Value -100 is negative') + ); + }); + + it('should error on bigints outside of range', () => { + assert.throws( + () => + utils.ensureSafeUnsignedInteger( + BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1) + ), + new Error('BigInt value -9007199254740992 is not a safe integer') + ); + assert.throws( + () => + utils.ensureSafeUnsignedInteger( + BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1) + ), + new Error('BigInt value 9007199254740992 is not a safe integer') + ); + }); + + it('should accept positive safe integers', () => { + assert.strictEqual(utils.ensureSafeUnsignedInteger(0), 0); + assert.strictEqual(utils.ensureSafeUnsignedInteger(7), 7); + assert.strictEqual( + utils.ensureSafeUnsignedInteger(Number.MAX_SAFE_INTEGER), + Number.MAX_SAFE_INTEGER + ); + }); + + it('should error on negative safe integers', () => { + assert.throws( + () => utils.ensureSafeUnsignedInteger(Number.MIN_SAFE_INTEGER), + new Error('Value -9007199254740991 is negative') + ); + assert.throws( + () => utils.ensureSafeUnsignedInteger(-100), + new Error('Value -100 is negative') + ); + }); + + it('should error on unsafe integers', () => { + assert.throws( + () => utils.ensureSafeUnsignedInteger(0.5), + new Error('Value 0.5 is not a safe integer') + ); + assert.throws( + () => utils.ensureSafeUnsignedInteger(Number.MIN_SAFE_INTEGER - 1), + new Error('Value -9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureSafeUnsignedInteger(Number.MAX_SAFE_INTEGER + 1), + new Error('Value 9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureSafeUnsignedInteger(NaN), + new Error('Value NaN is not a safe integer') + ); + }); + + it('should error on unexpected types', () => { + assert.throws( + () => utils.ensureSafeUnsignedInteger('0'), + new Error('Unexpected type string, 0') + ); + + assert.throws( + () => utils.ensureSafeUnsignedInteger(true), + new Error('Unexpected type boolean, true') + ); + + assert.throws( + () => utils.ensureSafeUnsignedInteger(null), + new Error('Unexpected type object, null') + ); + }); + }); + + describe('ensureBigInt', () => { + it('should error on undefined', () => { + assert.throws( + () => utils.ensureBigInt(undefined), + new Error('Value is undefined') + ); + }); + + it('should accept bigints', () => { + assert.strictEqual( + utils.ensureBigInt( + BigInt(-1) * BigInt('0xffffffffffffffff') - BigInt(1) + ), + BigInt(-1) * BigInt('0xffffffffffffffff') - BigInt(1) + ); + assert.strictEqual( + utils.ensureBigInt(BigInt(-1) * BigInt('0xffffffffffffffff')), + BigInt(-1) * BigInt('0xffffffffffffffff') + ); + assert.strictEqual( + utils.ensureBigInt(BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1)), + BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1) + ); + assert.strictEqual( + utils.ensureBigInt(BigInt(Number.MIN_SAFE_INTEGER)), + BigInt(Number.MIN_SAFE_INTEGER) + ); + assert.strictEqual(utils.ensureBigInt(BigInt(-100)), BigInt(-100)); + assert.strictEqual(utils.ensureBigInt(BigInt(0)), BigInt(0)); + assert.strictEqual(utils.ensureBigInt(BigInt(7)), BigInt(7)); + assert.strictEqual( + utils.ensureBigInt(BigInt(Number.MAX_SAFE_INTEGER)), + BigInt(Number.MAX_SAFE_INTEGER) + ); + assert.strictEqual( + utils.ensureBigInt(BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)), + BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1) + ); + assert.strictEqual( + utils.ensureBigInt(BigInt('0xffffffffffffffff')), + BigInt('0xffffffffffffffff') + ); + assert.strictEqual( + utils.ensureBigInt(BigInt('0xffffffffffffffff') + BigInt(1)), + BigInt('0xffffffffffffffff') + BigInt(1) + ); + }); + + it('should accept safe integers', () => { + assert.strictEqual( + utils.ensureBigInt(Number.MIN_SAFE_INTEGER), + BigInt(Number.MIN_SAFE_INTEGER) + ); + assert.strictEqual(utils.ensureBigInt(-100), BigInt(-100)); + assert.strictEqual(utils.ensureBigInt(0), BigInt(0)); + assert.strictEqual(utils.ensureBigInt(7), BigInt(7)); + assert.strictEqual( + utils.ensureBigInt(Number.MAX_SAFE_INTEGER), + BigInt(Number.MAX_SAFE_INTEGER) + ); + }); + + it('should error on unsafe integers', () => { + assert.throws( + () => utils.ensureBigInt(0.5), + new Error('Value 0.5 is not a safe integer') + ); + assert.throws( + () => utils.ensureBigInt(Number.MIN_SAFE_INTEGER - 1), + new Error('Value -9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureBigInt(Number.MAX_SAFE_INTEGER + 1), + new Error('Value 9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureBigInt(NaN), + new Error('Value NaN is not a safe integer') + ); + }); + + it('should error on unexpected types', () => { + assert.throws( + () => utils.ensureBigInt('0'), + new Error('Unexpected type string, 0') + ); + + assert.throws( + () => utils.ensureBigInt(true), + new Error('Unexpected type boolean, true') + ); + + assert.throws( + () => utils.ensureBigInt(null), + new Error('Unexpected type object, null') + ); + }); + }); + + describe('ensureUint64', () => { + it('should error on undefined', () => { + assert.throws( + () => utils.ensureUint64(undefined), + new Error('Value is undefined') + ); + }); + + it('should accept bigints in range', () => { + assert.strictEqual(utils.ensureUint64(BigInt(0)), BigInt(0)); + assert.strictEqual(utils.ensureUint64(BigInt(7)), BigInt(7)); + assert.strictEqual( + utils.ensureUint64(BigInt(Number.MAX_SAFE_INTEGER)), + BigInt(Number.MAX_SAFE_INTEGER) + ); + assert.strictEqual( + utils.ensureUint64(BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)), + BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1) + ); + assert.strictEqual( + utils.ensureUint64(BigInt('0xffffffffffffffff')), + BigInt('0xffffffffffffffff') + ); + }); + + it('should error on bigints out of range', () => { + assert.throws( + () => utils.ensureUint64(BigInt(-100)), + new Error('Value -100 is not a uint64') + ); + assert.throws( + () => utils.ensureUint64(BigInt('0xffffffffffffffff') + BigInt(1)), + new Error('Value 18446744073709551616 is not a uint64') + ); + }); + + it('should accept positive safe integers', () => { + assert.strictEqual(utils.ensureUint64(0), BigInt(0)); + assert.strictEqual(utils.ensureUint64(7), BigInt(7)); + assert.strictEqual( + utils.ensureUint64(Number.MAX_SAFE_INTEGER), + BigInt(Number.MAX_SAFE_INTEGER) + ); + }); + + it('should error on negative safe integers', () => { + assert.throws( + () => utils.ensureUint64(Number.MIN_SAFE_INTEGER), + new Error('Value -9007199254740991 is not a uint64') + ); + assert.throws( + () => utils.ensureUint64(-100), + new Error('Value -100 is not a uint64') + ); + }); + + it('should error on unsafe integers', () => { + assert.throws( + () => utils.ensureUint64(0.5), + new Error('Value 0.5 is not a safe integer') + ); + assert.throws( + () => utils.ensureUint64(Number.MIN_SAFE_INTEGER - 1), + new Error('Value -9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureUint64(Number.MAX_SAFE_INTEGER + 1), + new Error('Value 9007199254740992 is not a safe integer') + ); + assert.throws( + () => utils.ensureUint64(NaN), + new Error('Value NaN is not a safe integer') + ); + }); + + it('should error on unexpected types', () => { + assert.throws( + () => utils.ensureUint64('0'), + new Error('Unexpected type string, 0') + ); + + assert.throws( + () => utils.ensureUint64(true), + new Error('Unexpected type boolean, true') + ); + + assert.throws( + () => utils.ensureUint64(null), + new Error('Unexpected type object, null') + ); + }); + }); }); describe('nacl wrapper', () => { From ffc585b5785e6def3a5629ca4c78b0d9045231ec Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 9 Feb 2024 10:16:53 -0500 Subject: [PATCH 16/17] remove console.log in test --- tests/5.Transaction.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/5.Transaction.ts b/tests/5.Transaction.ts index 759aae61a..4da164a83 100644 --- a/tests/5.Transaction.ts +++ b/tests/5.Transaction.ts @@ -625,13 +625,7 @@ describe('Sign', () => { }, note: new Uint8Array([123, 12, 200]), }); - // console.log( - // `${expectedTxn.stateProofType} ${expectedTxn.stateProofMessage} ${expectedTxn.stateProof} ${expectedTxn.type}` - // ); const encRep = expectedTxn.get_obj_for_encoding(); - // console.log( - // `${encRep.sptype} ${encRep.spmsg} ${encRep.sp} ${encRep.type}` - // ); const encTxn = algosdk.encodeObj(encRep); const decEncRep = algosdk.decodeObj(encTxn); const decTxn = algosdk.Transaction.from_obj_for_encoding( From 1e3371a1fda6bd908fe29d029d089a175a4e4ed0 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 9 Feb 2024 10:21:37 -0500 Subject: [PATCH 17/17] simplify participation example math --- examples/participation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/participation.ts b/examples/participation.ts index 0bd36f572..17c74a4cf 100644 --- a/examples/participation.ts +++ b/examples/participation.ts @@ -24,10 +24,10 @@ async function main() { ); // sets up keys for 100,000 rounds - const numRounds = BigInt(100_000); + const numRounds = 100_000; // dilution default is sqrt num rounds - const keyDilution = BigInt(Math.floor(Math.sqrt(Number(numRounds)))); + const keyDilution = Math.floor(Math.sqrt(numRounds)); // create transaction const onlineKeyreg = @@ -37,7 +37,7 @@ async function main() { selectionKey, stateProofKey, voteFirst: params.firstValid, - voteLast: params.firstValid + numRounds, + voteLast: params.firstValid + BigInt(numRounds), voteKeyDilution: keyDilution, suggestedParams: params, });