From 1f6c456737dcbb8e68c47fb040e826e8cd37cf19 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Mon, 17 Mar 2025 11:50:54 +0100 Subject: [PATCH] feat: merge getProofOfReserve and getVaultAddresses functions into one --- package.json | 2 +- .../proof-of-reserve-functions.ts | 19 +++--- src/models/proof-of-reserve.models.ts | 9 +++ .../proof-of-reserve-handler.ts | 55 ++++++++--------- tests/unit/proof-of-reserve.test.ts | 60 +++++++------------ 5 files changed, 64 insertions(+), 81 deletions(-) create mode 100644 src/models/proof-of-reserve.models.ts diff --git a/package.json b/package.json index c47d20d..9aafefa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "2.6.7", + "version": "2.6.8", "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/functions/proof-of-reserve/proof-of-reserve-functions.ts b/src/functions/proof-of-reserve/proof-of-reserve-functions.ts index b57a339..e8ee08b 100644 --- a/src/functions/proof-of-reserve/proof-of-reserve-functions.ts +++ b/src/functions/proof-of-reserve/proof-of-reserve-functions.ts @@ -1,4 +1,5 @@ import { Network } from 'bitcoinjs-lib'; +import { VaultProofOfReserveData } from 'src/models/proof-of-reserve.models.js'; import { RawVault } from '../../models/ethereum-models.js'; import { isNonEmptyString } from '../../utilities/index.js'; @@ -49,20 +50,20 @@ export async function getVaultAddress( * @returns A promise that resolves to the value of the vault's output in the transaction in satoshis, or 0 if the transaction is not confirmed or invalid * @throws Will log an error message if there is an issue verifying the vault deposit */ -export async function getVaultDepositAmount( +export async function getVaultProofOfReserveData( vault: RawVault, extendedAttestorGroupPublicKey: string, bitcoinBlockchainBlockHeight: number, bitcoinBlockchainAPI: string, bitcoinNetwork: Network -): Promise { +): Promise { try { const { uuid, taprootPubKey, wdTxId, fundingTxId } = vault; const hasWithdrawDepositTransaction = isNonEmptyString(wdTxId); const hasFundingTransaction = isNonEmptyString(fundingTxId); - if (!hasWithdrawDepositTransaction && !hasFundingTransaction) return 0; + if (!hasWithdrawDepositTransaction && !hasFundingTransaction) return; const txID = hasWithdrawDepositTransaction ? wdTxId : fundingTxId; @@ -83,7 +84,9 @@ export async function getVaultDepositAmount( if (!isVaultTransactionConfirmed) { const vaultMultisigInput = findVaultMultisigInput(vaultTransaction, vaultPayment.address!); - return vaultMultisigInput ? vaultMultisigInput.prevout.value : 0; + return vaultMultisigInput + ? { amount: vaultMultisigInput.prevout.value, address: vaultPayment.address! } + : undefined; } const vaultTransactionOutput = getScriptMatchingOutputFromTransaction( @@ -91,13 +94,11 @@ export async function getVaultDepositAmount( vaultPayment.script ); - if (!vaultTransactionOutput) { - return 0; - } + if (!vaultTransactionOutput) return; - return vaultTransactionOutput.value; + return { amount: vaultTransactionOutput.value, address: vaultPayment.address! }; } catch (error) { console.log(`Error verifying Vault Deposit: ${error}`); - return 0; + return; } } diff --git a/src/models/proof-of-reserve.models.ts b/src/models/proof-of-reserve.models.ts new file mode 100644 index 0000000..805de24 --- /dev/null +++ b/src/models/proof-of-reserve.models.ts @@ -0,0 +1,9 @@ +export interface VaultProofOfReserveData { + amount: number; + address: string; +} + +export interface ProofOfReserveData { + proofOfReserve: number; + vaultAddresses: string[]; +} diff --git a/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts b/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts index 12ea169..f1dd3b2 100644 --- a/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts +++ b/src/proof-of-reserve-handlers/proof-of-reserve-handler.ts @@ -1,11 +1,9 @@ import { Network } from 'bitcoinjs-lib'; import { isNotNil } from 'ramda'; +import { ProofOfReserveData } from 'src/models/proof-of-reserve.models.js'; import { fetchBitcoinBlockchainBlockHeight } from '../functions/bitcoin/bitcoin-request-functions.js'; -import { - getVaultAddress, - getVaultDepositAmount, -} from '../functions/proof-of-reserve/proof-of-reserve-functions.js'; +import { getVaultProofOfReserveData } from '../functions/proof-of-reserve/proof-of-reserve-functions.js'; import { RawVault } from '../models/ethereum-models.js'; /** @@ -34,42 +32,37 @@ export class ProofOfReserveHandler { } /** - * Gets all vault addresses from a list of vaults. - * - * @param vaults - An array of vault objects containing address information - * @returns A promise that resolves to an array of vault addresses - */ - async getAllVaultAddresses(vaults: RawVault[]): Promise { - return Promise.all( - vaults.map(vault => - getVaultAddress(vault, this.extendedAttestorGroupPublicKey, this.bitcoinNetwork) - ) - ).then(addresses => addresses.filter(isNotNil)); - } - - /** - * Calculates the total value of deposits for a list of vaults in satoshis. + * Gets the Proof of Reserve data for a list of vaults. * * @param vaults - An array of vault objects containing deposit information - * @returns A promise that resolves to the total value of deposits in the vaults in satoshis + * @returns A promise that resolves to the Proof of Reserve data for the vaults */ - async calculateProofOfReserve(vaults: RawVault[]): Promise { + async getProofOfReserveData(vaults: RawVault[]): Promise { const bitcoinBlockchainBlockHeight = await fetchBitcoinBlockchainBlockHeight( this.bitcoinBlockchainAPI ); - const depositAmounts = await Promise.all( - vaults.map(vault => - getVaultDepositAmount( - vault, - this.extendedAttestorGroupPublicKey, - bitcoinBlockchainBlockHeight, - this.bitcoinBlockchainAPI, - this.bitcoinNetwork + const proofOfReserveData = ( + await Promise.all( + vaults.map(vault => + getVaultProofOfReserveData( + vault, + this.extendedAttestorGroupPublicKey, + bitcoinBlockchainBlockHeight, + this.bitcoinBlockchainAPI, + this.bitcoinNetwork + ) ) ) - ); + ).filter(isNotNil); - return depositAmounts.reduce((a, b) => a + b, 0); + return proofOfReserveData.reduce( + (acc, curr) => { + acc.proofOfReserve += curr.amount; + acc.vaultAddresses.push(curr.address); + return acc; + }, + { proofOfReserve: 0, vaultAddresses: [] as string[] } + ); } } diff --git a/tests/unit/proof-of-reserve.test.ts b/tests/unit/proof-of-reserve.test.ts index e5a561c..643dff5 100644 --- a/tests/unit/proof-of-reserve.test.ts +++ b/tests/unit/proof-of-reserve.test.ts @@ -1,10 +1,7 @@ import { bitcoin, testnet } from 'bitcoinjs-lib/src/networks.js'; import * as bitcoinRequestFunctions from '../../src/functions/bitcoin/bitcoin-request-functions.js'; -import { - getVaultAddress, - getVaultDepositAmount, -} from '../../src/functions/proof-of-reserve/proof-of-reserve-functions.js'; +import { getVaultProofOfReserveData } from '../../src/functions/proof-of-reserve/proof-of-reserve-functions.js'; import { TEST_MAINNET_BITCOIN_BLOCKCHAIN_API, TEST_TESTNET_BITCOIN_BLOCKCHAIN_API, @@ -24,11 +21,7 @@ import { TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_3, } from '../mocks/bitcoin.test.constants.js'; -import { - TEST_VAULT_2, - TEST_VAULT_3, - TEST_VAULT_4, -} 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(() => { @@ -40,7 +33,7 @@ describe('Proof of Reserve Calculation', () => { .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_1); - const result = await getVaultDepositAmount( + const result = await getVaultProofOfReserveData( TEST_VAULT_2, TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, @@ -48,7 +41,10 @@ describe('Proof of Reserve Calculation', () => { testnet ); - expect(result).toBe(10000000); + expect(result).toStrictEqual({ + amount: 10000000, + address: 'tb1pd4l9qxw8jhg9l57ls9cnq6d28gcfayf2v9244vlt6mj80apvracqgdt090', + }); }); it('should return 0 if the funding transaction is not found', async () => { @@ -58,7 +54,7 @@ describe('Proof of Reserve Calculation', () => { throw new Error('Transaction not found'); }); - const result = await getVaultDepositAmount( + const result = await getVaultProofOfReserveData( TEST_VAULT_2, TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, @@ -66,7 +62,7 @@ describe('Proof of Reserve Calculation', () => { testnet ); - expect(result).toBe(0); + expect(result).toBeUndefined(); }); it("should return 0 when the vault's funding transaction is not yet confirmed", async () => { @@ -74,7 +70,7 @@ describe('Proof of Reserve Calculation', () => { .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_1); - const result = await getVaultDepositAmount( + const result = await getVaultProofOfReserveData( TEST_VAULT_2, TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2, @@ -82,7 +78,7 @@ describe('Proof of Reserve Calculation', () => { testnet ); - expect(result).toBe(0); + expect(result).toBeUndefined(); }); it("should return 0 if the vault's funding transaction lacks an output with the multisig's script", async () => { @@ -90,7 +86,7 @@ describe('Proof of Reserve Calculation', () => { .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_2); - const result = await getVaultDepositAmount( + const result = await getVaultProofOfReserveData( TEST_VAULT_2, TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_1, @@ -98,7 +94,7 @@ describe('Proof of Reserve Calculation', () => { testnet ); - expect(result).toBe(0); + expect(result).toBeUndefined(); }); it("should return 0 if the vault is legacy and it's funding transaction lacks an output with the multisig's script", async () => { @@ -106,7 +102,7 @@ describe('Proof of Reserve Calculation', () => { .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') .mockImplementationOnce(async () => TEST_MAINNET_FUNDING_TRANSACTION_1); - const result = await getVaultDepositAmount( + const result = await getVaultProofOfReserveData( TEST_VAULT_3, TEST_MAINNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_3, @@ -114,7 +110,7 @@ describe('Proof of Reserve Calculation', () => { bitcoin ); - expect(result).toBe(0); + expect(result).toBeUndefined(); }); it("should return the vault's previous deposit amount when the vault's funding transaction is not yet confirmed", async () => { @@ -122,7 +118,7 @@ describe('Proof of Reserve Calculation', () => { .spyOn(bitcoinRequestFunctions, 'fetchBitcoinTransaction') .mockImplementationOnce(async () => TEST_TESTNET_FUNDING_TRANSACTION_7); - const result = await getVaultDepositAmount( + const result = await getVaultProofOfReserveData( TEST_VAULT_2, TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, TEST_BITCOIN_BLOCKCHAIN_BLOCK_HEIGHT_2, @@ -130,26 +126,10 @@ describe('Proof of Reserve Calculation', () => { testnet ); - expect(result).toBe(100000); - }); - }); - describe('getVaultAddress', () => { - it('should return the vault address', async () => { - const result = await getVaultAddress( - TEST_VAULT_2, - TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, - testnet - ); - expect(result).toBe('tb1pd4l9qxw8jhg9l57ls9cnq6d28gcfayf2v9244vlt6mj80apvracqgdt090'); - }); - - it('should return none if the vault has no funding transaction', async () => { - const result = await getVaultAddress( - TEST_VAULT_4, - TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1, - testnet - ); - expect(result).toBeUndefined(); + expect(result).toStrictEqual({ + amount: 100000, + address: 'tb1pd4l9qxw8jhg9l57ls9cnq6d28gcfayf2v9244vlt6mj80apvracqgdt090', + }); }); }); });