diff --git a/package-lock.json b/package-lock.json index 045a39bf..1e94b095 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37875,7 +37875,7 @@ }, "packages/bitcoin": { "name": "@meshsdk/bitcoin", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "bip174": "^3.0.0", @@ -39265,7 +39265,7 @@ }, "packages/mesh-common": { "name": "@meshsdk/common", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { "bech32": "^2.0.0", @@ -39283,11 +39283,11 @@ }, "packages/mesh-contract": { "name": "@meshsdk/contract", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { - "@meshsdk/common": "1.9.0-beta.83", - "@meshsdk/core": "1.9.0-beta.83" + "@meshsdk/common": "1.9.0-beta.84", + "@meshsdk/core": "1.9.0-beta.84" }, "devDependencies": { "@meshsdk/configs": "*", @@ -39298,15 +39298,15 @@ }, "packages/mesh-core": { "name": "@meshsdk/core", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { - "@meshsdk/common": "1.9.0-beta.83", - "@meshsdk/core-cst": "1.9.0-beta.83", - "@meshsdk/provider": "1.9.0-beta.83", - "@meshsdk/react": "1.9.0-beta.83", - "@meshsdk/transaction": "1.9.0-beta.83", - "@meshsdk/wallet": "1.9.0-beta.83" + "@meshsdk/common": "1.9.0-beta.84", + "@meshsdk/core-cst": "1.9.0-beta.84", + "@meshsdk/provider": "1.9.0-beta.84", + "@meshsdk/react": "1.9.0-beta.84", + "@meshsdk/transaction": "1.9.0-beta.84", + "@meshsdk/wallet": "1.9.0-beta.84" }, "devDependencies": { "@meshsdk/configs": "*", @@ -39317,10 +39317,10 @@ }, "packages/mesh-core-csl": { "name": "@meshsdk/core-csl", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { - "@meshsdk/common": "1.9.0-beta.83", + "@meshsdk/common": "1.9.0-beta.84", "@sidan-lab/whisky-js-browser": "^1.0.11", "@sidan-lab/whisky-js-nodejs": "^1.0.11", "@types/base32-encoding": "^1.0.2", @@ -39330,7 +39330,7 @@ }, "devDependencies": { "@meshsdk/configs": "*", - "@meshsdk/provider": "1.9.0-beta.83", + "@meshsdk/provider": "1.9.0-beta.84", "@types/json-bigint": "^1.0.4", "eslint": "^8.57.0", "ts-jest": "^29.1.4", @@ -39340,7 +39340,7 @@ }, "packages/mesh-core-cst": { "name": "@meshsdk/core-cst", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { "@cardano-sdk/core": "^0.45.5", @@ -39351,7 +39351,7 @@ "@harmoniclabs/pair": "^1.0.0", "@harmoniclabs/plutus-data": "1.2.4", "@harmoniclabs/uplc": "1.2.4", - "@meshsdk/common": "1.9.0-beta.83", + "@meshsdk/common": "1.9.0-beta.84", "@types/base32-encoding": "^1.0.2", "base32-encoding": "^1.0.0", "bech32": "^2.0.0", @@ -39370,11 +39370,11 @@ }, "packages/mesh-hydra": { "name": "@meshsdk/hydra", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "dependencies": { - "@meshsdk/common": "1.9.0-beta.83", - "@meshsdk/core": "1.9.0-beta.83", - "@meshsdk/core-cst": "1.9.0-beta.83", + "@meshsdk/common": "1.9.0-beta.84", + "@meshsdk/core": "1.9.0-beta.84", + "@meshsdk/core-cst": "1.9.0-beta.84", "axios": "^1.7.2" }, "devDependencies": { @@ -39426,12 +39426,12 @@ }, "packages/mesh-provider": { "name": "@meshsdk/provider", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { - "@meshsdk/bitcoin": "1.9.0-beta.83", - "@meshsdk/common": "1.9.0-beta.83", - "@meshsdk/core-cst": "1.9.0-beta.83", + "@meshsdk/bitcoin": "1.9.0-beta.84", + "@meshsdk/common": "1.9.0-beta.84", + "@meshsdk/core-cst": "1.9.0-beta.84", "@utxorpc/sdk": "^0.6.7", "@utxorpc/spec": "^0.16.0", "axios": "^1.7.2", @@ -39447,14 +39447,14 @@ }, "packages/mesh-react": { "name": "@meshsdk/react", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { "@fabianbormann/cardano-peer-connect": "^1.2.18", - "@meshsdk/bitcoin": "1.9.0-beta.83", - "@meshsdk/common": "1.9.0-beta.83", - "@meshsdk/transaction": "1.9.0-beta.83", - "@meshsdk/wallet": "1.9.0-beta.83", + "@meshsdk/bitcoin": "1.9.0-beta.84", + "@meshsdk/common": "1.9.0-beta.84", + "@meshsdk/transaction": "1.9.0-beta.84", + "@meshsdk/wallet": "1.9.0-beta.84", "@meshsdk/web3-sdk": "0.0.50", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", @@ -39492,10 +39492,10 @@ }, "packages/mesh-svelte": { "name": "@meshsdk/svelte", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { - "@meshsdk/core": "1.9.0-beta.83", + "@meshsdk/core": "1.9.0-beta.84", "bits-ui": "1.0.0-next.65" }, "devDependencies": { @@ -39521,14 +39521,14 @@ }, "packages/mesh-transaction": { "name": "@meshsdk/transaction", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { "@cardano-sdk/core": "^0.45.5", "@cardano-sdk/input-selection": "^0.13.33", "@cardano-sdk/util": "^0.15.5", - "@meshsdk/common": "1.9.0-beta.83", - "@meshsdk/core-cst": "1.9.0-beta.83", + "@meshsdk/common": "1.9.0-beta.84", + "@meshsdk/core-cst": "1.9.0-beta.84", "json-bigint": "^1.0.0" }, "devDependencies": { @@ -39541,12 +39541,12 @@ }, "packages/mesh-wallet": { "name": "@meshsdk/wallet", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { - "@meshsdk/common": "1.9.0-beta.83", - "@meshsdk/core-cst": "1.9.0-beta.83", - "@meshsdk/transaction": "1.9.0-beta.83", + "@meshsdk/common": "1.9.0-beta.84", + "@meshsdk/core-cst": "1.9.0-beta.84", + "@meshsdk/transaction": "1.9.0-beta.84", "@simplewebauthn/browser": "^13.0.0" }, "devDependencies": { @@ -39694,7 +39694,7 @@ }, "packages/midnight-setup": { "name": "@meshsdk/midnight-setup", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { "@midnight-ntwrk/compact-runtime": "^0.8.1", @@ -39721,7 +39721,7 @@ }, "scripts/mesh-cli": { "name": "meshjs", - "version": "1.9.0-beta.83", + "version": "1.9.0-beta.84", "license": "Apache-2.0", "dependencies": { "@sidan-lab/cardano-bar": "^0.0.7", diff --git a/packages/bitcoin/src/wallets/embedded/index.ts b/packages/bitcoin/src/wallets/embedded/index.ts index 1ed42414..b1bae217 100644 --- a/packages/bitcoin/src/wallets/embedded/index.ts +++ b/packages/bitcoin/src/wallets/embedded/index.ts @@ -1,6 +1,7 @@ import type { Network } from "bitcoinjs-lib"; import { BIP32Interface } from "bip32"; import { mnemonicToSeedSync, validateMnemonic } from "bip39"; + import { bip32, bip39, bitcoin, ECPair } from "../../core"; import { IBitcoinProvider } from "../../interfaces/provider"; import { UTxO } from "../../types/utxo"; @@ -59,14 +60,22 @@ export class EmbeddedWallet { } /** - * Returns the wallet's Bitcoin addresses following Xverse API format. + * Apps can specify which wallet addresses they require: Bitcoin ordinals address or Bitcoin payment address, + * using the `purposes` request parameter. The `message` request param gives apps the option to display a + * message to the user when requesting their addresses. (note: ignored for embedded wallets) * - * @param purposes Array of address purposes to request: - * - `'ordinals'` for managing ordinals (should return P2TR taproot address) - * - `'payment'` for managing bitcoin (returns P2WPKH address) - * TODO: Should follow Xverse docs to return taproot address for ordinals purpose - * @param message Optional message to display in request prompt (ignored for embedded wallets) - * @returns {Promise} Array of address objects with address, publicKey, purpose, addressType, network, and walletType + * @param purposes Array of strings used to specify the purpose of the address(es) to request: + * - `'ordinals'` is preferably used to manage the user's ordinals + * - `'payment'` is preferably used to manage the user's bitcoin + * Example: `['ordinals', 'payment']` + * @param message Optional - a message to be displayed to the user in the request prompt (ignored for embedded wallets) + * @returns {Promise} Once resolved, returns an array of the user's wallet address objects: + * - `address`: string - the user's connected wallet address + * - `publicKey`: A hex string representing the bytes of the public key of the account. You can use this to construct partially signed Bitcoin transactions (PSBT) + * - `purpose`: string - The purpose of the address ('ordinals' for managing ordinals, 'payment' for managing bitcoin) + * - `addressType`: string - the address's format ('P2TR' for ordinals, 'P2SH' for payment, 'P2WPKH' for payment using Ledger) + * - `network`: string - the network where the address is being used ('mainnet', 'testnet', 'signet') + * - `walletType`: string - the type of wallet used for the account ('ledger' for Ledger devices, 'software' otherwise) * @throws {Error} If wallet is not properly initialized. */ async getAddresses( @@ -168,10 +177,22 @@ export class EmbeddedWallet { } /** - * Signs a message following Xverse API format. + * You can request to sign a message with wallet's Bitcoin addresses, by invoking the `signMessage` method. + * + * @param params - Object containing the following parameters: + * - `address`: a string representing the address to use to sign the message + * - `message`: a string representing the message to be signed by the wallet + * - `protocol` (optional): By default, signMessage will use two type of signatures depending on the Bitcoin address used for signing: + * - ECDSA signatures over the secp256k1 curve when signing with the Bitcoin payment (`p2sh`) address + * - BIP322 signatures when signing with the Bitcoin Ordinals (`p2tr`) address or a Ledger-based Bitcoin payment address (`p2wpkh`) * - * @param params - Object containing address, message, and optional protocol - * @returns Promise resolving to signature result + * You have the option to specify your preferred signature type with the `protocol` parameter: + * - `ECDSA` to request ECDSA signatures over the secp256k1 curve (available for payment addresses only: `p2sh` and `p2wpkh`) + * - `BIP322` to request BIP322 signatures (available for all payment (`p2sh` and `p2wpkh`) & ordinals addresses (`p2tr`)) + * @returns Promise that resolves to the `SignMessageResult` object containing: + * - `signature`: a string representing the signed message + * - `messageHash`: a string representing the hash of the message + * - `address`: a string representing the address used for signing * @throws {Error} If the wallet is read-only or private key is not available. */ async signMessage(params: SignMessageParams): Promise { @@ -220,9 +241,24 @@ export class EmbeddedWallet { } /** - * Sign a PSBT following Xverse API format. - * @param params - Object containing PSBT and signing instructions - * @returns Promise resolving to signed PSBT result + * You can use the `signPsbt` method to request the signature of a Partially Signed Bitcoin Transaction (PSBT) + * from Bitcoin wallet addresses. + * + * The PSBT to be signed must be base64-encoded. You can use any Bitcoin library to construct this transaction. + * + * @param params - Object containing the following parameters: + * - `psbt`: a string representing the psbt to sign, encoded in base64 + * - `signInputs`: A Record where: + * - the keys are the addresses to use for signing + * - the values are the indexes of the inputs to sign with each address + * - `broadcast`: a boolean flag that specifies whether to broadcast the signed transaction after signature + * + * Depending on your use case, you can request that the PSBT be finalized and broadcasted after signing, + * by setting the broadcast flag to true. Otherwise, the signed PSBT will be returned in the response without broadcasting. + * + * @returns Promise that resolves to the `SignPsbtResult` object containing: + * - `psbt`: The base64 encoded signed PSBT + * - `txid`: The transaction id as a hex-encoded string (only returned if the transaction was broadcasted) * @throws {Error} If the wallet is read-only or private key is not available. */ async signPsbt(params: SignPsbtParams): Promise { @@ -279,9 +315,14 @@ export class EmbeddedWallet { } /** - * Send Bitcoin to recipients following Xverse API format. - * @param params - Object containing recipients array - * @returns Promise resolving to transaction result + * You can use the `sendTransfer` method to request a transfer of any amount of Bitcoin to one or more recipients from the wallet. + * + * @param params - Object containing the following parameters: + * - `recipients`: an array of objects with properties: + * - `address`: a string representing the recipient's address + * - `amount`: a number representing the amount of Bitcoin to send, denominated in satoshis (Bitcoin base unit) + * @returns Promise that resolves to the `sendTransferResult` object containing: + * - `txid`: The transaction id as a hex-encoded string * @throws {Error} If the wallet is read-only or provider is not available. */ async sendTransfer(params: SendTransferParams): Promise { @@ -290,7 +331,9 @@ export class EmbeddedWallet { } if (!this._provider) { - throw new Error("`provider` is not defined. Provide a BitcoinProvider for sending."); + throw new Error( + "`provider` is not defined. Provide a BitcoinProvider for sending.", + ); } if (!this._wallet) { @@ -327,8 +370,14 @@ export class EmbeddedWallet { } /** - * Get wallet balance following Xverse API format. - * @returns Promise resolving to balance information + * You can use the `getBalance` method to retrieve Bitcoin balance. + * + * The `getBalance` method will return an object representing the connected wallet's payment address BTC holdings: + * + * @returns Promise that resolves to an object containing the following balance information: + * - `confirmed`: a string representing the connected wallet's confirmed BTC balance, i.e. the amount of confirmed BTC which the payment address holds, in satoshis + * - `unconfirmed`: a string representing the connected wallet's unconfirmed BTC balance, i.e. the amount of unconfirmed BTC which the payment address will send/receive as a result of pending mempool transactions, in satoshis (Note: this amount can be negative if the net result of pending mempool transaction decreases the address balance) + * - `total`: a string representing the sum of confirmed and unconfirmed BTC balances * @throws {Error} If provider is not available. */ async getBalance(): Promise { @@ -342,8 +391,12 @@ export class EmbeddedWallet { const address = addresses[0].address; const addressInfo = await this._provider.fetchAddress(address); - const confirmed = addressInfo.chain_stats.funded_txo_sum - addressInfo.chain_stats.spent_txo_sum; - const unconfirmed = addressInfo.mempool_stats.funded_txo_sum - addressInfo.mempool_stats.spent_txo_sum; + const confirmed = + addressInfo.chain_stats.funded_txo_sum - + addressInfo.chain_stats.spent_txo_sum; + const unconfirmed = + addressInfo.mempool_stats.funded_txo_sum - + addressInfo.mempool_stats.spent_txo_sum; const total = confirmed + unconfirmed; return { @@ -354,8 +407,11 @@ export class EmbeddedWallet { } /** - * Sign multiple transactions (Xverse custom method). - * @param params - Object containing network, message, and PSBTs array + * To request signing of multiple PSBTs, you can use the `signMultipleTransactions` function. + * + * @param params - Object containing an array of PSBTs to sign, where each PSBT contains: + * - `psbtBase64`: a valid psbt encoded in base64 + * - `inputsToSign`: an array of objects describing the address and index of input to sign * @returns Promise resolving to signed PSBTs * @throws {Error} If the wallet is read-only or private key is not available. */ @@ -391,38 +447,42 @@ export class EmbeddedWallet { /** * Simple largest-first coin selection algorithm. * Selects UTXOs in descending order by value until target amount + fees is reached. - * + * * @param utxos Available UTXOs * @param targetAmount Amount needed in satoshis * @param feeRate Fee rate in sat/vByte * @returns Selected UTXOs and change amount */ - private _selectUtxosLargestFirst(utxos: UTxO[], targetAmount: number, feeRate: number): - { selectedUtxos: UTxO[], change: number } { - + private _selectUtxosLargestFirst( + utxos: UTxO[], + targetAmount: number, + feeRate: number, + ): { selectedUtxos: UTxO[]; change: number } { // Sort UTXOs by value (descending) - largest first const sortedUtxos = [...utxos].sort((a, b) => b.value - a.value); - + let selectedValue = 0; const selectedUtxos: UTxO[] = []; - + // Accumulate UTXOs until we have enough for (const utxo of sortedUtxos) { selectedUtxos.push(utxo); selectedValue += utxo.value; - + // Calculate fee with current selection (rough estimate) const estimatedTxSize = selectedUtxos.length * 150 + 2 * 34 + 10; // inputs + outputs + overhead const fee = Math.ceil(estimatedTxSize * feeRate); - + // Check if we have enough if (selectedValue >= targetAmount + fee) { - const finalFee = Math.ceil((selectedUtxos.length * 150 + 2 * 34 + 10) * feeRate); + const finalFee = Math.ceil( + (selectedUtxos.length * 150 + 2 * 34 + 10) * feeRate, + ); const change = selectedValue - targetAmount - finalFee; return { selectedUtxos, change }; } } - + throw new Error("Insufficient funds for transaction."); } @@ -449,14 +509,18 @@ export class EmbeddedWallet { // Use simple largest-first coin selection const targetAmount = recipients.reduce((sum, r) => sum + r.amount, 0); - const { selectedUtxos, change } = this._selectUtxosLargestFirst(utxos, targetAmount, feeRate); + const { selectedUtxos, change } = this._selectUtxosLargestFirst( + utxos, + targetAmount, + feeRate, + ); const psbt = new bitcoin.Psbt({ network: this._network }); const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: this._wallet!.publicKey, network: this._network, }); - selectedUtxos.forEach(utxo => { + selectedUtxos.forEach((utxo) => { psbt.addInput({ hash: utxo.txid, index: utxo.vout, @@ -464,7 +528,7 @@ export class EmbeddedWallet { }); }); - recipients.forEach(recipient => { + recipients.forEach((recipient) => { psbt.addOutput({ address: recipient.address, value: recipient.amount }); });