diff --git a/package.json b/package.json index 477c7a9..a89eeb4 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@scure/btc-signer": "1.3.2", "@types/ramda": "0.30.1", "bip32": "4.0.0", + "bip39": "^3.1.0", "bitcoinjs-lib": "6.1.6", "chalk": "5.3.0", "decimal.js": "10.4.3", diff --git a/src/functions/bitcoin/bitcoin-functions.ts b/src/functions/bitcoin/bitcoin-functions.ts index fc466fe..72c1e00 100644 --- a/src/functions/bitcoin/bitcoin-functions.ts +++ b/src/functions/bitcoin/bitcoin-functions.ts @@ -1,4 +1,4 @@ -import { hexToBytes } from '@noble/hashes/utils'; +import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { Address, OutScript, @@ -8,10 +8,12 @@ import { p2tr, p2tr_ns, p2wpkh, + selectUTXO, } from '@scure/btc-signer'; import { P2Ret, P2TROut } from '@scure/btc-signer/payment'; import { TransactionInput } from '@scure/btc-signer/psbt'; import { BIP32Factory, BIP32Interface } from 'bip32'; +import bip39 from 'bip39'; import { Network } from 'bitcoinjs-lib'; import { bitcoin, regtest, testnet } from 'bitcoinjs-lib/src/networks.js'; import { Decimal } from 'decimal.js'; @@ -31,6 +33,7 @@ import { isDefined, isUndefined, } from '../../utilities/index.js'; +import { broadcastTransaction } from './bitcoin-request-functions.js'; const TAPROOT_UNSPENDABLE_KEY_HEX = '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0'; @@ -43,6 +46,50 @@ export function getFeeAmount(bitcoinAmount: number, feeBasisPoints: number): num return new Decimal(bitcoinAmount).times(feePercentage.dividedBy(100)).toNumber(); } +export async function sendYield( + bitcoinWalletMnemonicPhrase: string, + bitcoinNetwork: Network, + bitcoinAmount: bigint, + bitcoinBlockchainAPIURL: string, + bitcoinRecipientAddress: string +): Promise { + const seed = await bip39.mnemonicToSeed(bitcoinWalletMnemonicPhrase); + const derivedKeyPair = bip32.fromSeed(seed, bitcoinNetwork).derivePath(`m/84'/0'/0'/0/0`); + + const payment = p2wpkh(derivedKeyPair.publicKey, bitcoinNetwork); + console.log('yieldpayer address:', payment.address); + + const utxos = await getUTXOs(payment, bitcoinBlockchainAPIURL); + + const psbtOutputs = [{ address: bitcoinRecipientAddress, amount: bitcoinAmount }]; + + const selected = selectUTXO(utxos, psbtOutputs, 'default', { + changeAddress: payment.address!, + feePerByte: 2n, + bip69: false, + createTx: true, + network: bitcoinNetwork, + dust: 546n as unknown as number, + }); + + if (!selected) { + throw new Error( + 'Failed to select Inputs for the Funding Transaction. Ensure sufficient funds are available.' + ); + } + + const fundingTX = selected.tx; + + if (!fundingTX) { + throw new Error('Failed to create the Funding Transaction'); + } + + fundingTX.sign(derivedKeyPair.privateKey!); + fundingTX.finalize(); + + await broadcastTransaction(bytesToHex(fundingTX.extract()), bitcoinBlockchainAPIURL); +} + /** * Derives the Public Key at the Unhardened Path (0/0) from a given Extended Public Key. * @param extendedPublicKey - The base58-encoded Extended Public Key. diff --git a/src/functions/bitcoin/index.ts b/src/functions/bitcoin/index.ts index 80c546a..340c673 100644 --- a/src/functions/bitcoin/index.ts +++ b/src/functions/bitcoin/index.ts @@ -4,6 +4,7 @@ import { getFeeAmount, getFeeRecipientAddressFromPublicKey, getInputIndicesByScript, + sendYield, } from '../bitcoin/bitcoin-functions.js'; import { broadcastTransaction, @@ -18,6 +19,7 @@ import { } from '../bitcoin/psbt-functions.js'; export { + sendYield, createFundingTransaction, createDepositTransaction, createWithdrawTransaction, diff --git a/yarn.lock b/yarn.lock index d129c05..5126c37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -764,7 +764,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@gemwallet/api@^3.8.0": +"@gemwallet/api@3.8.0": version "3.8.0" resolved "https://registry.yarnpkg.com/@gemwallet/api/-/api-3.8.0.tgz#46bc47789848c7ac9cc620613e0a1757dc8668a1" integrity sha512-hZ6XC0mVm3Q54cgonrzk6tHS/wUMjtPHyqsqbtlnNGPouCR7OIfEDo5Y802qLZ5ah6PskhsK0DouVnwUykEM8Q== @@ -1833,6 +1833,13 @@ bip32@^2.0.4: typeforce "^1.11.5" wif "^2.0.6" +bip39@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" + integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + dependencies: + "@noble/hashes" "^1.2.0" + bip66@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" @@ -4478,7 +4485,7 @@ ripple-address-codec@^5.0.0: "@scure/base" "^1.1.3" "@xrplf/isomorphic" "^1.0.0" -ripple-binary-codec@^2.1.0: +ripple-binary-codec@2.1.0, ripple-binary-codec@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-2.1.0.tgz#f1ef81f8d1f05a6cecc06fc6d9b13456569cafda" integrity sha512-q0GAx+hj3UVcDbhXVjk7qeNfgUMehlElYJwiCuIBwqs/51GVTOwLr39Ht3eNsX5ow2xPRaC5mqHwcFDvLRm6cA==