Skip to content

Commit

Permalink
feat: modify fee recipient address retrieving function (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
Polybius93 authored Nov 21, 2024
1 parent e93978e commit 6f90f08
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 21 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "dlc-btc-lib",
"version": "2.4.14",
"version": "2.4.16",
"description": "This library provides a comprehensive set of interfaces and functions for minting dlcBTC tokens on supported blockchains.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
31 changes: 24 additions & 7 deletions src/functions/bitcoin/bitcoin-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { P2Ret, P2TROut } from '@scure/btc-signer/payment';
import { TransactionInput } from '@scure/btc-signer/psbt';
import { BIP32Factory, BIP32Interface } from 'bip32';
import { Network } from 'bitcoinjs-lib';
import { Network, address } from 'bitcoinjs-lib';
import { bitcoin, regtest, testnet } from 'bitcoinjs-lib/src/networks.js';
import { Decimal } from 'decimal.js';
import * as ellipticCurveCryptography from 'tiny-secp256k1';
Expand Down Expand Up @@ -242,18 +242,35 @@ export async function getBalance(
}

/**
* Gets the Fee Recipient's Address from the Rcipient's Public Key.
* @param feePublicKey - The Fee Recipient's Public Key.
* Validates a Bitcoin Address.
* @param bitcoinAddress
* @param bitcoinNetwork
* @returns A boolean indicating if the Bitcoin Address is valid.
*/
export function isBitcoinAddress(bitcoinAddress: string, bitcoinNetwork: Network): boolean {
try {
return !!address.toOutputScript(bitcoinAddress, bitcoinNetwork);
} catch {
return false;
}
}

/**
* Gets the Fee Recipient's Address from the Recipient's Public Key or Address.
* @param bitcoinFeeRecipient - The Fee Recipient's Public Key or Address.
* @param bitcoinNetwork - The Bitcoin Network to use.
* @returns The Fee Recipient's Address.
*/
export function getFeeRecipientAddressFromPublicKey(
feePublicKey: string,
export function getFeeRecipientAddress(
bitcoinFeeRecipient: string,
bitcoinNetwork: Network
): string {
const feePublicKeyBuffer = Buffer.from(feePublicKey, 'hex');
const { address } = p2wpkh(feePublicKeyBuffer, bitcoinNetwork);
if (isBitcoinAddress(bitcoinFeeRecipient, bitcoinNetwork)) return bitcoinFeeRecipient;

const { address } = p2wpkh(Buffer.from(bitcoinFeeRecipient, 'hex'), bitcoinNetwork);

if (!address) throw new Error('Could not create Fee Address from Public Key');

return address;
}

Expand Down
4 changes: 2 additions & 2 deletions src/functions/bitcoin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
finalizeUserInputs,
getBitcoinAddressFromExtendedPublicKey,
getFeeAmount,
getFeeRecipientAddressFromPublicKey,
getFeeRecipientAddress,
getInputIndicesByScript,
} from '../bitcoin/bitcoin-functions.js';
import {
Expand All @@ -27,7 +27,7 @@ export {
finalizeUserInputs,
getFeeAmount,
getBalance,
getFeeRecipientAddressFromPublicKey,
getFeeRecipientAddress,
getInputIndicesByScript,
getBitcoinAddressFromExtendedPublicKey,
};
20 changes: 10 additions & 10 deletions src/functions/bitcoin/psbt-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { reverseBytes } from '../../utilities/index.js';
import {
ecdsaPublicKeyToSchnorr,
getFeeAmount,
getFeeRecipientAddressFromPublicKey,
getFeeRecipientAddress,
getUTXOs,
} from '../bitcoin/bitcoin-functions.js';
import { fetchBitcoinTransaction } from './bitcoin-request-functions.js';
Expand All @@ -25,7 +25,7 @@ import { fetchBitcoinTransaction } from './bitcoin-request-functions.js';
* @param multisigPayment - The Multisig Payment object created from the User's Taproot Public Key, the Attestor's Public Key, and the Unspendable Public Key committed to the Vault's UUID.
* @param depositPayment - The User's Payment object which will be used to fund the Deposit Transaction.
* @param feeRate - The Fee Rate to use for the Transaction.
* @param feePublicKey - The Fee Recipient's Public Key.
* @param feeRecipient - The Fee Recipient's Public Key or Address.
* @param feeBasisPoints - The Fee Basis Points.
* @returns A Funding Transaction.
*/
Expand All @@ -36,7 +36,7 @@ export async function createFundingTransaction(
multisigPayment: P2TROut,
depositPayment: P2Ret | P2TROut,
feeRate: bigint,
feePublicKey: string,
feeRecipient: string,
feeBasisPoints: bigint
): Promise<Transaction> {
const multisigAddress = multisigPayment.address;
Expand All @@ -51,7 +51,7 @@ export async function createFundingTransaction(
throw new Error('Deposit Payment is missing Address');
}

const feeAddress = getFeeRecipientAddressFromPublicKey(feePublicKey, bitcoinNetwork);
const feeAddress = getFeeRecipientAddress(feeRecipient, bitcoinNetwork);
const feeAmount = getFeeAmount(Number(depositAmount), Number(feeBasisPoints));

const userUTXOs = await getUTXOs(depositPayment, bitcoinBlockchainAPIURL);
Expand Down Expand Up @@ -103,7 +103,7 @@ export async function createFundingTransaction(
* @param multisigPayment - The Multisig Payment object created from the User's Taproot Public Key, the Attestor's Public Key, and the Unspendable Public Key committed to the Vault's UUID.
* @param depositPayment - The User's Payment object which will be used to fund the Deposit Transaction.
* @param feeRate - The Fee Rate to use for the Transaction.
* @param feePublicKey - The Fee Recipient's Public Key.
* @param feeRecipient - The Fee Recipient's Public Key or Address.
* @param feeBasisPoints - The Fee Basis Points.
* @returns A Deposit Transaction.
*/
Expand All @@ -115,7 +115,7 @@ export async function createDepositTransaction(
multisigPayment: P2TROut,
depositPayment: P2TROut | P2Ret,
feeRate: bigint,
feePublicKey: string,
feeRecipient: string,
feeBasisPoints: bigint
): Promise<Transaction> {
const multisigAddress = multisigPayment.address;
Expand All @@ -130,7 +130,7 @@ export async function createDepositTransaction(
throw new Error('Deposit Payment is missing Address');
}

const feeAddress = getFeeRecipientAddressFromPublicKey(feePublicKey, bitcoinNetwork);
const feeAddress = getFeeRecipientAddress(feeRecipient, bitcoinNetwork);
const feeAmount = getFeeAmount(Number(depositAmount), Number(feeBasisPoints));

const vaultTransaction = await fetchBitcoinTransaction(vaultTransactionID, bitcoinBlockchainURL);
Expand Down Expand Up @@ -248,7 +248,7 @@ export async function createDepositTransaction(
* @param multisigPayment - The Multisig Payment object created from the User's Taproot Public Key, the Attestor's Public Key, and the Unspendable Public Key committed to the Vault's UUID.
* @param withdrawPayment - The User's Payment object which will be used to receive the withdrawn Bitcoin.
* @param feeRate - The Fee Rate to use for the Transaction.
* @param feePublicKey - The Fee Recipient's Public Key.
* @param feeRecipient - The Fee Recipient's Public Key or Address.
* @param feeBasisPoints - The Fee Basis Points.
* @returns A Withdraw Transaction.
*/
Expand All @@ -260,7 +260,7 @@ export async function createWithdrawTransaction(
multisigPayment: P2TROut,
withdrawPayment: P2Ret | P2TROut,
feeRate: bigint,
feePublicKey: string,
feeRecipient: string,
feeBasisPoints: bigint
): Promise<Transaction> {
const multisigAddress = multisigPayment.address;
Expand Down Expand Up @@ -298,7 +298,7 @@ export async function createWithdrawTransaction(
const remainingAmount =
BigInt(fundingTransaction.vout[fundingTransactionOutputIndex].value) - BigInt(withdrawAmount);

const feeAddress = getFeeRecipientAddressFromPublicKey(feePublicKey, bitcoinNetwork);
const feeAddress = getFeeRecipientAddress(feeRecipient, bitcoinNetwork);
const feeAmount = getFeeAmount(Number(withdrawAmount), Number(feeBasisPoints));

const inputs = [
Expand Down
79 changes: 78 additions & 1 deletion tests/unit/bitcoin-functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { Transaction, p2tr, p2wpkh } from '@scure/btc-signer';
import { regtest, testnet } from 'bitcoinjs-lib/src/networks';
import { bitcoin, regtest, testnet } from 'bitcoinjs-lib/src/networks';

import {
createTaprootMultisigPayment,
deriveUnhardenedPublicKey,
ecdsaPublicKeyToSchnorr,
finalizeUserInputs,
getFeeRecipientAddress,
getInputIndicesByScript,
getScriptMatchingOutputFromTransaction,
getUnspendableKeyCommittedToUUID,
Expand Down Expand Up @@ -66,6 +67,82 @@ describe('Bitcoin Functions', () => {
});
});

describe('getFeeRecipientAddress', () => {
describe('mainnet', () => {
const network = bitcoin;

it('accepts native segwit (p2wpkh) address', () => {
const address = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4';
expect(getFeeRecipientAddress(address, network)).toBe(address);
});

it('accepts taproot (p2tr) address', () => {
const address = 'bc1qw02rsw9afgp4dsd5n87z5s6rqnf455yhhsnz9f';
expect(getFeeRecipientAddress(address, network)).toBe(address);
});

it('converts public key to native segwit address', () => {
const publicKey = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798';
const expectedAddress = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4';
expect(getFeeRecipientAddress(publicKey, network)).toBe(expectedAddress);
});
});

describe('testnet', () => {
const network = testnet;

it('accepts native segwit (p2wpkh) address', () => {
const address = 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx';
expect(getFeeRecipientAddress(address, network)).toBe(address);
});

it('accepts taproot (p2tr) address', () => {
const address = 'tb1qqhy33peyp82mf82fktdtphfmnhtxyhtp6x9hrc';
expect(getFeeRecipientAddress(address, network)).toBe(address);
});

it('converts public key to native segwit address', () => {
const publicKey = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798';
const expectedAddress = 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx';
expect(getFeeRecipientAddress(publicKey, network)).toBe(expectedAddress);
});
});

describe('regtest', () => {
const network = regtest;

it('accepts native segwit (p2wpkh) address', () => {
const address = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080';
expect(getFeeRecipientAddress(address, network)).toBe(address);
});

it('accepts taproot (p2tr) address', () => {
const address = 'bcrt1qqhy33peyp82mf82fktdtphfmnhtxyhtpc0u653';
expect(getFeeRecipientAddress(address, network)).toBe(address);
});

it('converts public key to native segwit address', () => {
const publicKey = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798';
const expectedAddress = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080';
expect(getFeeRecipientAddress(publicKey, network)).toBe(expectedAddress);
});
});

describe('error cases', () => {
it('throws on invalid public key', () => {
const invalidKey = 'invalidPublicKey';
expect(() => getFeeRecipientAddress(invalidKey, bitcoin)).toThrow(
'P2WPKH: invalid publicKey'
);
});

it('throws on invalid address', () => {
const invalidAddress = 'invalidAddress';
expect(() => getFeeRecipientAddress(invalidAddress, bitcoin)).toThrow();
});
});
});

describe('finalizeUserInputs', () => {
it('correctly finalizes inputs given a transaction and a native segwit payment script', () => {
const transaction = Transaction.fromPSBT(
Expand Down

0 comments on commit 6f90f08

Please sign in to comment.