UNPKG

@dolaned/wallet-sdk-ts

Version:

Wallet SDK for the Nexa blockchain

190 lines (170 loc) 8.59 kB
import { AddressType, TransactionBuilder, } from "libnexa-ts"; import {WatchOnlyAddress} from "../../models/wallet.entities"; import {TransactionCreator} from "./interfaces/TransactionCreator"; import { watchOnlyBuildCreateGroupTransaction, watchOnlyPopulateAndDuplicateTokenAuths, watchOnlyPopulateNexaInputsAndChange, watchOnlyPopulateTokenAuth, watchOnlyPopulateTokenInputsAndChange, watchOnlyPrepareDeleteTransaction } from "../../utils/WatchOnlyTXUtils"; import {isString} from "lodash-es"; import {rostrumProvider} from "../../network/RostrumProvider"; import {PermissionLabel} from "../../models/transaction.entities"; import {isValidNexaAddress} from "../../utils/WalletUtils"; /** * WatchOnlyTransactionCreator extends TransactionCreator to handle transaction creation * for watch-only wallets. It manages addresses without private keys and delegates * UTXO selection and input population to specialized utility functions. */ export default class WatchOnlyTransactionCreator extends TransactionCreator { /** Addresses that need to be signed with (populated during transaction building) */ private _addressesToSignWith: string[] = []; /** Available addresses for input selection and change */ private _availableAddresses: WatchOnlyAddress[] = [] /** * Creates a new WatchOnlyTransactionCreator * @param tx Optional existing transaction builder or transaction data */ constructor(tx?: TransactionBuilder | string | Buffer) { super(tx) } /** * Sets the source addresses for transaction inputs * @param address Single address string, array of addresses, or WatchOnlyAddress objects * @returns This instance for chaining */ public from(address: string | string[] | WatchOnlyAddress[] | WatchOnlyAddress): this { if (isString(address)) { if (!isValidNexaAddress(address, this.network) && !isValidNexaAddress(address, this.network, AddressType.PayToPublicKeyHash)) { throw new Error('Invalid Address.'); } // Single address string this._availableAddresses.push({address: address}); } else if(Array.isArray(address)) { // Array of addresses or WatchOnlyAddress objects address.forEach((addr) => { if (isString(addr)) { if (!isValidNexaAddress(addr, this.network) && !isValidNexaAddress(addr, this.network, AddressType.PayToPublicKeyHash)) { throw new Error('Invalid Address.'); } // String address this._availableAddresses.push({address: addr}); } else if (addr && typeof addr === 'object' && 'address' in addr) { // WatchOnlyAddress object this._availableAddresses.push(<WatchOnlyAddress>addr); } }) } else if(address.address != null) { // Single WatchOnlyAddress object this._availableAddresses.push(<WatchOnlyAddress>address) } return this } /** * Adds a token minting operation to the transaction * @param token Token ID to mint * @param amount Amount to mint * @param toAddr Destination address for minted tokens * @returns This instance for chaining */ public mint(token: string, amount: string, toAddr:string): this { this.builder.push(async() => { this.tokenAction(toAddr, amount, token, 'mint') }) return this } /** * Adds a token melting operation to the transaction * @param token Token ID to melt * @param amount Amount to melt * @param toAddr Destination address for melted tokens * @returns This instance for chaining */ public melt(token: string, amount: string, toAddr:string): this { this.builder.push(async() => { this.tokenAction(toAddr, amount, token, 'melt') }) return this } /** * Populates the transaction with inputs and outputs based on the configured actions. * Handles different token operations (mint, melt, group creation, etc.) and * populates NEXA inputs for transaction fees. * @returns This instance for chaining */ public populate(): this { this.builder.push(async () => { let tokenAddresses: string[] = [] let nexaAddresses: string[] = [] // Process token operations if any are configured if(this.tokens.size > 0) { for (const tokenAction of this.tokens) { if(tokenAction.action == 'mint' || tokenAction.action == 'melt') { // Handle token minting/melting - requires authority tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateTokenAuth(this.transactionBuilder, this._availableAddresses, tokenAction.token!, tokenAction.action)) } else if (tokenAction.action == 'group') { // Handle group token creation tokenAddresses = tokenAddresses.concat(await watchOnlyBuildCreateGroupTransaction(this.transactionBuilder, this._availableAddresses, tokenAction.token!, this.network)) } else if (tokenAction.action == 'subgroup') { // Handle subgroup token creation tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateTokenAuth(this.transactionBuilder, this._availableAddresses, tokenAction.parentToken!, tokenAction.action, tokenAction.token)); } else if(tokenAction.action == 'renew') { // Handle authority renewal tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateAndDuplicateTokenAuths(this.transactionBuilder, this._availableAddresses, tokenAction.token!, tokenAction.extraData!.perms!, tokenAction.extraData?.address!)) } else if(tokenAction.action == 'delete') { // Handle authority deletion tokenAddresses = tokenAddresses.concat(await watchOnlyPrepareDeleteTransaction(this.transactionBuilder, this._availableAddresses, tokenAction.extraData!.outpoint!)) } else { // Handle regular token transfers tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateTokenInputsAndChange(this.transactionBuilder, this._availableAddresses, tokenAction.token!, tokenAction.amount)) } // Accumulate addresses that need signing this._addressesToSignWith!.concat(tokenAddresses) } } // Populate NEXA inputs for transaction fees and change nexaAddresses = nexaAddresses.concat(await watchOnlyPopulateNexaInputsAndChange(this.transactionBuilder, this._availableAddresses, this.totalValue, this.txOptions)) // Combine all addresses that need signing this._addressesToSignWith! = tokenAddresses.concat(nexaAddresses) }) return this } /** * Parse transaction from buffer (not implemented for watch-only) * @param tx Transaction buffer * @returns This instance for chaining * @throws Error indicating method not implemented */ public parseTxBuffer(tx: Buffer): this { this.builder.push(async () => { this.transactionBuilder = new TransactionBuilder(tx); }) return this } /** * Parse transaction from hex string (not implemented for watch-only) * @param tx Transaction hex string * @returns This instance for chaining * @throws Error indicating method not implemented */ public parseTxHex(tx: string) { this.builder.push(async () => { const txBuilder = new TransactionBuilder(tx) const newTxBuilder = new TransactionBuilder() const oldInputs = txBuilder.transaction.inputs for (const input of oldInputs) { const utxo = await rostrumProvider.getUtxo(input.outpoint.toString('hex')) newTxBuilder.from({ outpoint: utxo.tx_hash, amount: utxo.amount, scriptPubKey: utxo.scriptpubkey }) } newTxBuilder.transaction.outputs = txBuilder.transaction.outputs this.transactionBuilder = newTxBuilder; }) return this } }