Merge branch 'return_tx_builder' into 'development'

signTransaction returns TransactionBuilder

See merge request GeneralProtocols/xo/stack!100
This commit is contained in:
Jim Hamill
2025-06-18 06:53:31 +00:00
5 changed files with 57 additions and 19 deletions
+33 -2
View File
@@ -68,7 +68,7 @@ export class Address {
): Address { ): Address {
// Attempt to decode as a Cash Address. // Attempt to decode as a Cash Address.
try { try {
return this.fromCashAddr(address, tryPrefixes); return this.fromCashAddr(address, false, tryPrefixes);
} catch (_error) { } catch (_error) {
// And if that fails, attempt to decode as a Base58 Address. // And if that fails, attempt to decode as a Base58 Address.
return this.fromLegacy(address); return this.fromLegacy(address);
@@ -89,6 +89,7 @@ export class Address {
*/ */
public static fromCashAddr( public static fromCashAddr(
cashAddr: string, cashAddr: string,
requireTokenSupport = false,
tryPrefixes: Array<string> = DEFAULT_ADDRESS_PREFIXES, tryPrefixes: Array<string> = DEFAULT_ADDRESS_PREFIXES,
): Address { ): Address {
// Try all prefixes (including empty string) until we find a valid decode // Try all prefixes (including empty string) until we find a valid decode
@@ -105,6 +106,16 @@ export class Address {
// Define the encode type in advance. // Define the encode type in advance.
let encodeType: AddressType; let encodeType: AddressType;
// Determine whether this is a CashToken address.
const isTokenAddress =
decodeResult.type === 'p2pkhWithTokens' ||
decodeResult.type === 'p2shWithTokens';
// If this is not a CashToken Address, but we require a CashToken address, throw an error.
if (!isTokenAddress && requireTokenSupport) {
throw new Error(`Address '${cashAddr}' does not support tokens`);
}
// Determine the encoding to use based on the Decoding Result's Type. // Determine the encoding to use based on the Decoding Result's Type.
switch (decodeResult.type) { switch (decodeResult.type) {
case 'p2pkh': case 'p2pkh':
@@ -365,7 +376,27 @@ export class Address {
tryPrefixes = DEFAULT_ADDRESS_PREFIXES, tryPrefixes = DEFAULT_ADDRESS_PREFIXES,
): boolean { ): boolean {
try { try {
Address.fromCashAddr(address, tryPrefixes); Address.fromCashAddr(address, false, tryPrefixes);
} catch (_error) {
return false;
}
return true;
}
/**
* Checks whether the given address is a valid CashAddr with Token Support.
*
* @param address The address.
*
* @returns boolean indicating whether the address is a valid CashAddr with Token Support.
*/
public static isValidTokenAddress(
address: string,
tryPrefixes = DEFAULT_ADDRESS_PREFIXES,
): boolean {
try {
Address.fromCashAddr(address, true, tryPrefixes);
} catch (_error) { } catch (_error) {
return false; return false;
} }
+2 -1
View File
@@ -1,3 +1,4 @@
import { type TransactionBuilder } from './transaction-builder.js';
import { import {
WalletUTXOs, WalletUTXOs,
type InputTemplateBCH, type InputTemplateBCH,
@@ -207,7 +208,7 @@ export abstract class BaseWallet<
*/ */
abstract signTransaction( abstract signTransaction(
template: Partial<TransactionTemplate>, template: Partial<TransactionTemplate>,
): Promise<Uint8Array>; ): Promise<TransactionBuilder>;
/** /**
* Select UTXOs from this wallet to meet the amounts required and broadcast it to the Blockchain. * Select UTXOs from this wallet to meet the amounts required and broadcast it to the Blockchain.
@@ -13,6 +13,7 @@ import {
encodeTransactionOutput, encodeTransactionOutput,
generateTransaction, generateTransaction,
getDustThreshold, getDustThreshold,
isDustOutput,
} from '@bitauth/libauth'; } from '@bitauth/libauth';
// TODO: This could probably still use a refactor, but isn't terrible anymore. // TODO: This could probably still use a refactor, but isn't terrible anymore.
@@ -303,6 +304,15 @@ export class TransactionBuilder {
throw new Error(`Failed to generate transaction: ${errors}`); throw new Error(`Failed to generate transaction: ${errors}`);
} }
// Check for dust outputs.
const hasDustOutputs = generatedTransaction.transaction.outputs.some(
(output) => isDustOutput(output),
);
if (hasDustOutputs) {
throw new Error('Output amounts must be above the dust threshold');
}
return generatedTransaction.transaction; return generatedTransaction.transaction;
} }
+7 -9
View File
@@ -293,7 +293,7 @@ export class WalletHD extends BaseWallet {
async signTransaction( async signTransaction(
txTemplate: Partial<TransactionTemplate>, txTemplate: Partial<TransactionTemplate>,
): Promise<Uint8Array> { ): Promise<TransactionBuilder> {
// Populate the final transaction template by merging with defaults. // Populate the final transaction template by merging with defaults.
// NOTE: This is just a convenience to make "send" easier to use. // NOTE: This is just a convenience to make "send" easier to use.
const txTemplateFinal: TransactionTemplate = { const txTemplateFinal: TransactionTemplate = {
@@ -317,21 +317,19 @@ export class WalletHD extends BaseWallet {
const unspents = await this.getUnspentDirectives(); const unspents = await this.getUnspentDirectives();
// Build the transaction. // Build the transaction.
const tx = TransactionBuilder.build( return TransactionBuilder.build(
txTemplateFinal, txTemplateFinal,
unspents.toArray(), unspents.toArray(),
changeLockingBytecode, changeLockingBytecode,
); );
// Generate the transaction.
const generatedTxBytes = tx.toBytes();
return generatedTxBytes;
} }
async sendTransaction(txTemplate: Partial<TransactionTemplate>) { async sendTransaction(txTemplate: Partial<TransactionTemplate>) {
// Sign the transaction template. // Generate the transaction.
const generatedTxBytes = await this.signTransaction(txTemplate); const generatedTx = await this.signTransaction(txTemplate);
// Convert the tx to bytes.
const generatedTxBytes = generatedTx.toBytes();
// Broadcast the transaction to the blockchain. // Broadcast the transaction to the blockchain.
await this.blockchain.broadcastTransaction(generatedTxBytes); await this.blockchain.broadcastTransaction(generatedTxBytes);
+5 -7
View File
@@ -360,21 +360,19 @@ export class WalletP2PKH extends BaseWallet {
const unspents = await this.getUnspentDirectives(); const unspents = await this.getUnspentDirectives();
// Build the transaction. // Build the transaction.
const tx = TransactionBuilder.build( return TransactionBuilder.build(
txTemplateFinal, txTemplateFinal,
unspents.toArray(), unspents.toArray(),
changeLockingBytecode, changeLockingBytecode,
); );
// Generate the transaction.
const generatedTxBytes = tx.toBytes();
return generatedTxBytes;
} }
async sendTransaction(txTemplate: Partial<TransactionTemplate>) { async sendTransaction(txTemplate: Partial<TransactionTemplate>) {
// Generate the transaction. // Generate the transaction.
const generatedTxBytes = await this.signTransaction(txTemplate); const generatedTx = await this.signTransaction(txTemplate);
// Convert the tx to bytes.
const generatedTxBytes = generatedTx.toBytes();
// Broadcast the transaction to the blockchain. // Broadcast the transaction to the blockchain.
await this.blockchain.broadcastTransaction(generatedTxBytes); await this.blockchain.broadcastTransaction(generatedTxBytes);