diff --git a/package.json b/package.json index a7aa947..a9abc49 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "2.1.3", + "version": "2.2.5", "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", diff --git a/src/constants/ethereum-constants.ts b/src/constants/ethereum-constants.ts index 7a82fbb..1c190c1 100644 --- a/src/constants/ethereum-constants.ts +++ b/src/constants/ethereum-constants.ts @@ -4,6 +4,34 @@ import { EthereumNetworkID, } from '../models/ethereum-models.js'; +export const ethereumMainnet: EthereumNetwork = { + name: 'Mainnet', + displayName: 'Mainnet', + id: EthereumNetworkID.Mainnet, + defaultNodeURL: 'https://cloudflare-eth.com', +}; + +export const ethereumSepolia: EthereumNetwork = { + name: 'Sepolia', + displayName: 'Sepolia', + id: EthereumNetworkID.Sepolia, + defaultNodeURL: 'https://rpc.sepolia.org', +}; + +export const ethereumBase: EthereumNetwork = { + name: 'Base', + displayName: 'Base', + id: EthereumNetworkID.Base, + defaultNodeURL: 'https://mainnet.base.org', +}; + +export const ethereumBaseSepolia: EthereumNetwork = { + name: 'BaseSepolia', + displayName: 'Base Sepolia', + id: EthereumNetworkID.BaseSepolia, + defaultNodeURL: 'https://sepolia.base.org', +}; + export const ethereumArbitrumSepolia: EthereumNetwork = { name: 'ArbSepolia', displayName: 'Arbitrum Sepolia', @@ -28,57 +56,13 @@ const ethereumHardhat: EthereumNetwork = { export const supportedEthereumNetworks: EthereumNetwork[] = [ ethereumArbitrumSepolia, ethereumArbitrum, + ethereumMainnet, + ethereumSepolia, + ethereumBase, + ethereumBaseSepolia, ethereumHardhat, ]; -export const hexChainIDs: { [key in EthereumNetworkID]: string } = { - [EthereumNetworkID.ArbitrumSepolia]: '0x66eee', - [EthereumNetworkID.Arbitrum]: '0xa4b1', - [EthereumNetworkID.Hardhat]: '0x7a69', -}; - -export const addNetworkParams = { - [EthereumNetworkID.ArbitrumSepolia]: [ - { - chainId: '0x66eee', - rpcUrls: ['https://sepolia-rollup.arbitrum.io/rpc', 'https://arb-sepolia.infura.io/v3/'], - chainName: 'Arbitrum Sepolia Testnet', - nativeCurrency: { - name: 'ETH', - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://sepolia.arbiscan.io/'], - }, - ], - [EthereumNetworkID.Arbitrum]: [ - { - chainId: '42161', - rpcUrls: ['https://arb1.arbitrum.io/rpc', 'https://arbitrum-mainnet.infura.io'], - chainName: 'Arbitrum One', - nativeCurrency: { - name: 'ETH', - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: ['https://arbiscan.io/'], - }, - ], - [EthereumNetworkID.Hardhat]: [ - { - chainId: '31337', - rpcUrls: ['http://localhost:8545'], - chainName: 'Hardhat', - nativeCurrency: { - name: 'ETH', - symbol: 'ETH', - decimals: 18, - }, - blockExplorerUrls: [], - }, - ], -}; - export const GITHUB_SOLIDITY_URL = 'https://raw.githubusercontent.com/DLC-link/dlc-solidity'; export const dlcContractNames: DLCEthereumContractName[] = ['DLCManager', 'DLCBTC']; diff --git a/src/models/ethereum-models.ts b/src/models/ethereum-models.ts index c1771c1..557d74f 100644 --- a/src/models/ethereum-models.ts +++ b/src/models/ethereum-models.ts @@ -8,8 +8,12 @@ export interface EthereumNetwork { } export enum EthereumNetworkID { - ArbitrumSepolia = '421614', + Mainnet = '1', + Sepolia = '11155111', Arbitrum = '42161', + ArbitrumSepolia = '421614', + Base = '8453', + BaseSepolia = '84532', Hardhat = '31337', } diff --git a/tests/mocks/api.test.constants.ts b/tests/mocks/api.test.constants.ts index c94000c..cd625bd 100644 --- a/tests/mocks/api.test.constants.ts +++ b/tests/mocks/api.test.constants.ts @@ -1,5 +1,6 @@ export const TEST_REGTEST_BITCOIN_BLOCKCHAIN_API = 'https://devnet.dlc.link/electrs'; export const TEST_TESTNET_BITCOIN_BLOCKCHAIN_API = 'https://testnet.dlc.link/electrs'; +export const TEST_MAINNET_BITCOIN_BLOCKCHAIN_API = 'https://mainnet.dlc.link/electrs'; export const TEST_BITCOIN_BLOCKCHAIN_FEE_RECOMMENDATION_API = 'https://devnet.dlc.link/electrs/fee-estimates'; diff --git a/tests/mocks/attestor.test.constants.ts b/tests/mocks/attestor.test.constants.ts index 4f5e5dd..6e3f1f9 100644 --- a/tests/mocks/attestor.test.constants.ts +++ b/tests/mocks/attestor.test.constants.ts @@ -4,8 +4,14 @@ export const TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 = export const TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 = 'tpubDDRekL64eJJav32TLhNhG59qra7wAMaei8YMGXNiJE8ksdYrKgvaFM1XG6JrSt31W97XryScrX37RUEujjZT4qScNf8Zu1JxWj4VYkwz4rU'; +export const TEST_MAINNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 = + 'xpub6C1F2SwADP3TNajQjg2PaniEGpZLvWdMiFP8ChPjQBRWD1XUBeMdE4YkQYvnNhAYGoZKfcQbsRCefserB5DyJM7R9VR6ce6vLrXHVfeqyH3'; + export const TEST_REGTEST_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 = '02532fc9ebdb559f900e8b469e2e1caebbf5cf84c22a2376a47105cf5003e0ca08'; export const TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 = '027eda4d625f781dcc98bf68901360fdaaacce8ed466096c1dfe4865209b28c058'; + +export const TEST_MAINNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 = + '03fc36ff16e336657e6ab87bf5ad19ce538ca94e0a52e12a9fd9b65291d296866c'; diff --git a/tests/mocks/bitcoin-transaction.test.constants.ts b/tests/mocks/bitcoin-transaction.test.constants.ts index 72e824d..633291c 100644 --- a/tests/mocks/bitcoin-transaction.test.constants.ts +++ b/tests/mocks/bitcoin-transaction.test.constants.ts @@ -921,6 +921,45 @@ export const TEST_WITHDRAW_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_2 = // } // } +export const TEST_FUNDING_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_1 = + '70736274ff0100a80200000001dcd01b1b19b0f64371bcf4dad0483f963cec83e635b1a650e3bba6dfb91137420000000000f0ffffff0340420f0000000000225120dcdc93b920e39fe9c6efd225dca8cd4fe7bbf873dc8effeb5286159857d7bb1c1027000000000000160014622c23eebbf46df254d7da8e1c4d95d4f5c7d69fcc74e60500000000225120676504fcaf89119cc9762c2f867aaa56aa3fffc85158f7dd61da345cdbf4a9be000000000001012b00e1f50500000000225120676504fcaf89119cc9762c2f867aaa56aa3fffc85158f7dd61da345cdbf4a9be011720bb7e175e63064479102ee0b69a719a9f54f8f1b29df17cfaa5437697393e7cfc00000000'; +// Readable format: +// { +// global: { txVersion: 2 }, +// inputs: [ +// { +// tapInternalKey: , +// txid: [Uint8Array], +// index: 0, +// witnessUtxo: [Object], +// sequence: 4294967280 +// } +// ], +// outputs: [ +// { amount: 1000000n, script: [Uint8Array] }, +// { amount: 10000n, script: [Uint8Array] }, +// { amount: 98989260n, script: [Uint8Array] } +// ], +// opts: { +// createTx: true, +// bip69: false, +// changeAddress: 'bcrt1pvajsfl903ygeejtk9shcv742264rll7g29v00htpmg69ekl54xlq8eddah', +// feePerByte: 4n, +// network: { +// messagePrefix: '\x18Bitcoin Signed Message:\n', +// bech32: 'bcrt', +// bip32: [Object], +// pubKeyHash: 111, +// scriptHash: 196, +// wif: 239 +// }, +// dust: 546n, +// version: 2, +// lockTime: 0, +// PSBTVersion: 0 +// } +// } + // This is a testnet funding transaction with valid inputs and outputs export const TEST_TESTNET_FUNDING_TRANSACTION_1: BitcoinTransaction = { txid: '4cf5c2954c84bf5225d98ef014aa97bbfa0f05d56b5749782fcd8af8b9d505a5', @@ -1159,6 +1198,62 @@ export const TEST_REGTEST_FUNDING_TRANSACTION_1: BitcoinTransaction = { }, }; +// This transaction is missing the output with the multisig's script. +export const TEST_MAINNET_FUNDING_TRANSACTION_1: BitcoinTransaction = { + txid: '2a220f043ff34cfca57d910209fa676c82220a817da5ebf09f145cc012d8a85c', + version: 2, + locktime: 0, + vin: [ + { + txid: '2e9d09f8304b6bdf635adeef799fb9f4e2434bca508eecb35adb41480e24aec3', + vout: 0, + prevout: { + scriptpubkey: '5120ec1354cc8defd6ced2fed85e6921473f7d9e6157974a29d6156ae71d83bf8266', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 ec1354cc8defd6ced2fed85e6921473f7d9e6157974a29d6156ae71d83bf8266', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'bc1pasf4fnydaltva5h7mp0xjg288a7euc2hja9zn4s4dtn3mqalsfnqckzx6z', + value: 1000000, + }, + scriptsig: '', + scriptsig_asm: '', + witness: [ + '3dd16b2aa465cfef33fd82d02ca2d21dd095b1ada92b36ce8042faed22eaaf1495304344ed40a1ada5e79845b16fee867b4e104fb820eaefd9dfec7e20ec8e07', + 'f4f3648bff8c887b18c586fdb5642ddde361cec802498c25a8d86d510c721ec30a515c87bbfc72f2ef6c9c5e60cc261a458276558c226a133c9311ddf54960cb', + '20b362931e3e4cf3cc20f75ae11ff5a4c115ec1548cb5f2c7c48294929f1e8979cad20fc36ff16e336657e6ab87bf5ad19ce538ca94e0a52e12a9fd9b65291d296866cac', + 'c15d741aac77028f6c72167559fe4f8b2ba34b1078648dd621b31411ca2178227f', + ], + is_coinbase: false, + sequence: 4294967295, + }, + ], + vout: [ + { + scriptpubkey: '00142d2d0c13815a141129c9df2ab9b68344398de74b', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 2d2d0c13815a141129c9df2ab9b68344398de74b', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'bc1q95kscyuptg2pz2wfmu4tnd5rgsucme6tpx900g', + value: 10000, + }, + { + scriptpubkey: '001405b8d44eb1d67d47192c6168a24cb4e5b5a7b438', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 05b8d44eb1d67d47192c6168a24cb4e5b5a7b438', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'bc1qqkudgn436e75wxfvv952yn95uk660dpc7ve7vq', + value: 987400, + }, + ], + size: 349, + weight: 688, + fee: 2600, + status: { + confirmed: true, + block_height: 846609, + block_hash: '00000000000000000001484d3e7d39acef1604da54d206ed1d52da0af5527522', + block_time: 1717580151, + }, +}; + export const TEST_ALICE_UTXOS_REGTEST_1 = [ { type: 'wpkh', diff --git a/tests/mocks/bitcoin.test.constants.ts b/tests/mocks/bitcoin.test.constants.ts index 7be1c88..69b4410 100644 --- a/tests/mocks/bitcoin.test.constants.ts +++ b/tests/mocks/bitcoin.test.constants.ts @@ -1,5 +1,6 @@ export const TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1 = 2867441; export const TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2 = 2867285; +export const TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_3 = 858774; export const TEST_BITCOIN_AMOUNT = 0.01; export const TEST_FUNDING_PAYMENT_TYPE = 'tr'; diff --git a/tests/mocks/ethereum-vault.test.constants.ts b/tests/mocks/ethereum-vault.test.constants.ts index 1a1cef1..1bec4da 100644 --- a/tests/mocks/ethereum-vault.test.constants.ts +++ b/tests/mocks/ethereum-vault.test.constants.ts @@ -35,3 +35,20 @@ export const TEST_VAULT_2: RawVault = { btcRedeemFeeBasisPoints: BigNumber.from('0x64'), taprootPubKey: 'dc544c17af0887dfc8ca9936755c9fdef0c79bbc8866cd69bf120c71509742d2', }; + +export const TEST_VAULT_3: RawVault = { + uuid: '0x493ec75561131a09b06a9ab53cb1a5a35b55760b1db8ef4f62bc00066f043038', + protocolContract: '0x2940FcBb3C32901Df405da0E96fd97D1E2a53f34', + timestamp: BigNumber.from('0x66508949'), + valueLocked: BigNumber.from(0), + valueMinted: BigNumber.from(0), + creator: '0x0DD4f29E21F10cb2E485cf9bDAb9F2dD1f240Bfa', + status: 3, + fundingTxId: '2a220f043ff34cfca57d910209fa676c82220a817da5ebf09f145cc012d8a85c', + closingTxId: '', + wdTxId: '', + btcFeeRecipient: '021b34f36d8487ce3a7a6f0124f58854d561cb52077593d1e86973fac0fea1a8b1', + btcMintFeeBasisPoints: BigNumber.from('0x64'), + btcRedeemFeeBasisPoints: BigNumber.from('0x64'), + taprootPubKey: 'b362931e3e4cf3cc20f75ae11ff5a4c115ec1548cb5f2c7c48294929f1e8979c', +}; diff --git a/tests/unit/proof-of-reserve.test.ts b/tests/unit/proof-of-reserve.test.ts index f682b77..6040d38 100644 --- a/tests/unit/proof-of-reserve.test.ts +++ b/tests/unit/proof-of-reserve.test.ts @@ -1,18 +1,26 @@ -import { testnet } from 'bitcoinjs-lib/src/networks.js'; +import { bitcoin, testnet } from 'bitcoinjs-lib/src/networks.js'; import * as bitcoinRequestFunctions from '../../src/functions/bitcoin/bitcoin-request-functions.js'; import { verifyVaultDeposit } from '../../src/functions/proof-of-reserve/proof-of-reserve-functions.js'; -import { TEST_TESTNET_BITCOIN_BLOCKCHAIN_API } from '../mocks/api.test.constants.js'; -import { TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 } from '../mocks/attestor.test.constants.js'; import { + TEST_MAINNET_BITCOIN_BLOCKCHAIN_API, + TEST_TESTNET_BITCOIN_BLOCKCHAIN_API, +} from '../mocks/api.test.constants.js'; +import { + TEST_MAINNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, + TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, +} from '../mocks/attestor.test.constants.js'; +import { + TEST_MAINNET_FUNDING_TRANSACTION_1, TEST_TESTNET_FUNDING_TRANSACTION_1, TEST_TESTNET_FUNDING_TRANSACTION_2, } from '../mocks/bitcoin-transaction.test.constants.js'; import { TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2, + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_3, } from '../mocks/bitcoin.test.constants.js'; -import { TEST_VAULT_2 } from '../mocks/ethereum-vault.test.constants.js'; +import { TEST_VAULT_2, TEST_VAULT_3 } from '../mocks/ethereum-vault.test.constants.js'; describe('Proof of Reserve Calculation', () => { beforeEach(() => { @@ -84,5 +92,21 @@ describe('Proof of Reserve Calculation', () => { expect(result).toBe(0); }); + + it("should return 0 if the vault is legacy and it's funding transaction lacks an output with the multisig's script", async () => { + jest + .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') + .mockImplementationOnce(async () => TEST_MAINNET_FUNDING_TRANSACTION_1); + + const result = await verifyVaultDeposit( + TEST_VAULT_3, + Buffer.from(TEST_MAINNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_3, + TEST_MAINNET_BITCOIN_BLOCKCHAIN_API, + bitcoin + ); + + expect(result).toBe(0); + }); }); }); diff --git a/tests/unit/sign-transactions.test.ts b/tests/unit/sign-transactions.test.ts index 860491e..893ac27 100644 --- a/tests/unit/sign-transactions.test.ts +++ b/tests/unit/sign-transactions.test.ts @@ -1,25 +1,21 @@ -import { Transaction, p2wpkh } from '@scure/btc-signer'; +import { hexToBytes } from '@noble/hashes/utils'; +import { Transaction } from '@scure/btc-signer'; import { regtest } from 'bitcoinjs-lib/src/networks.js'; import { PrivateKeyDLCHandler } from '../../src/index.js'; -import { shiftValue } from '../../src/utilities/index.js'; import { TEST_BITCOIN_BLOCKCHAIN_FEE_RECOMMENDATION_API, TEST_REGTEST_BITCOIN_BLOCKCHAIN_API, } from '../mocks/api.test.constants.js'; -import { TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 } from '../mocks/attestor.test.constants.js'; +import { TEST_FUNDING_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_1 } from '../mocks/bitcoin-transaction.test.constants.js'; import { - TEST_BITCOIN_AMOUNT, TEST_BITCOIN_EXTENDED_PRIVATE_KEY, TEST_BITCOIN_WALLET_ACCOUNT_INDEX, TEST_FUNDING_PAYMENT_TYPE, } from '../mocks/bitcoin.test.constants.js'; -import { TEST_VAULT_1 } from '../mocks/ethereum-vault.test.constants.js'; describe('Create and Sign Vault related Transactions', () => { let dlcHandler: PrivateKeyDLCHandler; - let fundingTransaction: Transaction; - let signedFundingTransaction: Transaction; it('should initialize a Private Key DLC Handler', async () => { dlcHandler = new PrivateKeyDLCHandler( @@ -32,41 +28,11 @@ describe('Create and Sign Vault related Transactions', () => { ); }); - it('should create a funding transaction', async () => { - fundingTransaction = await dlcHandler.createFundingPSBT( - TEST_VAULT_1, - BigInt(shiftValue(TEST_BITCOIN_AMOUNT)), - TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, - 2 - ); - - const vaultAmount = TEST_VAULT_1.valueLocked.toBigInt(); - const feeAmount = vaultAmount / TEST_VAULT_1.btcMintFeeBasisPoints.toBigInt(); - - const feeRecipientScript = p2wpkh( - Buffer.from(TEST_VAULT_1.btcFeeRecipient, 'hex'), - regtest - ).script; - const multisigScript = dlcHandler.payment?.multisigPayment.script; - - const outputs = Array.from({ length: fundingTransaction.outputsLength }, (_, index) => - fundingTransaction.getOutput(index) - ); - - const multisigOutput = outputs.find( - output => output.script?.toString() === multisigScript?.toString() - ); - const feeOutput = outputs.find( - output => output.script?.toString() === feeRecipientScript.toString() - ); - - expect(fundingTransaction).toBeDefined(); - expect(multisigOutput?.amount === vaultAmount).toBeTruthy(); - expect(feeOutput?.amount === feeAmount).toBeTruthy(); - }); - it('should sign a funding transaction', async () => { - signedFundingTransaction = dlcHandler.signPSBT(fundingTransaction, 'funding'); + const signedFundingTransaction = dlcHandler.signPSBT( + Transaction.fromPSBT(hexToBytes(TEST_FUNDING_PSBT_PARTIALLY_SIGNED_WITHDRAW_PSBT_1)), + 'funding' + ); expect(signedFundingTransaction.isFinal).toBeTruthy(); });