diff --git a/.github/MIGRATION.md b/.github/MIGRATION.md index e3696b1cc..8a41ce4e0 100644 --- a/.github/MIGRATION.md +++ b/.github/MIGRATION.md @@ -229,6 +229,10 @@ Namely, the node/API it is sent to will "broadcast" the transaction to the mempo ### `serialize` methods +Most users shouldn't need to use `serializeXyz` methods. +Concepts in Stacks.js have helpers like `postConditionToHex` instead. +Serialization is meant for internal representations in transactions, mostly not for user-facing data. + Existing methods now take or return **hex-encoded strings** _instead_ of `Uint8Array`s. > If you were already converting returned bytes to hex-strings in your code, you can now skip the conversion step — hex-strings are the new default. @@ -236,18 +240,22 @@ Existing methods now take or return **hex-encoded strings** _instead_ of `Uint8A For easier migrating, renaming the following methods is possible to keep the previous behavior: - `StacksTransaction.serialize` → `StacksTransaction.serializeBytes` -- `serializeCV` → `serializeCVBytes` - `serializeAddress` → `serializeAddressBytes` +- `serializeAuthorization` → `serializeAuthorizationBytes` +- `serializeCV` → `serializeCVBytes` - `serializeLPList` → `serializeLPListBytes` - `serializeLPString` → `serializeLPStringBytes` -- `serializePayload` → `serializePayloadBytes` -- `serializePublicKey` → `serializePublicKeyBytes` -- `serializeStacksMessage` → `serializeStacksMessageBytes` - `serializeMemoString` → `serializeMemoStringBytes` -- `serializeTransactionAuthField` → `serializeTransactionAuthFieldBytes` - `serializeMessageSignature` → `serializeMessageSignatureBytes` +- `serializeMultiSigSpendingCondition` → `serializeMultiSigSpendingConditionBytes` +- `serializePayload` → `serializePayloadBytes` - `serializePostCondition` → `serializePostConditionBytes` +- `serializePublicKey` → `serializePublicKeyBytes` +- `serializeSingleSigSpendingCondition` → `serializeSingleSigSpendingConditionBytes` +- `serializeSpendingCondition` → `serializeSpendingConditionBytes` +- `serializeStacksMessage` → `serializeStacksMessageBytes` - `serializeStacksMessage` → `serializeStacksWireBytes` +- `serializeTransactionAuthField` → `serializeTransactionAuthFieldBytes` ### Asset Helper Methods diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index a05ae035b..afaa6ef7a 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -18,7 +18,7 @@ import { ClarityAbi, ContractIdString, FeeEstimation, - StacksTransaction, + StacksTransactionWire, TxBroadcastResult, broadcastTransaction, fetchAbi, @@ -65,7 +65,7 @@ export class StacksNodeApi { * @returns a Promise that resolves to a {@link TxBroadcastResult} object */ broadcastTransaction = async ( - transaction: StacksTransaction, + transaction: StacksTransactionWire, attachment?: Uint8Array | string, network?: StacksNetworkName | StacksNetwork ): Promise => { diff --git a/packages/bns/src/index.ts b/packages/bns/src/index.ts index 9b09234df..9c0fc415b 100644 --- a/packages/bns/src/index.ts +++ b/packages/bns/src/index.ts @@ -6,7 +6,7 @@ import { NonFungiblePostCondition, PostCondition, ResponseErrorCV, - StacksTransaction, + StacksTransactionWire, StxPostCondition, UnsignedContractCallOptions, bufferCV, @@ -60,7 +60,9 @@ export interface BnsContractCallOptions { postConditions?: PostCondition[]; } -async function makeBnsContractCall(options: BnsContractCallOptions): Promise { +async function makeBnsContractCall( + options: BnsContractCallOptions +): Promise { const txOptions: UnsignedContractCallOptions = { contractAddress: options.network.bootAddress, contractName: BNS_CONTRACT_NAME, @@ -271,7 +273,7 @@ export interface PreorderNamespaceOptions { * * @param {PreorderNamespaceOptions} options - an options object for the preorder * - * @return {Promise} + * @return {Promise} */ export async function buildPreorderNamespaceTx({ namespace, @@ -279,7 +281,7 @@ export async function buildPreorderNamespaceTx({ stxToBurn, publicKey, network, -}: PreorderNamespaceOptions): Promise { +}: PreorderNamespaceOptions): Promise { const bnsFunctionName = 'namespace-preorder'; const saltedNamespaceBytes = utf8ToBytes(`${namespace}${salt}`); const hashedSaltedNamespace = hash160(saltedNamespaceBytes); @@ -328,7 +330,7 @@ export interface RevealNamespaceOptions { * * @param {RevealNamespaceOptions} options - an options object for the reveal * - * @return {Promise} + * @return {Promise} */ export async function buildRevealNamespaceTx({ namespace, @@ -338,7 +340,7 @@ export async function buildRevealNamespaceTx({ namespaceImportAddress, publicKey, network, -}: RevealNamespaceOptions): Promise { +}: RevealNamespaceOptions): Promise { const bnsFunctionName = 'namespace-reveal'; return makeBnsContractCall({ @@ -401,7 +403,7 @@ export interface ImportNameOptions { * * @param {ImportNameOptions} options - an options object for the name import * - * @return {Promise} + * @return {Promise} */ export async function buildImportNameTx({ namespace, @@ -410,7 +412,7 @@ export async function buildImportNameTx({ zonefile, publicKey, network, -}: ImportNameOptions): Promise { +}: ImportNameOptions): Promise { const bnsFunctionName = 'name-import'; const zonefileHash = getZonefileHash(zonefile); @@ -449,13 +451,13 @@ export interface ReadyNamespaceOptions { * * @param {ReadyNamespaceOptions} options - an options object for the namespace ready transaction * - * @return {Promise} + * @return {Promise} */ export async function buildReadyNamespaceTx({ namespace, publicKey, network, -}: ReadyNamespaceOptions): Promise { +}: ReadyNamespaceOptions): Promise { const bnsFunctionName = 'namespace-ready'; return makeBnsContractCall({ @@ -491,7 +493,7 @@ export interface PreorderNameOptions { * * @param {PreorderNameOptions} options - an options object for the preorder * - * @return {Promise} + * @return {Promise} */ export async function buildPreorderNameTx({ fullyQualifiedName, @@ -499,7 +501,7 @@ export async function buildPreorderNameTx({ stxToBurn, publicKey, network, -}: PreorderNameOptions): Promise { +}: PreorderNameOptions): Promise { const bnsFunctionName = 'name-preorder'; const { subdomain } = decodeFQN(fullyQualifiedName); if (subdomain) { @@ -550,7 +552,7 @@ export interface RegisterNameOptions { * * @param {RegisterNameOptions} options - an options object for the registration * - * @return {Promise} + * @return {Promise} */ export async function buildRegisterNameTx({ fullyQualifiedName, @@ -558,7 +560,7 @@ export async function buildRegisterNameTx({ zonefile, publicKey, network, -}: RegisterNameOptions): Promise { +}: RegisterNameOptions): Promise { const bnsFunctionName = 'name-register'; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { @@ -604,14 +606,14 @@ export interface UpdateNameOptions { * * @param {UpdateNameOptions} options - an options object for the update * - * @return {Promise} + * @return {Promise} */ export async function buildUpdateNameTx({ fullyQualifiedName, zonefile, publicKey, network, -}: UpdateNameOptions): Promise { +}: UpdateNameOptions): Promise { const bnsFunctionName = 'name-update'; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { @@ -657,7 +659,7 @@ export interface TransferNameOptions { * * @param {TransferNameOptions} options - an options object for the transfer * - * @return {Promise} + * @return {Promise} */ export async function buildTransferNameTx({ fullyQualifiedName, @@ -665,7 +667,7 @@ export async function buildTransferNameTx({ zonefile, publicKey, network, -}: TransferNameOptions): Promise { +}: TransferNameOptions): Promise { const bnsFunctionName = 'name-transfer'; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { @@ -730,13 +732,13 @@ export interface RevokeNameOptions { * * @param {RevokeNameOptions} options - an options object for the revoke * - * @return {Promise} + * @return {Promise} */ export async function buildRevokeNameTx({ fullyQualifiedName, publicKey, network, -}: RevokeNameOptions): Promise { +}: RevokeNameOptions): Promise { const bnsFunctionName = 'name-revoke'; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { @@ -777,7 +779,7 @@ export interface RenewNameOptions { * * @param {RenewNameOptions} options - an options object for the renew * - * @return {Promise} + * @return {Promise} */ export async function buildRenewNameTx({ fullyQualifiedName, @@ -786,7 +788,7 @@ export async function buildRenewNameTx({ zonefile, publicKey, network, -}: RenewNameOptions): Promise { +}: RenewNameOptions): Promise { const bnsFunctionName = 'name-renewal'; const { subdomain, namespace, name } = decodeFQN(fullyQualifiedName); if (subdomain) { diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 3036c8b02..e4c2e8a78 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -28,7 +28,7 @@ import { SignedContractDeployOptions, SignedTokenTransferOptions, signWithKey, - StacksTransaction, + StacksTransactionWire, TransactionSigner, TxBroadcastResult, validateContractCall, @@ -704,7 +704,7 @@ async function sendTokens(_network: CLINetworkAdapter, args: string[]): Promise< network, }; - const tx: StacksTransaction = await makeSTXTokenTransfer(options); + const tx: StacksTransactionWire = await makeSTXTokenTransfer(options); if (estimateOnly) { return fetchFeeEstimateTransfer({ transaction: tx, network }).then(cost => { @@ -758,7 +758,7 @@ async function contractDeploy(_network: CLINetworkAdapter, args: string[]): Prom fee, nonce, network, - postConditionMode: PostConditionMode.Allow, + postConditionMode: 'allow', }; const tx = await makeContractDeploy(options); diff --git a/packages/stacking/src/index.ts b/packages/stacking/src/index.ts index dfba015d1..680211cb7 100644 --- a/packages/stacking/src/index.ts +++ b/packages/stacking/src/index.ts @@ -21,7 +21,7 @@ import { OptionalCV, PrincipalCV, ResponseErrorCV, - StacksTransaction, + StacksTransactionWire, TupleCV, TxBroadcastResult, UIntCV, @@ -1569,9 +1569,15 @@ export class StackingClient { /** * Adjust microstacks amount for locking after taking into account transaction fees * - * @returns {StacksTransaction} that resolves to a transaction object if the operation succeeds + * @returns {StacksTransactionWire} that resolves to a transaction object if the operation succeeds */ - modifyLockTxFee({ tx, amountMicroStx }: { tx: StacksTransaction; amountMicroStx: IntegerType }) { + modifyLockTxFee({ + tx, + amountMicroStx, + }: { + tx: StacksTransactionWire; + amountMicroStx: IntegerType; + }) { const fee = getFee(tx.auth); (tx.payload as ContractCallPayload).functionArgs[0] = uintCV(intToBigInt(amountMicroStx) - fee); return tx; diff --git a/packages/transactions/src/authorization.ts b/packages/transactions/src/authorization.ts index ac50f1be4..99aa74c04 100644 --- a/packages/transactions/src/authorization.ts +++ b/packages/transactions/src/authorization.ts @@ -210,6 +210,13 @@ function clearCondition(condition: SpendingConditionOpts): SpendingCondition { export function serializeSingleSigSpendingCondition( condition: SingleSigSpendingConditionOpts +): string { + return bytesToHex(serializeSingleSigSpendingConditionBytes(condition)); +} + +/** @internal */ +export function serializeSingleSigSpendingConditionBytes( + condition: SingleSigSpendingConditionOpts ): Uint8Array { const bytesArray = [ condition.hashMode, @@ -224,6 +231,13 @@ export function serializeSingleSigSpendingCondition( export function serializeMultiSigSpendingCondition( condition: MultiSigSpendingConditionOpts +): string { + return bytesToHex(serializeMultiSigSpendingConditionBytes(condition)); +} + +/** @internal */ +export function serializeMultiSigSpendingConditionBytes( + condition: MultiSigSpendingConditionOpts ): Uint8Array { const bytesArray = [ condition.hashMode, @@ -320,11 +334,14 @@ export function deserializeMultiSigSpendingCondition( }; } -export function serializeSpendingCondition(condition: SpendingConditionOpts): Uint8Array { - if (isSingleSig(condition)) { - return serializeSingleSigSpendingCondition(condition); - } - return serializeMultiSigSpendingCondition(condition); +export function serializeSpendingCondition(condition: SpendingConditionOpts): string { + return bytesToHex(serializeSpendingConditionBytes(condition)); +} + +/** @internal */ +export function serializeSpendingConditionBytes(condition: SpendingConditionOpts): Uint8Array { + if (isSingleSig(condition)) return serializeSingleSigSpendingConditionBytes(condition); + return serializeMultiSigSpendingConditionBytes(condition); } export function deserializeSpendingCondition(bytesReader: BytesReader): SpendingCondition { @@ -674,17 +691,22 @@ export function setSponsor( }; } -export function serializeAuthorization(auth: Authorization): Uint8Array { +export function serializeAuthorization(auth: Authorization): string { + return bytesToHex(serializeAuthorizationBytes(auth)); +} + +/** @internal */ +export function serializeAuthorizationBytes(auth: Authorization): Uint8Array { const bytesArray = []; bytesArray.push(auth.authType); switch (auth.authType) { case AuthType.Standard: - bytesArray.push(serializeSpendingCondition(auth.spendingCondition)); + bytesArray.push(serializeSpendingConditionBytes(auth.spendingCondition)); break; case AuthType.Sponsored: - bytesArray.push(serializeSpendingCondition(auth.spendingCondition)); - bytesArray.push(serializeSpendingCondition(auth.sponsorSpendingCondition)); + bytesArray.push(serializeSpendingConditionBytes(auth.spendingCondition)); + bytesArray.push(serializeSpendingConditionBytes(auth.sponsorSpendingCondition)); break; } diff --git a/packages/transactions/src/builders.ts b/packages/transactions/src/builders.ts index 3cc91dd6e..9c948cb89 100644 --- a/packages/transactions/src/builders.ts +++ b/packages/transactions/src/builders.ts @@ -17,7 +17,6 @@ import { import { ClarityValue, PrincipalCV } from './clarity'; import { AddressHashMode, - AnchorMode, ClarityVersion, MultiSigHashMode, PayloadType, @@ -33,10 +32,10 @@ import { publicKeyToAddress, publicKeyToHex, } from './keys'; -import { postConditionToWire } from './postcondition'; -import { PostCondition } from './postcondition-types'; +import { postConditionModeFrom, postConditionToWire } from './postcondition'; +import { PostCondition, PostConditionModeName } from './postcondition-types'; import { TransactionSigner } from './signer'; -import { StacksTransaction, deriveNetworkFromTx } from './transaction'; +import { StacksTransactionWire, deriveNetworkFromTx } from './transaction'; import { omit } from './utils'; import { PostConditionWire, @@ -114,11 +113,11 @@ export type SignedMultiSigTokenTransferOptions = TokenTransferOptions & SignedMu * * @param {UnsignedTokenTransferOptions | UnsignedMultiSigTokenTransferOptions} txOptions - an options object for the token transfer * - * @return {Promise} + * @return {Promise} */ export async function makeUnsignedSTXTokenTransfer( txOptions: UnsignedTokenTransferOptions | UnsignedMultiSigTokenTransferOptions -): Promise { +): Promise { const defaultOptions = { fee: BigInt(0), nonce: BigInt(0), @@ -171,15 +170,13 @@ export async function makeUnsignedSTXTokenTransfer( ? createSponsoredAuth(spendingCondition) : createStandardAuth(spendingCondition); - const transaction = new StacksTransaction( - options.network.transactionVersion, - authorization, + const transaction = new StacksTransactionWire({ + transactionVersion: options.network.transactionVersion, + chainId: options.network.chainId, + auth: authorization, payload, - undefined, // no post conditions on STX transfers (see SIP-005) - undefined, // no post conditions on STX transfers (see SIP-005) - AnchorMode.Any, - options.network.chainId - ); + // no post conditions on STX transfers (see SIP-005) + }); if (txOptions.fee == null) { const fee = await fetchFeeEstimate({ transaction, ...options }); @@ -203,11 +200,11 @@ export async function makeUnsignedSTXTokenTransfer( * * @param {SignedTokenTransferOptions | SignedMultiSigTokenTransferOptions} txOptions - an options object for the token transfer * - * @return {StacksTransaction} + * @return {StacksTransactionWire} */ export async function makeSTXTokenTransfer( txOptions: SignedTokenTransferOptions | SignedMultiSigTokenTransferOptions -): Promise { +): Promise { if ('senderKey' in txOptions) { // single-sig const publicKey = privateKeyToPublic(txOptions.senderKey); @@ -249,9 +246,9 @@ export type BaseContractDeployOptions = { nonce?: IntegerType; /** the post condition mode, specifying whether or not post-conditions must fully cover all * transfered assets */ - postConditionMode?: PostConditionMode; + postConditionMode?: PostConditionModeName | PostConditionMode; /** a list of post conditions to add to the transaction */ - postConditions?: PostConditionWire[]; + postConditions?: PostCondition[] | PostConditionWire[]; /** set to true if another account is sponsoring the transaction (covering the transaction fee) */ sponsored?: boolean; } & NetworkClientParam; @@ -280,11 +277,11 @@ export type SignedMultiSigContractDeployOptions = BaseContractDeployOptions & Si * * Returns a signed Stacks smart contract deploy transaction. * - * @return {StacksTransaction} + * @return {StacksTransactionWire} */ export async function makeContractDeploy( txOptions: SignedContractDeployOptions | SignedMultiSigContractDeployOptions -): Promise { +): Promise { if ('senderKey' in txOptions) { // single-sig const publicKey = privateKeyToPublic(txOptions.senderKey); @@ -314,7 +311,7 @@ export async function makeContractDeploy( export async function makeUnsignedContractDeploy( txOptions: UnsignedContractDeployOptions | UnsignedMultiSigContractDeployOptions -): Promise { +): Promise { const defaultOptions = { fee: BigInt(0), nonce: BigInt(0), @@ -327,6 +324,7 @@ export async function makeUnsignedContractDeploy( const options = Object.assign(defaultOptions, txOptions); options.network = networkFrom(options.network); options.client = Object.assign({}, clientFromNetwork(options.network), txOptions.client); + options.postConditionMode = postConditionModeFrom(options.postConditionMode); const payload = createSmartContractPayload( options.contractName, @@ -372,23 +370,20 @@ export async function makeUnsignedContractDeploy( ? createSponsoredAuth(spendingCondition) : createStandardAuth(spendingCondition); - const postConditions: PostConditionWire[] = []; - if (options.postConditions && options.postConditions.length > 0) { - options.postConditions.forEach(postCondition => { - postConditions.push(postCondition); - }); - } + const postConditions: PostConditionWire[] = (options.postConditions ?? []).map(pc => { + if (typeof pc.type === 'string') return postConditionToWire(pc); + return pc; + }); const lpPostConditions = createLPList(postConditions); - const transaction = new StacksTransaction( - options.network.transactionVersion, - authorization, + const transaction = new StacksTransactionWire({ + transactionVersion: options.network.transactionVersion, + chainId: options.network.chainId, + auth: authorization, payload, - lpPostConditions, - options.postConditionMode, - AnchorMode.Any, - options.network.chainId - ); + postConditions: lpPostConditions, + postConditionMode: options.postConditionMode, + }); if (txOptions.fee === undefined || txOptions.fee === null) { const fee = await fetchFeeEstimate({ transaction, ...options }); @@ -420,7 +415,7 @@ export type ContractCallOptions = { nonce?: IntegerType; /** the post condition mode, specifying whether or not post-conditions must fully cover all * transfered assets */ - postConditionMode?: PostConditionMode; + postConditionMode?: PostConditionModeName | PostConditionMode; /** a list of post conditions to add to the transaction */ postConditions?: PostCondition[]; /** set to true to validate that the supplied function args match those specified in @@ -447,11 +442,11 @@ export type SignedMultiSigContractCallOptions = ContractCallOptions & SignedMult * * @param {UnsignedContractCallOptions | UnsignedMultiSigContractCallOptions} txOptions - an options object for the contract call * - * @returns {Promise} + * @returns {Promise} */ export async function makeUnsignedContractCall( txOptions: UnsignedContractCallOptions | UnsignedMultiSigContractCallOptions -): Promise { +): Promise { const defaultOptions = { fee: BigInt(0), nonce: BigInt(0), @@ -463,6 +458,7 @@ export async function makeUnsignedContractCall( const options = Object.assign(defaultOptions, txOptions); options.network = networkFrom(options.network); options.client = Object.assign({}, clientFromNetwork(options.network), options.client); + options.postConditionMode = postConditionModeFrom(options.postConditionMode); const payload = createContractCallPayload( options.contractAddress, @@ -524,20 +520,20 @@ export async function makeUnsignedContractCall( ? createSponsoredAuth(spendingCondition) : createStandardAuth(spendingCondition); - const postConditions = (options.postConditions ?? []).map( - postConditionToWire as (post: PostCondition) => PostConditionWire - ); + const postConditions: PostConditionWire[] = (options.postConditions ?? []).map(pc => { + if (typeof pc.type === 'string') return postConditionToWire(pc); + return pc; + }); const lpPostConditions = createLPList(postConditions); - const transaction = new StacksTransaction( - options.network.transactionVersion, - authorization, + const transaction = new StacksTransactionWire({ + transactionVersion: options.network.transactionVersion, + chainId: options.network.chainId, + auth: authorization, payload, - lpPostConditions, - options.postConditionMode, - AnchorMode.Any, - options.network.chainId - ); + postConditions: lpPostConditions, + postConditionMode: options.postConditionMode, + }); if (txOptions.fee === undefined || txOptions.fee === null) { const fee = await fetchFeeEstimate({ transaction, ...options }); @@ -561,11 +557,11 @@ export async function makeUnsignedContractCall( * * Returns a signed Stacks smart contract function call transaction. * - * @return {StacksTransaction} + * @return {StacksTransactionWire} */ export async function makeContractCall( txOptions: SignedContractCallOptions | SignedMultiSigContractCallOptions -): Promise { +): Promise { if ('senderKey' in txOptions) { // single-sig const publicKey = privateKeyToPublic(txOptions.senderKey); @@ -598,7 +594,7 @@ export async function makeContractCall( */ export type SponsorOptionsOpts = { /** the origin-signed transaction */ - transaction: StacksTransaction; + transaction: StacksTransactionWire; /** the sponsor's private key */ sponsorPrivateKey: PrivateKey; /** the transaction fee amount to sponsor */ @@ -620,7 +616,7 @@ export type SponsorOptionsOpts = { */ export async function sponsorTransaction( sponsorOptions: SponsorOptionsOpts -): Promise { +): Promise { const defaultOptions = { fee: 0 as IntegerType, sponsorNonce: 0 as IntegerType, @@ -683,7 +679,7 @@ export async function sponsorTransaction( /** @internal multi-sig signing re-use */ function mutatingSignAppendMultiSig( /** **Warning:** method mutates `transaction` */ - transaction: StacksTransaction, + transaction: StacksTransactionWire, publicKeys: string[], signerKeys: string[], address?: string diff --git a/packages/transactions/src/constants.ts b/packages/transactions/src/constants.ts index 297296999..3dd7dbece 100644 --- a/packages/transactions/src/constants.ts +++ b/packages/transactions/src/constants.ts @@ -72,7 +72,7 @@ const AnchorModeMap = { [AnchorMode.Any]: AnchorMode.Any, }; -/** @ignore */ +/** @ignore @deprecated Block anchor modes don't exist on-chain anymore. */ export function anchorModeFrom(mode: AnchorModeName | AnchorMode): AnchorMode { if (mode in AnchorModeMap) return AnchorModeMap[mode]; throw new Error(`Invalid anchor mode "${mode}", must be one of: ${AnchorModeNames.join(', ')}`); diff --git a/packages/transactions/src/fetch.ts b/packages/transactions/src/fetch.ts index 8d3421fc7..2c45a9bd2 100644 --- a/packages/transactions/src/fetch.ts +++ b/packages/transactions/src/fetch.ts @@ -4,7 +4,7 @@ import { ClarityValue, NoneCV, deserializeCV, serializeCV } from './clarity'; import { ClarityAbi } from './contract-abi'; import { NoEstimateAvailableError } from './errors'; import { - StacksTransaction, + StacksTransactionWire, deriveNetworkFromTx, estimateTransactionByteLength, } from './transaction'; @@ -40,7 +40,7 @@ export async function broadcastTransaction({ client: _client, }: { /** The transaction to broadcast */ - transaction: StacksTransaction; + transaction: StacksTransactionWire; /** Optional attachment in bytes or encoded as a hex string */ attachment?: Uint8Array | string; } & NetworkClientParam): Promise { @@ -139,7 +139,7 @@ export async function fetchFeeEstimateTransfer({ client: _client, }: { /** The token transfer transaction to estimate fees for */ - transaction: StacksTransaction; + transaction: StacksTransactionWire; } & NetworkClientParam): Promise { const network = _network ?? deriveNetworkFromTx(txOpt); const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); @@ -221,7 +221,7 @@ export async function fetchFeeEstimate({ network: _network, client: _client, }: { - transaction: StacksTransaction; + transaction: StacksTransactionWire; } & NetworkClientParam): Promise { const network = _network ?? deriveNetworkFromTx(txOpt); const client = Object.assign({}, clientFromNetwork(networkFrom(network)), _client); diff --git a/packages/transactions/src/postcondition-types.ts b/packages/transactions/src/postcondition-types.ts index 7ef7d903b..ee6fc9bef 100644 --- a/packages/transactions/src/postcondition-types.ts +++ b/packages/transactions/src/postcondition-types.ts @@ -40,3 +40,5 @@ export type NonFungiblePostCondition = { }; export type PostCondition = StxPostCondition | FungiblePostCondition | NonFungiblePostCondition; + +export type PostConditionModeName = 'allow' | 'deny'; diff --git a/packages/transactions/src/postcondition.ts b/packages/transactions/src/postcondition.ts index 4a13dd3b1..6462bcd14 100644 --- a/packages/transactions/src/postcondition.ts +++ b/packages/transactions/src/postcondition.ts @@ -1,16 +1,17 @@ import { FungibleConditionCode, NonFungibleConditionCode, + PostConditionMode, PostConditionPrincipalId, PostConditionType, } from './constants'; -import { PostCondition } from './postcondition-types'; +import { PostCondition, PostConditionModeName } from './postcondition-types'; import { PostConditionWire, StacksWireType, parseAssetString, parsePrincipalString, - serializePostCondition, + serializePostConditionWire, } from './wire'; const FUNGIBLE_COMPARATOR_MAPPING = { @@ -89,5 +90,15 @@ export function postConditionToWire(postcondition: PostCondition): PostCondition */ export function postConditionToHex(postcondition: PostCondition): string { const wire = postConditionToWire(postcondition); - return serializePostCondition(wire); + return serializePostConditionWire(wire); +} + +/** @internal */ +export function postConditionModeFrom( + mode: PostConditionModeName | PostConditionMode +): PostConditionMode { + if (typeof mode === 'number') return mode; + if (mode === 'allow') return PostConditionMode.Allow; + if (mode === 'deny') return PostConditionMode.Deny; + throw new Error(`Invalid post condition mode: ${mode}`); } diff --git a/packages/transactions/src/signer.ts b/packages/transactions/src/signer.ts index 9400e58fa..08db57b58 100644 --- a/packages/transactions/src/signer.ts +++ b/packages/transactions/src/signer.ts @@ -8,20 +8,20 @@ import { } from './authorization'; import { AddressHashMode, AuthType, PubKeyEncoding } from './constants'; import { SigningError } from './errors'; -import { StacksTransaction } from './transaction'; +import { StacksTransactionWire } from './transaction'; import { cloneDeep } from './utils'; import { PublicKeyWire, StacksWireType } from './wire'; import { createStacksPublicKey } from './keys'; // todo: get rid of signer and combine with transaction class? could reduce code and complexity by calculating sighash newly each sign and append. export class TransactionSigner { - transaction: StacksTransaction; + transaction: StacksTransactionWire; sigHash: string; originDone: boolean; checkOversign: boolean; checkOverlap: boolean; - constructor(transaction: StacksTransaction) { + constructor(transaction: StacksTransactionWire) { this.transaction = transaction; this.sigHash = transaction.signBegin(); this.originDone = false; @@ -61,14 +61,14 @@ export class TransactionSigner { } static createSponsorSigner( - transaction: StacksTransaction, + transaction: StacksTransactionWire, spendingCondition: SpendingConditionOpts ) { if (transaction.auth.authType != AuthType.Sponsored) { throw new SigningError('Cannot add sponsor to non-sponsored transaction'); } - const tx: StacksTransaction = cloneDeep(transaction); + const tx: StacksTransactionWire = cloneDeep(transaction); tx.setSponsor(spendingCondition); const originSigHash = tx.verifyOrigin(); const signer = new this(tx); @@ -152,11 +152,11 @@ export class TransactionSigner { this.originDone = true; } - getTxInComplete(): StacksTransaction { + getTxInComplete(): StacksTransactionWire { return cloneDeep(this.transaction); } - resume(transaction: StacksTransaction) { + resume(transaction: StacksTransactionWire) { this.transaction = cloneDeep(transaction); this.sigHash = transaction.signBegin(); } diff --git a/packages/transactions/src/transaction.ts b/packages/transactions/src/transaction.ts index 42991063e..11137b74e 100644 --- a/packages/transactions/src/transaction.ts +++ b/packages/transactions/src/transaction.ts @@ -11,10 +11,11 @@ import { } from '@stacks/common'; import { ChainId, - DEFAULT_CHAIN_ID, + NetworkParam, STACKS_MAINNET, STACKS_TESTNET, TransactionVersion, + networkFrom, whenTransactionVersion, } from '@stacks/network'; import { serializePayloadBytes } from '.'; @@ -27,7 +28,7 @@ import { intoInitialSighashAuth, isSingleSig, nextSignature, - serializeAuthorization, + serializeAuthorizationBytes, setFee, setNonce, setSponsor, @@ -37,12 +38,10 @@ import { import { AddressHashMode, AnchorMode, - AnchorModeName, AuthType, PostConditionMode, PubKeyEncoding, RECOVERABLE_ECDSA_SIG_LENGTH_BYTES, - anchorModeFrom, } from './constants'; import { SerializationError, SigningError } from './errors'; import { createStacksPublicKey, privateKeyIsCompressed, publicKeyIsCompressed } from './keys'; @@ -61,27 +60,40 @@ import { serializeLPListBytes, } from './wire'; -export class StacksTransaction { - version: TransactionVersion; +export class StacksTransactionWire { + transactionVersion: TransactionVersion; chainId: ChainId; auth: Authorization; - anchorMode: AnchorMode; payload: PayloadWire; postConditionMode: PostConditionMode; postConditions: LengthPrefixedList; - // todo: next: change to opts object with `network` opt - constructor( - version: TransactionVersion, - auth: Authorization, - payload: PayloadInput, - postConditions?: LengthPrefixedList, - postConditionMode?: PostConditionMode, - anchorMode?: AnchorModeName | AnchorMode, - chainId?: ChainId - ) { - this.version = version; + /** @deprecated Not used, starting with Stacks 2.5. Still needed for serialization. */ + anchorMode: AnchorMode; + + constructor({ + auth, + payload, + postConditions = createLPList([]), + postConditionMode = PostConditionMode.Deny, + transactionVersion, + chainId, + /** The network is only used if `transactionVersion` or `chainId` are not provided */ + network = 'mainnet', + }: { + payload: PayloadInput; + auth: Authorization; + postConditions?: LengthPrefixedList; + postConditionMode?: PostConditionMode; + transactionVersion?: TransactionVersion; + chainId?: ChainId; + } & NetworkParam) { + network = networkFrom(network); + + this.transactionVersion = transactionVersion ?? network.transactionVersion; + this.chainId = chainId ?? network.chainId; this.auth = auth; + if ('amount' in payload) { this.payload = { ...payload, @@ -90,11 +102,11 @@ export class StacksTransaction { } else { this.payload = payload; } - this.chainId = chainId ?? DEFAULT_CHAIN_ID; - this.postConditionMode = postConditionMode ?? PostConditionMode.Deny; - this.postConditions = postConditions ?? createLPList([]); - this.anchorMode = anchorModeFrom(anchorMode ?? AnchorMode.Any); + this.postConditionMode = postConditionMode; + this.postConditions = postConditions; + + this.anchorMode = AnchorMode.Any; } /** @deprecated Does NOT mutate transaction, but rather returns the hash of the transaction with a cleared initial authorization */ @@ -276,8 +288,8 @@ export class StacksTransaction { * ``` */ serializeBytes(): Uint8Array { - if (this.version === undefined) { - throw new SerializationError('"version" is undefined'); + if (this.transactionVersion === undefined) { + throw new SerializationError('"transactionVersion" is undefined'); } if (this.chainId === undefined) { throw new SerializationError('"chainId" is undefined'); @@ -285,20 +297,17 @@ export class StacksTransaction { if (this.auth === undefined) { throw new SerializationError('"auth" is undefined'); } - if (this.anchorMode === undefined) { - throw new SerializationError('"anchorMode" is undefined'); - } if (this.payload === undefined) { throw new SerializationError('"payload" is undefined'); } const bytesArray = []; - bytesArray.push(this.version); + bytesArray.push(this.transactionVersion); const chainIdBytes = new Uint8Array(4); writeUInt32BE(chainIdBytes, this.chainId, 0); bytesArray.push(chainIdBytes); - bytesArray.push(serializeAuthorization(this.auth)); + bytesArray.push(serializeAuthorizationBytes(this.auth)); bytesArray.push(this.anchorMode); bytesArray.push(this.postConditionMode); bytesArray.push(serializeLPListBytes(this.postConditions)); @@ -313,7 +322,7 @@ export class StacksTransaction { */ export function deserializeTransaction(tx: string | Uint8Array | BytesReader) { const bytesReader = isInstance(tx, BytesReader) ? tx : new BytesReader(tx); - const version = bytesReader.readUInt8Enum(TransactionVersion, n => { + const transactionVersion = bytesReader.readUInt8Enum(TransactionVersion, n => { throw new Error(`Could not parse ${n} as TransactionVersion`); }); const chainId = bytesReader.readUInt32BE(); @@ -327,21 +336,22 @@ export function deserializeTransaction(tx: string | Uint8Array | BytesReader) { const postConditions = deserializeLPList(bytesReader, StacksWireType.PostCondition); const payload = deserializePayload(bytesReader); - return new StacksTransaction( - version, + const transaction = new StacksTransactionWire({ + transactionVersion, + chainId, auth, payload, postConditions, postConditionMode, - anchorMode, - chainId - ); + }); + transaction.anchorMode = anchorMode; // not used anymore, but part of the transaction wire format + return transaction; } /** @ignore */ -export function deriveNetworkFromTx(transaction: StacksTransaction) { +export function deriveNetworkFromTx(transaction: StacksTransactionWire) { // todo: maybe add as renamed public method - return whenTransactionVersion(transaction.version)({ + return whenTransactionVersion(transaction.transactionVersion)({ [TransactionVersion.Mainnet]: STACKS_MAINNET, [TransactionVersion.Testnet]: STACKS_TESTNET, }); @@ -356,7 +366,7 @@ export function deriveNetworkFromTx(transaction: StacksTransaction) { * @param {transaction} - StacksTransaction object to be estimated * @return {number} Estimated transaction byte length */ -export function estimateTransactionByteLength(transaction: StacksTransaction): number { +export function estimateTransactionByteLength(transaction: StacksTransactionWire): number { const hashMode = transaction.auth.spendingCondition.hashMode; // List of Multi-sig transaction hash modes const multiSigHashModes = [AddressHashMode.P2SH, AddressHashMode.P2WSH]; @@ -398,7 +408,7 @@ export function estimateTransactionByteLength(transaction: StacksTransaction): n * const hex = serializeTransaction(transaction); * ``` */ -export function serializeTransaction(transaction: StacksTransaction): Hex { +export function serializeTransaction(transaction: StacksTransactionWire): Hex { return transaction.serialize(); } @@ -415,7 +425,7 @@ export function serializeTransaction(transaction: StacksTransaction): Hex { * const bytes = serializeTransactionBytes(transaction); * ``` */ -export function serializeTransactionBytes(transaction: StacksTransaction): Uint8Array { +export function serializeTransactionBytes(transaction: StacksTransactionWire): Uint8Array { return transaction.serializeBytes(); } @@ -432,6 +442,6 @@ export function serializeTransactionBytes(transaction: StacksTransaction): Uint8 * const hex = transactionToHex(transaction); * ``` */ -export function transactionToHex(transaction: StacksTransaction): string { +export function transactionToHex(transaction: StacksTransactionWire): string { return transaction.serialize(); } diff --git a/packages/transactions/src/wire/serialization.ts b/packages/transactions/src/wire/serialization.ts index eed1993fe..28460d01e 100644 --- a/packages/transactions/src/wire/serialization.ts +++ b/packages/transactions/src/wire/serialization.ts @@ -93,7 +93,7 @@ export function serializeStacksWireBytes(wire: StacksWire): Uint8Array { case StacksWireType.Asset: return serializeAssetBytes(wire); case StacksWireType.PostCondition: - return serializePostConditionBytes(wire); + return serializePostConditionWireBytes(wire); case StacksWireType.PublicKey: return serializePublicKeyBytes(wire); case StacksWireType.LengthPrefixedList: @@ -125,7 +125,7 @@ export function deserializeStacksWire( case StacksWireType.Asset: return deserializeAsset(bytesReader); case StacksWireType.PostCondition: - return deserializePostCondition(bytesReader); + return deserializePostConditionWire(bytesReader); case StacksWireType.PublicKey: return deserializePublicKey(bytesReader); case StacksWireType.Payload: @@ -328,7 +328,7 @@ export function deserializeLPList( l.push(deserializeAsset(bytesReader)); break; case StacksWireType.PostCondition: - l.push(deserializePostCondition(bytesReader)); + l.push(deserializePostConditionWire(bytesReader)); break; case StacksWireType.PublicKey: l.push(deserializePublicKey(bytesReader)); @@ -341,11 +341,12 @@ export function deserializeLPList( return createLPList(l, lengthPrefixBytes); } -export function serializePostCondition(postCondition: PostConditionWire): string { - return bytesToHex(serializePostConditionBytes(postCondition)); +export function serializePostConditionWire(postCondition: PostConditionWire): string { + return bytesToHex(serializePostConditionWireBytes(postCondition)); } + /** @internal */ -export function serializePostConditionBytes(postCondition: PostConditionWire): Uint8Array { +export function serializePostConditionWireBytes(postCondition: PostConditionWire): Uint8Array { const bytesArray = []; bytesArray.push(postCondition.conditionType); bytesArray.push(serializePrincipalBytes(postCondition.principal)); @@ -377,7 +378,7 @@ export function serializePostConditionBytes(postCondition: PostConditionWire): U } /** @internal */ -export function deserializePostCondition( +export function deserializePostConditionWire( serialized: string | Uint8Array | BytesReader ): PostConditionWire { const bytesReader = isInstance(serialized, BytesReader) @@ -672,6 +673,7 @@ export function serializeTransactionAuthFieldBytes(field: TransactionAuthFieldWi export function serializePublicKey(key: PublicKeyWire): string { return bytesToHex(serializePublicKeyBytes(key)); } + /** @ignore */ export function serializePublicKeyBytes(key: PublicKeyWire): Uint8Array { return key.data.slice(); diff --git a/packages/transactions/tests/authorization.test.ts b/packages/transactions/tests/authorization.test.ts index 8b7685cbb..d729f3657 100644 --- a/packages/transactions/tests/authorization.test.ts +++ b/packages/transactions/tests/authorization.test.ts @@ -9,8 +9,9 @@ import { createSingleSigSpendingCondition, deserializeSpendingCondition, emptyMessageSignature, - serializeAuthorization, + serializeAuthorizationBytes, serializeSpendingCondition, + serializeSpendingConditionBytes, } from '../src/authorization'; import { AddressHashMode, AuthType, PubKeyEncoding } from '../src/constants'; import { createStacksPublicKey, signWithKey } from '../src/keys'; @@ -56,7 +57,7 @@ test('Single sig spending condition uncompressed', () => { const signature = createMessageSignature('ff'.repeat(65)); spendingCondition.signature = signature; - const serializedSpendingCondition = serializeSpendingCondition(spendingCondition); + const serializedSpendingCondition = serializeSpendingConditionBytes(spendingCondition); // prettier-ignore const spendingConditionBytesHex = [ @@ -105,7 +106,7 @@ test('Multi sig spending condition uncompressed', () => { createTransactionAuthField(PubKeyEncoding.Compressed, sig) ); - const serializedSpendingCondition = serializeSpendingCondition(spendingCondition); + const serializedSpendingCondition = serializeSpendingConditionBytes(spendingCondition); // prettier-ignore const spendingConditionBytesHex = [ @@ -164,7 +165,7 @@ test('Multi sig P2SH spending condition compressed', () => { createTransactionAuthField(PubKeyEncoding.Compressed, sig) ); - const serializedSpendingCondition = serializeSpendingCondition(spendingCondition); + const serializedSpendingCondition = serializeSpendingConditionBytes(spendingCondition); // prettier-ignore const spendingConditionBytesHex = [ @@ -231,7 +232,7 @@ test('Multi sig P2WSH spending condition compressed', () => { createTransactionAuthField(PubKeyEncoding.Compressed, sig) ); - const serializedSpendingCondition = serializeSpendingCondition(spendingCondition); + const serializedSpendingCondition = serializeSpendingConditionBytes(spendingCondition); // prettier-ignore const spendingConditionBytesHex = [ @@ -341,8 +342,8 @@ test('Spending conditions', () => { const spendingConditions = [sp1, sp2, sp3, sp4, sp5, sp6]; for (let i = 0; i < spendingConditions.length; i++) { - const serialized1 = serializeSpendingCondition(spendingConditions[i]); - const serialized2 = serializeSpendingCondition( + const serialized1 = serializeSpendingConditionBytes(spendingConditions[i]); + const serialized2 = serializeSpendingConditionBytes( spendingConditions[(i + 1) % spendingConditions.length] ); @@ -366,8 +367,8 @@ test('Spending conditions', () => { sponsoredArray.push(serialized2); const sponsoredBytes = concatArray(sponsoredArray); - expect(serializeAuthorization(standard)).toEqual(standardBytes); - expect(serializeAuthorization(sponsored)).toEqual(sponsoredBytes); + expect(serializeAuthorizationBytes(standard)).toEqual(standardBytes); + expect(serializeAuthorizationBytes(sponsored)).toEqual(sponsoredBytes); } }); @@ -592,7 +593,7 @@ test('Invalid spending conditions', () => { ]; // we can serialize the invalid p2wpkh uncompressed condition, but we can't deserialize it - const serializedSPUncompressedKeys = serializeSpendingCondition(badP2WpkhUncompressedSP); + const serializedSPUncompressedKeys = serializeSpendingConditionBytes(badP2WpkhUncompressedSP); expect(new Uint8Array(badP2WpkhUncompressedBytes)).toEqual(serializedSPUncompressedKeys); expect(() => @@ -667,7 +668,7 @@ test('Single sig P2PKH spending condition', () => { ]; for (let i = 0; i < spendingConditions.length; i++) { - const serializedSpendingCondition = serializeSpendingCondition(spendingConditions[i]); + const serializedSpendingCondition = serializeSpendingConditionBytes(spendingConditions[i]); expect(bytesToHex(serializedSpendingCondition)).toEqual( bytesToHex(new Uint8Array(spendingConditionsBytes[i])) ); @@ -712,7 +713,7 @@ test('Single sig P2WPKH spending condition', () => { const spendingConditionsBytes = [spendingConditionP2WpkhCompressedBytes]; for (let i = 0; i < spendingConditions.length; i++) { - const serializedSpendingCondition = serializeSpendingCondition(spendingConditions[i]); + const serializedSpendingCondition = serializeSpendingConditionBytes(spendingConditions[i]); expect(bytesToHex(serializedSpendingCondition)).toEqual( bytesToHex(new Uint8Array(spendingConditionsBytes[i])) ); diff --git a/packages/transactions/tests/builder.test.ts b/packages/transactions/tests/builder.test.ts index f2c9a8dc2..90a2c4208 100644 --- a/packages/transactions/tests/builder.test.ts +++ b/packages/transactions/tests/builder.test.ts @@ -9,7 +9,7 @@ import { hexToBytes, utf8ToBytes, } from '@stacks/common'; -import { STACKS_MAINNET, STACKS_TESTNET, TransactionVersion } from '@stacks/network'; +import { STACKS_MAINNET, STACKS_TESTNET } from '@stacks/network'; import * as fs from 'fs'; import fetchMock from 'jest-fetch-mock'; import { @@ -28,21 +28,21 @@ import { addressFromPublicKeys, addressToString, broadcastTransaction, - fetchCallReadOnlyFunction, createMessageSignature, createStacksPublicKey, createTokenTransferPayload, createTransactionAuthField, + fetchCallReadOnlyFunction, + fetchContractMapEntry, fetchFeeEstimate, fetchFeeEstimateTransaction, - fetchContractMapEntry, fetchNonce, postConditionToWire, privateKeyToPublic, publicKeyIsCompressed, publicKeyToHex, serializePayloadBytes, - serializePostConditionBytes, + serializePostConditionWireBytes, serializePublicKeyBytes, } from '../src'; import { BytesReader } from '../src/BytesReader'; @@ -92,7 +92,7 @@ import { compressPrivateKey, makeRandomPrivKey } from '../src/keys'; import { FungiblePostCondition, PostCondition, StxPostCondition } from '../src/postcondition-types'; import { TransactionSigner } from '../src/signer'; import { - StacksTransaction, + StacksTransactionWire, deserializeTransaction, estimateTransactionByteLength, transactionToHex, @@ -100,9 +100,9 @@ import { import { cloneDeep, randomBytes } from '../src/utils'; function setSignature( - unsignedTransaction: StacksTransaction, + unsignedTransaction: StacksTransactionWire, signature: string | Uint8Array -): StacksTransaction { +): StacksTransactionWire { const parsedSig = typeof signature === 'string' ? signature : bytesToHex(signature); const tx = cloneDeep(unsignedTransaction); if (!tx.auth.spendingCondition) { @@ -960,7 +960,7 @@ test('Make contract-call with post conditions', async () => { nonce: 1, network: STACKS_TESTNET, postConditions, - postConditionMode: PostConditionMode.Deny, + postConditionMode: 'deny', }); expect(() => transaction.verifyOrigin()).not.toThrow(); @@ -1436,8 +1436,11 @@ test('Make sponsored STX token transfer', async () => { sponsorFee ); const authorization = createSponsoredAuth(baseSpendingCondition, sponsorSpendingCondition); - const transactionVersion = TransactionVersion.Mainnet; - const sponsoredTransaction = new StacksTransaction(transactionVersion, authorization, payload); + const sponsoredTransaction = new StacksTransactionWire({ + network: STACKS_MAINNET, + auth: authorization, + payload, + }); const signer = new TransactionSigner(sponsoredTransaction); signer.signOrigin(senderKey); @@ -1944,7 +1947,7 @@ test('Make contract-call with network ABI validation', async () => { nonce: 1, network: STACKS_TESTNET, validateWithAbi: true, - postConditionMode: PostConditionMode.Allow, + postConditionMode: 'allow', }); expect(fetchMock.mock.calls.length).toEqual(1); @@ -2143,11 +2146,11 @@ test('Post-conditions with amount larger than 8 bytes throw an error', () => { }; expect(() => { - serializePostConditionBytes(postConditionToWire(stxPc)); + serializePostConditionWireBytes(postConditionToWire(stxPc)); }).toThrowError('The post-condition amount may not be larger than 8 bytes'); expect(() => { - serializePostConditionBytes(postConditionToWire(fungiblePc)); + serializePostConditionWireBytes(postConditionToWire(fungiblePc)); }).toThrowError('The post-condition amount may not be larger than 8 bytes'); }); diff --git a/packages/transactions/tests/postcondition.test.ts b/packages/transactions/tests/postcondition.test.ts index 6c0497189..d101151e6 100644 --- a/packages/transactions/tests/postcondition.test.ts +++ b/packages/transactions/tests/postcondition.test.ts @@ -3,6 +3,7 @@ import { Cl, ContractPrincipalWire, FungiblePostConditionWire, + LengthPrefixedList, NonFungiblePostConditionWire, PostConditionWire, STXPostConditionWire, @@ -220,7 +221,7 @@ describe('origin postcondition', () => { expect(() => { const tx = deserializeTransaction(txHex); - const pc = tx.postConditions.values[0] as PostConditionWire; + const pc = (tx.postConditions as LengthPrefixedList).values[0] as PostConditionWire; expect(pc.principal.prefix).toBe(PostConditionPrincipalId.Origin); }).not.toThrow(); }); diff --git a/packages/transactions/tests/transaction.test.ts b/packages/transactions/tests/transaction.test.ts index 27ffa0465..54113eaee 100644 --- a/packages/transactions/tests/transaction.test.ts +++ b/packages/transactions/tests/transaction.test.ts @@ -1,5 +1,11 @@ import { bytesToHex, hexToBytes } from '@stacks/common'; -import { DEFAULT_CHAIN_ID, TransactionVersion } from '@stacks/network'; +import { + ChainId, + DEFAULT_CHAIN_ID, + STACKS_MAINNET, + STACKS_TESTNET, + TransactionVersion, +} from '@stacks/network'; import fetchMock from 'jest-fetch-mock'; import { BytesReader } from '../src/BytesReader'; import { @@ -14,7 +20,6 @@ import { import { contractPrincipalCV, standardPrincipalCV } from '../src/clarity'; import { AddressHashMode, - AnchorMode, AuthType, FungibleConditionCode, PostConditionMode, @@ -23,6 +28,7 @@ import { createStacksPublicKey, privateKeyToPublic, publicKeyToHex } from '../sr import { CoinbasePayloadToAltRecipient, + LengthPrefixedList, Pc, STXPostConditionWire, TokenTransferPayloadWire, @@ -34,7 +40,7 @@ import { import { postConditionToWire } from '../src/postcondition'; import { TransactionSigner } from '../src/signer'; import { - StacksTransaction, + StacksTransactionWire, deserializeTransaction, serializeTransaction, transactionToHex, @@ -46,9 +52,8 @@ beforeEach(() => { test('STX token transfer transaction serialization and deserialization', () => { const transactionVersion = TransactionVersion.Testnet; - const chainId = DEFAULT_CHAIN_ID; + const chainId = ChainId.Testnet; - const anchorMode = AnchorMode.Any; const postConditionMode = PostConditionMode.Deny; const address = 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159'; @@ -76,12 +81,12 @@ test('STX token transfer transaction serialization and deserialization', () => { }); const postConditions = createLPList([postCondition]); - const transaction = new StacksTransaction( - transactionVersion, - authorization, + const transaction = new StacksTransactionWire({ + network: STACKS_TESTNET, + auth: authorization, payload, - postConditions - ); + postConditions, + }); const signer = new TransactionSigner(transaction); signer.signOrigin(secretKey); @@ -102,7 +107,7 @@ test('STX token transfer transaction serialization and deserialization', () => { bytesToHex(serialized) ); - expect(deserialized.version).toBe(transactionVersion); + expect(deserialized.transactionVersion).toBe(transactionVersion); expect(deserialized.chainId).toBe(chainId); expect(deserialized.auth.authType).toBe(authType); expect((deserialized.auth.spendingCondition! as SingleSigSpendingCondition).hashMode).toBe( @@ -110,11 +115,11 @@ test('STX token transfer transaction serialization and deserialization', () => { ); expect(deserialized.auth.spendingCondition!.nonce!.toString()).toBe(nonce.toString()); expect(deserialized.auth.spendingCondition!.fee!.toString()).toBe(fee.toString()); - expect(deserialized.anchorMode).toBe(anchorMode); expect(deserialized.postConditionMode).toBe(postConditionMode); expect(deserialized.postConditions.values.length).toBe(1); - const deserializedPostCondition = deserialized.postConditions.values[0] as STXPostConditionWire; + const deserializedPostCondition = (deserialized.postConditions as LengthPrefixedList) + .values[0] as STXPostConditionWire; if (!('address' in deserializedPostCondition.principal)) throw TypeError; expect(deserializedPostCondition.principal.address).toStrictEqual(recipient.address); expect(deserializedPostCondition.conditionCode).toBe(FungibleConditionCode.GreaterEqual); @@ -127,9 +132,8 @@ test('STX token transfer transaction serialization and deserialization', () => { test('STX token transfer transaction fee setting', () => { const transactionVersion = TransactionVersion.Testnet; - const chainId = DEFAULT_CHAIN_ID; + const chainId = ChainId.Testnet; - const anchorMode = AnchorMode.Any; const postConditionMode = PostConditionMode.Deny; const address = 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159'; @@ -153,12 +157,12 @@ test('STX token transfer transaction fee setting', () => { const postConditions = createLPList([postCondition]); - const transaction = new StacksTransaction( - transactionVersion, - authorization, + const transaction = new StacksTransactionWire({ + network: STACKS_TESTNET, + auth: authorization, payload, - postConditions - ); + postConditions, + }); const signer = new TransactionSigner(transaction); signer.signOrigin(secretKey); @@ -177,7 +181,7 @@ test('STX token transfer transaction fee setting', () => { const postSetFeeSerialized = transaction.serializeBytes(); const postSetFeeDeserialized = deserializeTransaction(new BytesReader(postSetFeeSerialized)); - expect(postSetFeeDeserialized.version).toBe(transactionVersion); + expect(postSetFeeDeserialized.transactionVersion).toBe(transactionVersion); expect(postSetFeeDeserialized.chainId).toBe(chainId); expect(postSetFeeDeserialized.auth.authType).toBe(authType); expect( @@ -185,11 +189,10 @@ test('STX token transfer transaction fee setting', () => { ).toBe(addressHashMode); expect(postSetFeeDeserialized.auth.spendingCondition!.nonce!.toString()).toBe(nonce.toString()); expect(postSetFeeDeserialized.auth.spendingCondition!.fee!.toString()).toBe(setFee.toString()); - expect(postSetFeeDeserialized.anchorMode).toBe(anchorMode); expect(postSetFeeDeserialized.postConditionMode).toBe(postConditionMode); expect(postSetFeeDeserialized.postConditions.values.length).toBe(1); - const deserializedPostCondition = postSetFeeDeserialized.postConditions + const deserializedPostCondition = (postSetFeeDeserialized.postConditions as LengthPrefixedList) .values[0] as STXPostConditionWire; if (!('address' in deserializedPostCondition.principal)) throw TypeError; expect(deserializedPostCondition.principal.address).toStrictEqual(recipient.address); @@ -223,17 +226,15 @@ test('STX token transfer transaction multi-sig serialization and deserialization fee ); const authType = AuthType.Standard; - const originAuth = createStandardAuth(spendingCondition); + const auth = createStandardAuth(spendingCondition); - const originAddress = originAuth.spendingCondition?.signer; + const originAddress = auth.spendingCondition?.signer; expect(originAddress).toEqual('a23ea89d6529ac48ac766f720e480beec7f19273'); const transactionVersion = TransactionVersion.Mainnet; const chainId = DEFAULT_CHAIN_ID; - const anchorMode = AnchorMode.Any; - const address = 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159'; const recipientCV = standardPrincipalCV(address); const amount = 2500000; @@ -242,7 +243,7 @@ test('STX token transfer transaction multi-sig serialization and deserialization const payload = createTokenTransferPayload(recipientCV, amount, memo); - const transaction = new StacksTransaction(transactionVersion, originAuth, payload); + const transaction = new StacksTransactionWire({ network: STACKS_MAINNET, auth, payload }); const signer = new TransactionSigner(transaction); signer.signOrigin(privKeys[0]); @@ -253,7 +254,7 @@ test('STX token transfer transaction multi-sig serialization and deserialization const serialized = transaction.serializeBytes(); const deserialized = deserializeTransaction(new BytesReader(serialized)); - expect(deserialized.version).toBe(transactionVersion); + expect(deserialized.transactionVersion).toBe(transactionVersion); expect(deserialized.chainId).toBe(chainId); expect(deserialized.auth.authType).toBe(authType); expect((deserialized.auth.spendingCondition! as MultiSigSpendingCondition).hashMode).toBe( @@ -261,7 +262,6 @@ test('STX token transfer transaction multi-sig serialization and deserialization ); expect(deserialized.auth.spendingCondition!.nonce!.toString()).toBe(nonce.toString()); expect(deserialized.auth.spendingCondition!.fee!.toString()).toBe(fee.toString()); - expect(deserialized.anchorMode).toBe(anchorMode); expect(deserialized.postConditionMode).toBe(PostConditionMode.Deny); expect(deserialized.postConditions.values.length).toBe(0); @@ -296,11 +296,10 @@ test('STX token transfer transaction multi-sig uncompressed keys serialization a ); spendingCondition.hashMode = AddressHashMode.P2WSH; - const originAuth = createStandardAuth(spendingCondition); - const originAddress = originAuth.spendingCondition?.signer; + const auth = createStandardAuth(spendingCondition); + const originAddress = auth.spendingCondition?.signer; expect(originAddress).toEqual('73a8b4a751a678fe83e9d35ce301371bb3d397f7'); - const transactionVersion = TransactionVersion.Mainnet; const address = 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159'; const recipientCV = standardPrincipalCV(address); const amount = 2500000; @@ -309,7 +308,7 @@ test('STX token transfer transaction multi-sig uncompressed keys serialization a const payload = createTokenTransferPayload(recipientCV, amount, memo); - const transaction = new StacksTransaction(transactionVersion, originAuth, payload); + const transaction = new StacksTransactionWire({ network: STACKS_MAINNET, auth, payload }); const signer = new TransactionSigner(transaction); signer.signOrigin(privKeys[0]); @@ -324,12 +323,6 @@ test('STX token transfer transaction multi-sig uncompressed keys serialization a }); test('Sponsored STX token transfer transaction serialization and deserialization', () => { - const transactionVersion = TransactionVersion.Testnet; - const chainId = DEFAULT_CHAIN_ID; - - const anchorMode = AnchorMode.Any; - const postConditionMode = PostConditionMode.Deny; - const address = 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159'; // const recipient = createStandardPrincipal(address); const recipientCV = standardPrincipalCV(address); @@ -355,9 +348,9 @@ test('Sponsored STX token transfer transaction serialization and deserialization ); const authType = AuthType.Sponsored; - const authorization = createSponsoredAuth(spendingCondition, sponsorSpendingCondition); + const auth = createSponsoredAuth(spendingCondition, sponsorSpendingCondition); - const transaction = new StacksTransaction(transactionVersion, authorization, payload); + const transaction = new StacksTransactionWire({ network: STACKS_TESTNET, auth, payload }); const signer = new TransactionSigner(transaction); signer.signOrigin(secretKey); @@ -367,8 +360,8 @@ test('Sponsored STX token transfer transaction serialization and deserialization const serialized = transaction.serialize(); const deserialized = deserializeTransaction(serialized); - expect(deserialized.version).toBe(transactionVersion); - expect(deserialized.chainId).toBe(chainId); + expect(deserialized.transactionVersion).toBe(TransactionVersion.Testnet); + expect(deserialized.chainId).toBe(ChainId.Testnet); expect(deserialized.auth.authType).toBe(authType); expect(deserialized.auth.spendingCondition!.hashMode).toBe(addressHashMode); expect(deserialized.auth.spendingCondition!.nonce!.toString()).toBe(nonce.toString()); @@ -382,8 +375,7 @@ test('Sponsored STX token transfer transaction serialization and deserialization expect( (deserialized.auth as SponsoredAuthorization).sponsorSpendingCondition!.fee!.toString() ).toBe(fee.toString()); - expect(deserialized.anchorMode).toBe(anchorMode); - expect(deserialized.postConditionMode).toBe(postConditionMode); + expect(deserialized.postConditionMode).toBe(PostConditionMode.Deny); const deserializedPayload = deserialized.payload as TokenTransferPayloadWire; expect(deserializedPayload.recipient).toEqual(recipientCV); @@ -396,7 +388,6 @@ test('Coinbase pay to alt standard principal recipient deserialization', () => { '0x80800000000400fd3cd910d78fe7c4cd697d5228e51a912ff2ba740000000000000004000000000000000001008d36064b250dba5d3221ac235a9320adb072cfc23cd63511e6d814f97f0302e66c2ece80d7512df1b3e90ca6dce18179cb67b447973c739825ce6c6756bc247d010200000000050000000000000000000000000000000000000000000000000000000000000000051aba27f99e007c7f605a8305e318c1abde3cd220ac'; const deserializedTx = deserializeTransaction(serializedTx); - expect(deserializedTx.anchorMode).toBe(AnchorMode.OnChainOnly); expect((deserializedTx.auth.spendingCondition as SingleSigSpendingCondition).signature.data).toBe( '008d36064b250dba5d3221ac235a9320adb072cfc23cd63511e6d814f97f0302e66c2ece80d7512df1b3e90ca6dce18179cb67b447973c739825ce6c6756bc247d' ); @@ -409,7 +400,7 @@ test('Coinbase pay to alt standard principal recipient deserialization', () => { expect(deserializedTx.txid()).toBe( '449f5ea5c541bbbbbf7a1bff2434c449dca2ae3cdc52ba8d24b0bd0d3632d9bc' ); - expect(deserializedTx.version).toBe(TransactionVersion.Testnet); + expect(deserializedTx.transactionVersion).toBe(TransactionVersion.Testnet); }); test('Coinbase pay to alt contract principal recipient deserialization', () => { @@ -418,7 +409,6 @@ test('Coinbase pay to alt contract principal recipient deserialization', () => { '0x8080000000040055a0a92720d20398211cd4c7663d65d018efcc1f00000000000000030000000000000000010118da31f542913e8c56961b87ee4794924e655a28a2034e37ef4823eeddf074747285bd6efdfbd84eecdf62cffa7c1864e683c688f4c105f4db7429066735b4e2010200000000050000000000000000000000000000000000000000000000000000000000000000061aba27f99e007c7f605a8305e318c1abde3cd220ac0b68656c6c6f5f776f726c64'; const deserializedTx = deserializeTransaction(serializedTx); - expect(deserializedTx.anchorMode).toBe(AnchorMode.OnChainOnly); expect((deserializedTx.auth.spendingCondition as SingleSigSpendingCondition).signature.data).toBe( '0118da31f542913e8c56961b87ee4794924e655a28a2034e37ef4823eeddf074747285bd6efdfbd84eecdf62cffa7c1864e683c688f4c105f4db7429066735b4e2' ); @@ -431,7 +421,7 @@ test('Coinbase pay to alt contract principal recipient deserialization', () => { expect(deserializedTx.txid()).toBe( 'bd1a9e1d60ca29fc630633170f396f5b6b85c9620bd16d63384ebc5a01a1829b' ); - expect(deserializedTx.version).toBe(TransactionVersion.Testnet); + expect(deserializedTx.transactionVersion).toBe(TransactionVersion.Testnet); }); describe(serializeTransaction.name, () => {