From 8c3174a31fe1dc8c08099d359f07c50178cc677c Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 17 Jul 2024 17:13:22 +0200 Subject: [PATCH 1/8] feat: replace attestor handler with attestor functions --- package.json | 2 + src/attestor-handlers/attestor-handler.ts | 75 ----------------- .../attestor/attestor-request.functions.ts | 83 +++++++++++++++++++ src/functions/attestor/index.ts | 4 + src/index.ts | 2 - tests/unit/attestor-request-functions.test.ts | 51 ++++++++++++ yarn.lock | 24 ++++++ 7 files changed, 164 insertions(+), 77 deletions(-) delete mode 100644 src/attestor-handlers/attestor-handler.ts create mode 100644 src/functions/attestor/attestor-request.functions.ts create mode 100644 src/functions/attestor/index.ts create mode 100644 tests/unit/attestor-request-functions.test.ts diff --git a/package.json b/package.json index 99e321b..086975a 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@noble/hashes": "^1.4.0", "@scure/base": "^1.1.6", "@scure/btc-signer": "^1.3.1", + "@types/ramda": "^0.30.1", "bip32": "^4.0.0", "bitcoinjs-lib": "^6.1.5", "chalk": "^5.3.0", @@ -68,6 +69,7 @@ "ethers": "5.7.2", "ledger-bitcoin": "^0.2.3", "prompts": "^2.4.2", + "ramda": "^0.30.1", "scure": "^1.6.0", "tiny-secp256k1": "^2.2.3" } diff --git a/src/attestor-handlers/attestor-handler.ts b/src/attestor-handlers/attestor-handler.ts deleted file mode 100644 index e29bd32..0000000 --- a/src/attestor-handlers/attestor-handler.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { FundingTXAttestorInfo, WithdrawDepositTXAttestorInfo } from '../models/attestor.models.js'; -import { AttestorError } from '../models/errors.js'; - -export class AttestorHandler { - private attestorRootURLs: string[]; - - constructor(attestorRootURLs: string[]) { - this.attestorRootURLs = attestorRootURLs; - } - - private async sendRequest(url: string, body: string): Promise { - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, - body, - }); - if (!response.ok) { - throw new AttestorError(`Attestor Response ${url} was not OK: ${response.statusText}`); - } - return true; - } - - async submitFundingPSBT(fundingTXAttestorInfo: FundingTXAttestorInfo): Promise { - const fundingEndpoints = this.attestorRootURLs.map(url => `${url}/app/create-psbt-event`); - - const body = JSON.stringify({ - uuid: fundingTXAttestorInfo.vaultUUID, - funding_transaction_psbt: fundingTXAttestorInfo.fundingPSBT, - mint_address: fundingTXAttestorInfo.userEthereumAddress, - chain: fundingTXAttestorInfo.attestorChainID, - alice_pubkey: fundingTXAttestorInfo.userBitcoinTaprootPublicKey, - }); - - const attestorResponses: (boolean | string)[] = await Promise.all( - fundingEndpoints.map(async url => - this.sendRequest(url, body) - .then(response => response) - .catch(error => error.message) - ) - ); - - if (attestorResponses.every(response => response !== true)) { - throw new AttestorError( - `Error sending [Funding] Transaction to Attestors: - ${attestorResponses.join('| ')}` - ); - } - } - - async submitWithdrawDepositPSBT( - withdrawDepositTXAttestorInfo: WithdrawDepositTXAttestorInfo - ): Promise { - const depositWithdrawEndpoints = this.attestorRootURLs.map(url => `${url}/app/withdraw`); - - const body = JSON.stringify({ - uuid: withdrawDepositTXAttestorInfo.vaultUUID, - wd_psbt: withdrawDepositTXAttestorInfo.depositWithdrawPSBT, - }); - - const attestorResponses: (boolean | string)[] = await Promise.all( - depositWithdrawEndpoints.map(async url => - this.sendRequest(url, body) - .then(response => response) - .catch(error => error.message) - ) - ); - - if (attestorResponses.every(response => response !== true)) { - throw new AttestorError( - `Error sending [Deposit/Withdraw] Transaction to Attestors: - ${attestorResponses.join('| ')}` - ); - } - } -} diff --git a/src/functions/attestor/attestor-request.functions.ts b/src/functions/attestor/attestor-request.functions.ts new file mode 100644 index 0000000..f2f646a --- /dev/null +++ b/src/functions/attestor/attestor-request.functions.ts @@ -0,0 +1,83 @@ +import { isEmpty, join } from 'ramda'; +import { + FundingTXAttestorInfo, + WithdrawDepositTXAttestorInfo, +} from 'src/models/attestor.models.js'; +import { AttestorError } from 'src/models/errors.js'; + +export async function sendRequest(url: string, body: string): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, + body, + }); + if (!response.ok) { + throw new AttestorError(`Attestor Response ${url} was not OK: ${response.statusText}`); + } + return true; +} + +export async function submitFundingPSBT( + attestorRootURLs: string[], + fundingTXAttestorInfo: FundingTXAttestorInfo +): Promise { + if (isEmpty(attestorRootURLs)) { + throw new AttestorError('No Attestor URLs provided'); + } + + const fundingEndpoints = attestorRootURLs.map(url => `${url}/app/create-psbt-event`); + + const body = JSON.stringify({ + uuid: fundingTXAttestorInfo.vaultUUID, + funding_transaction_psbt: fundingTXAttestorInfo.fundingPSBT, + mint_address: fundingTXAttestorInfo.userEthereumAddress, + chain: fundingTXAttestorInfo.attestorChainID, + alice_pubkey: fundingTXAttestorInfo.userBitcoinTaprootPublicKey, + }); + + const attestorResponses: (boolean | string)[] = await Promise.all( + fundingEndpoints.map(async url => + sendRequest(url, body) + .then(response => response) + .catch(error => error.message) + ) + ); + + if (attestorResponses.every(response => response !== true)) { + throw new AttestorError( + `Error sending [Funding] Transaction to Attestors: + ${join('|', attestorResponses)}` + ); + } +} + +export async function submitWithdrawDepositPSBT( + attestorRootURLs: string[], + withdrawDepositTXAttestorInfo: WithdrawDepositTXAttestorInfo +): Promise { + if (isEmpty(attestorRootURLs)) { + throw new AttestorError('No Attestor URLs provided'); + } + + const depositWithdrawEndpoints = attestorRootURLs.map(url => `${url}/app/withdraw`); + + const body = JSON.stringify({ + uuid: withdrawDepositTXAttestorInfo.vaultUUID, + wd_psbt: withdrawDepositTXAttestorInfo.depositWithdrawPSBT, + }); + + const attestorResponses: (boolean | string)[] = await Promise.all( + depositWithdrawEndpoints.map(async url => + sendRequest(url, body) + .then(response => response) + .catch(error => error.message) + ) + ); + + if (attestorResponses.every(response => response !== true)) { + throw new AttestorError( + `Error sending [Deposit/Withdraw] Transaction to Attestors: + ${join('|', attestorResponses)}` + ); + } +} diff --git a/src/functions/attestor/index.ts b/src/functions/attestor/index.ts new file mode 100644 index 0000000..8b032cd --- /dev/null +++ b/src/functions/attestor/index.ts @@ -0,0 +1,4 @@ +export { + submitFundingPSBT, + submitWithdrawDepositPSBT, +} from '../attestor/attestor-request.functions.js'; diff --git a/src/index.ts b/src/index.ts index 0f82efd..24adf02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -import { AttestorHandler } from './attestor-handlers/attestor-handler.js'; import { LedgerDLCHandler } from './dlc-handlers/ledger-dlc-handler.js'; import { PrivateKeyDLCHandler } from './dlc-handlers/private-key-dlc-handler.js'; import { SoftwareWalletDLCHandler } from './dlc-handlers/software-wallet-dlc-handler.js'; @@ -12,6 +11,5 @@ export { SoftwareWalletDLCHandler, EthereumHandler, ReadOnlyEthereumHandler, - AttestorHandler, ProofOfReserveHandler, }; diff --git a/tests/unit/attestor-request-functions.test.ts b/tests/unit/attestor-request-functions.test.ts new file mode 100644 index 0000000..78e221b --- /dev/null +++ b/tests/unit/attestor-request-functions.test.ts @@ -0,0 +1,51 @@ +import * as attestorRequestFunctions from '../../src/functions/attestor/attestor-request.functions.js'; +import { submitFundingPSBT } from '../../src/functions/attestor/attestor-request.functions.js'; + +describe('Attestor ', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('submitFundingPSBT', () => { + it('should not throw an error if all requests were succesful', async () => { + jest + .spyOn(attestorRequestFunctions, 'sendRequest') + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => true); + + await expect( + submitFundingPSBT( + ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], + { + vaultUUID: 'vaultUUID', + fundingPSBT: 'fundingPSBT', + userEthereumAddress: 'userEthereumAddress', + attestorChainID: 'evm-arbitrum', + userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', + } + ) + ).resolves.not.toThrow(); + }); + + it('should not throw an error if not all requests were succesful', async () => { + jest + .spyOn(attestorRequestFunctions, 'sendRequest') + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => false); + + await expect( + submitFundingPSBT( + ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], + { + vaultUUID: 'vaultUUID', + fundingPSBT: 'fundingPSBT', + userEthereumAddress: 'userEthereumAddress', + attestorChainID: 'evm-arbitrum', + userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', + } + ) + ).resolves.not.toThrow(); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 920bab7..8aa3761 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1301,6 +1301,13 @@ "@types/node" "*" kleur "^3.0.3" +"@types/ramda@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.30.1.tgz#316257fec12747bb39a2e921df48a9dcb8c164a9" + integrity sha512-aoyF/ADPL6N+/NXXfhPWF+Qj6w1Cql59m9wX0Gi15uyF+bpzXeLd63HPdiTDE2bmLXfNcVufsDPKmbfOrOzTBA== + dependencies: + types-ramda "^0.30.1" + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -4207,6 +4214,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +ramda@^0.30.1: + version "0.30.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.30.1.tgz#7108ac95673062b060025052cd5143ae8fc605bf" + integrity sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw== + randombytes@^2.0.1, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -4747,6 +4759,11 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +ts-toolbelt@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5" + integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w== + tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -4808,6 +4825,13 @@ typeforce@^1.11.3, typeforce@^1.11.5, typeforce@^1.18.0: resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== +types-ramda@^0.30.1: + version "0.30.1" + resolved "https://registry.yarnpkg.com/types-ramda/-/types-ramda-0.30.1.tgz#03d255128e3696aeaac76281ca19949e01dddc78" + integrity sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA== + dependencies: + ts-toolbelt "^9.6.0" + typescript-eslint@^7.7.0: version "7.11.0" resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-7.11.0.tgz#7a208fc1d178b3fed58e33ce37150ac6efecf1fb" From 54985697f76fdfa61a7e6d974bbf8a0931973313 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 17 Jul 2024 18:01:25 +0200 Subject: [PATCH 2/8] 1.0.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 086975a..71793b5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "1.0.21", + "version": "1.0.22", "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", From 52b0270e5244af782a1216cd50edc3c6a040ba0f Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Wed, 17 Jul 2024 18:01:47 +0200 Subject: [PATCH 3/8] 1.0.23 --- package.json | 2 +- tests/unit/attestor-request-functions.test.ts | 51 ------------------- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 tests/unit/attestor-request-functions.test.ts diff --git a/package.json b/package.json index 71793b5..4e6ba80 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "1.0.22", + "version": "1.0.23", "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/tests/unit/attestor-request-functions.test.ts b/tests/unit/attestor-request-functions.test.ts deleted file mode 100644 index 78e221b..0000000 --- a/tests/unit/attestor-request-functions.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as attestorRequestFunctions from '../../src/functions/attestor/attestor-request.functions.js'; -import { submitFundingPSBT } from '../../src/functions/attestor/attestor-request.functions.js'; - -describe('Attestor ', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('submitFundingPSBT', () => { - it('should not throw an error if all requests were succesful', async () => { - jest - .spyOn(attestorRequestFunctions, 'sendRequest') - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => true); - - await expect( - submitFundingPSBT( - ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], - { - vaultUUID: 'vaultUUID', - fundingPSBT: 'fundingPSBT', - userEthereumAddress: 'userEthereumAddress', - attestorChainID: 'evm-arbitrum', - userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', - } - ) - ).resolves.not.toThrow(); - }); - - it('should not throw an error if not all requests were succesful', async () => { - jest - .spyOn(attestorRequestFunctions, 'sendRequest') - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => false); - - await expect( - submitFundingPSBT( - ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], - { - vaultUUID: 'vaultUUID', - fundingPSBT: 'fundingPSBT', - userEthereumAddress: 'userEthereumAddress', - attestorChainID: 'evm-arbitrum', - userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', - } - ) - ).resolves.not.toThrow(); - }); - }); -}); From 2c33749ea0396fd6c95f55c70b95a27da2120423 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 18 Jul 2024 12:32:23 +0200 Subject: [PATCH 4/8] feat: modify attestor request functions, add tests --- .../attestor/attestor-request.functions.ts | 106 ++++++------- src/functions/request/request.functions.ts | 11 ++ src/models/attestor.models.ts | 2 +- tests/unit/attestor-request-function.test.ts | 139 ++++++++++++++++++ 4 files changed, 195 insertions(+), 63 deletions(-) create mode 100644 src/functions/request/request.functions.ts create mode 100644 tests/unit/attestor-request-function.test.ts diff --git a/src/functions/attestor/attestor-request.functions.ts b/src/functions/attestor/attestor-request.functions.ts index f2f646a..89cc69e 100644 --- a/src/functions/attestor/attestor-request.functions.ts +++ b/src/functions/attestor/attestor-request.functions.ts @@ -1,83 +1,65 @@ -import { isEmpty, join } from 'ramda'; +import { complement, equals, filter, isEmpty, isNil, join, map, prop } from 'ramda'; + import { FundingTXAttestorInfo, WithdrawDepositTXAttestorInfo, -} from 'src/models/attestor.models.js'; -import { AttestorError } from 'src/models/errors.js'; - -export async function sendRequest(url: string, body: string): Promise { - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, - body, - }); - if (!response.ok) { - throw new AttestorError(`Attestor Response ${url} was not OK: ${response.statusText}`); - } - return true; -} - -export async function submitFundingPSBT( - attestorRootURLs: string[], - fundingTXAttestorInfo: FundingTXAttestorInfo -): Promise { - if (isEmpty(attestorRootURLs)) { - throw new AttestorError('No Attestor URLs provided'); - } +} from '../../models/attestor.models.js'; +import { AttestorError } from '../../models/errors.js'; +import { sendRequest } from '../request/request.functions.js'; - const fundingEndpoints = attestorRootURLs.map(url => `${url}/app/create-psbt-event`); - - const body = JSON.stringify({ - uuid: fundingTXAttestorInfo.vaultUUID, - funding_transaction_psbt: fundingTXAttestorInfo.fundingPSBT, - mint_address: fundingTXAttestorInfo.userEthereumAddress, - chain: fundingTXAttestorInfo.attestorChainID, - alice_pubkey: fundingTXAttestorInfo.userBitcoinTaprootPublicKey, - }); - - const attestorResponses: (boolean | string)[] = await Promise.all( - fundingEndpoints.map(async url => - sendRequest(url, body) - .then(response => response) - .catch(error => error.message) +const processAttestorResponses = async (attestorEndpoints: string[], requestBody: string) => { + const attestorErrorResponses: string[] = filter( + complement(isNil), + await Promise.all( + map( + url => sendRequest(url, requestBody).catch(error => prop('message', error)), + attestorEndpoints + ) ) ); - if (attestorResponses.every(response => response !== true)) { + if (equals(attestorEndpoints.length, attestorErrorResponses.length)) { throw new AttestorError( - `Error sending [Funding] Transaction to Attestors: - ${join('|', attestorResponses)}` + `Error sending Transaction to Attestors: ${join('|', attestorErrorResponses)}` ); } -} +}; -export async function submitWithdrawDepositPSBT( +export async function submitPSBT( attestorRootURLs: string[], - withdrawDepositTXAttestorInfo: WithdrawDepositTXAttestorInfo + transactionInfo: T, + endpointPath: string, + transformBody: (transactionInfo: T) => object ): Promise { if (isEmpty(attestorRootURLs)) { throw new AttestorError('No Attestor URLs provided'); } - const depositWithdrawEndpoints = attestorRootURLs.map(url => `${url}/app/withdraw`); + const endpoints: string[] = attestorRootURLs.map(url => `${url}${endpointPath}`); + const requestBody: string = JSON.stringify(transformBody(transactionInfo)); - const body = JSON.stringify({ - uuid: withdrawDepositTXAttestorInfo.vaultUUID, - wd_psbt: withdrawDepositTXAttestorInfo.depositWithdrawPSBT, - }); + await processAttestorResponses(endpoints, requestBody); +} - const attestorResponses: (boolean | string)[] = await Promise.all( - depositWithdrawEndpoints.map(async url => - sendRequest(url, body) - .then(response => response) - .catch(error => error.message) - ) - ); +export async function submitFundingPSBT( + attestorRootURLs: string[], + fundingTXAttestorInfo: FundingTXAttestorInfo +): Promise { + await submitPSBT(attestorRootURLs, fundingTXAttestorInfo, '/app/create-psbt-event', info => ({ + uuid: info.vaultUUID, + funding_transaction_psbt: info.fundingPSBT, + mint_address: info.userEthereumAddress, + chain: info.attestorChainID, + alice_pubkey: info.userBitcoinTaprootPublicKey, + })); +} - if (attestorResponses.every(response => response !== true)) { - throw new AttestorError( - `Error sending [Deposit/Withdraw] Transaction to Attestors: - ${join('|', attestorResponses)}` - ); - } +export async function submitWithdrawDepositPSBT( + attestorRootURLs: string[], + withdrawDepositTXAttestorInfo: WithdrawDepositTXAttestorInfo +): Promise { + await submitPSBT(attestorRootURLs, withdrawDepositTXAttestorInfo, '/app/withdraw', info => ({ + uuid: info.vaultUUID, + wd_psbt: info.withdrawDepositPSBT, + })); } diff --git a/src/functions/request/request.functions.ts b/src/functions/request/request.functions.ts new file mode 100644 index 0000000..5955b3b --- /dev/null +++ b/src/functions/request/request.functions.ts @@ -0,0 +1,11 @@ +export async function sendRequest(url: string, body: string): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, + body, + }); + + if (!response.ok) { + throw new Error(`Response ${url} was not OK: ${response.statusText}`); + } +} diff --git a/src/models/attestor.models.ts b/src/models/attestor.models.ts index 8ba4653..538fd1e 100644 --- a/src/models/attestor.models.ts +++ b/src/models/attestor.models.ts @@ -15,5 +15,5 @@ export interface FundingTXAttestorInfo { export interface WithdrawDepositTXAttestorInfo { vaultUUID: string; - depositWithdrawPSBT: string; + withdrawDepositPSBT: string; } diff --git a/tests/unit/attestor-request-function.test.ts b/tests/unit/attestor-request-function.test.ts new file mode 100644 index 0000000..5f93c9d --- /dev/null +++ b/tests/unit/attestor-request-function.test.ts @@ -0,0 +1,139 @@ +import { + submitFundingPSBT, + submitWithdrawDepositPSBT, +} from '../../src/functions/attestor/attestor-request.functions'; +import * as requestFunctions from '../../src/functions/request/request.functions'; +import { + FundingTXAttestorInfo, + WithdrawDepositTXAttestorInfo, +} from '../../src/models/attestor.models'; +import { AttestorError } from '../../src/models/errors'; +import { TEST_REGTEST_ATTESTOR_APIS } from '../mocks/api.test.constants'; + +describe('Attestor Request Sending', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('submitFundingPSBT', () => { + const fundingTXAttestorInfo: FundingTXAttestorInfo = { + vaultUUID: 'vault-uuid', + fundingPSBT: 'funding-psbt', + userEthereumAddress: 'user-ethereum-address', + userBitcoinTaprootPublicKey: 'user-bitcoin-taproot-public-key', + attestorChainID: 'evm-arbitrum', + }; + it('should succeed without errors when all requests are successful', async () => { + jest + .spyOn(requestFunctions, 'sendRequest') + .mockImplementationOnce(async () => {}) + .mockImplementationOnce(async () => {}) + .mockImplementationOnce(async () => {}); + + await expect( + submitFundingPSBT(TEST_REGTEST_ATTESTOR_APIS, fundingTXAttestorInfo) + ).resolves.not.toThrow(); + }); + + it('should not throw an error if not all requests are successful', async () => { + jest + .spyOn(requestFunctions, 'sendRequest') + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[0]} was not OK`); + }) + .mockImplementationOnce(async () => {}) + .mockImplementationOnce(async () => {}); + + await expect( + submitFundingPSBT(TEST_REGTEST_ATTESTOR_APIS, fundingTXAttestorInfo) + ).resolves.not.toThrow(); + }); + + it('should throw an error if all requests fail', async () => { + jest + .spyOn(requestFunctions, 'sendRequest') + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[0]} was not OK`); + }) + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[1]} was not OK`); + }) + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[2]} was not OK`); + }); + + await expect( + submitFundingPSBT(TEST_REGTEST_ATTESTOR_APIS, fundingTXAttestorInfo) + ).rejects.toThrow( + new AttestorError( + `Error sending Transaction to Attestors: Response ${TEST_REGTEST_ATTESTOR_APIS[0]} was not OK|Response ${TEST_REGTEST_ATTESTOR_APIS[1]} was not OK|Response ${TEST_REGTEST_ATTESTOR_APIS[2]} was not OK` + ) + ); + }); + + it('should raise an error when the attestorURLs parameter is empty', async () => { + await expect(submitFundingPSBT([], fundingTXAttestorInfo)).rejects.toThrow( + new AttestorError('No Attestor URLs provided') + ); + }); + }); + describe('submitWithdrawDepositPSBT', () => { + const withdrawDepositTXAttestorInfo: WithdrawDepositTXAttestorInfo = { + vaultUUID: 'vault-uuid', + withdrawDepositPSBT: 'deposit-withdraw-psbt', + }; + + it('should succeed without errors when all requests are successful', async () => { + jest + .spyOn(requestFunctions, 'sendRequest') + .mockImplementationOnce(async () => {}) + .mockImplementationOnce(async () => {}) + .mockImplementationOnce(async () => {}); + + await expect( + submitWithdrawDepositPSBT(TEST_REGTEST_ATTESTOR_APIS, withdrawDepositTXAttestorInfo) + ).resolves.not.toThrow(); + }); + + it('should not throw an error if not all requests are successful', async () => { + jest + .spyOn(requestFunctions, 'sendRequest') + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[0]} was not OK`); + }) + .mockImplementationOnce(async () => {}) + .mockImplementationOnce(async () => {}); + + await expect( + submitWithdrawDepositPSBT(TEST_REGTEST_ATTESTOR_APIS, withdrawDepositTXAttestorInfo) + ).resolves.not.toThrow(); + }); + + it('should throw an error if all requests fail', async () => { + jest + .spyOn(requestFunctions, 'sendRequest') + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[0]} was not OK`); + }) + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[1]} was not OK`); + }) + .mockImplementationOnce(async () => { + throw new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[2]} was not OK`); + }); + + await expect( + submitWithdrawDepositPSBT(TEST_REGTEST_ATTESTOR_APIS, withdrawDepositTXAttestorInfo) + ).rejects.toThrow( + new AttestorError( + `Error sending Transaction to Attestors: Response ${TEST_REGTEST_ATTESTOR_APIS[0]} was not OK|Response ${TEST_REGTEST_ATTESTOR_APIS[1]} was not OK|Response ${TEST_REGTEST_ATTESTOR_APIS[2]} was not OK` + ) + ); + }); + + it('should raise an error when the attestorURLs parameter is empty', async () => { + await expect(submitWithdrawDepositPSBT([], withdrawDepositTXAttestorInfo)).rejects.toThrow( + new AttestorError('No Attestor URLs provided') + ); + }); + }); +}); From 5cc0e956437b60c1cd601269dc01375db15804dd Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 18 Jul 2024 13:50:12 +0200 Subject: [PATCH 5/8] feat: add request function tests --- .../attestor/attestor-request.functions.ts | 70 +++++++++---------- tests/unit/request-functions.test.ts | 43 ++++++++++++ 2 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 tests/unit/request-functions.test.ts diff --git a/src/functions/attestor/attestor-request.functions.ts b/src/functions/attestor/attestor-request.functions.ts index 89cc69e..64992bc 100644 --- a/src/functions/attestor/attestor-request.functions.ts +++ b/src/functions/attestor/attestor-request.functions.ts @@ -1,4 +1,4 @@ -import { complement, equals, filter, isEmpty, isNil, join, map, prop } from 'ramda'; +import { equals, filter, isEmpty, isNotNil, join, map, prop } from 'ramda'; import { FundingTXAttestorInfo, @@ -7,40 +7,6 @@ import { import { AttestorError } from '../../models/errors.js'; import { sendRequest } from '../request/request.functions.js'; -const processAttestorResponses = async (attestorEndpoints: string[], requestBody: string) => { - const attestorErrorResponses: string[] = filter( - complement(isNil), - await Promise.all( - map( - url => sendRequest(url, requestBody).catch(error => prop('message', error)), - attestorEndpoints - ) - ) - ); - - if (equals(attestorEndpoints.length, attestorErrorResponses.length)) { - throw new AttestorError( - `Error sending Transaction to Attestors: ${join('|', attestorErrorResponses)}` - ); - } -}; - -export async function submitPSBT( - attestorRootURLs: string[], - transactionInfo: T, - endpointPath: string, - transformBody: (transactionInfo: T) => object -): Promise { - if (isEmpty(attestorRootURLs)) { - throw new AttestorError('No Attestor URLs provided'); - } - - const endpoints: string[] = attestorRootURLs.map(url => `${url}${endpointPath}`); - const requestBody: string = JSON.stringify(transformBody(transactionInfo)); - - await processAttestorResponses(endpoints, requestBody); -} - export async function submitFundingPSBT( attestorRootURLs: string[], fundingTXAttestorInfo: FundingTXAttestorInfo @@ -63,3 +29,37 @@ export async function submitWithdrawDepositPSBT( wd_psbt: info.withdrawDepositPSBT, })); } + +export async function submitPSBT( + attestorRootURLs: string[], + transactionInfo: T, + endpointPath: string, + transformBody: (transactionInfo: T) => object +): Promise { + if (isEmpty(attestorRootURLs)) { + throw new AttestorError('No Attestor URLs provided'); + } + + const endpoints: string[] = attestorRootURLs.map(url => `${url}${endpointPath}`); + const requestBody: string = JSON.stringify(transformBody(transactionInfo)); + + await sendAndProcessRequests(endpoints, requestBody); +} + +const sendAndProcessRequests = async (attestorEndpoints: string[], requestBody: string) => { + const attestorErrorResponses: string[] = filter( + isNotNil, + await Promise.all( + map( + url => sendRequest(url, requestBody).catch(error => prop('message', error)), + attestorEndpoints + ) + ) + ); + + if (equals(attestorEndpoints.length, attestorErrorResponses.length)) { + throw new AttestorError( + `Error sending Transaction to Attestors: ${join('|', attestorErrorResponses)}` + ); + } +}; diff --git a/tests/unit/request-functions.test.ts b/tests/unit/request-functions.test.ts new file mode 100644 index 0000000..b7c0098 --- /dev/null +++ b/tests/unit/request-functions.test.ts @@ -0,0 +1,43 @@ +import { sendRequest } from '../../src/functions/request/request.functions'; +import { TEST_REGTEST_ATTESTOR_APIS } from '../mocks/api.test.constants'; + +global.fetch = jest.fn(); + +describe('Request Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('sendRequest', () => { + it('should not result in an error when the response status is ok', async () => { + jest + .spyOn(global, 'fetch') + .mockImplementationOnce(async () => new Response(null, { status: 200 })); + + await expect( + sendRequest(TEST_REGTEST_ATTESTOR_APIS[0], 'requestBody') + ).resolves.not.toThrow(); + }); + + it('should result in an error when the response status is not ok', async () => { + jest + .spyOn(global, 'fetch') + .mockImplementationOnce( + async () => new Response(null, { status: 400, statusText: 'Bad Request' }) + ); + + await expect(sendRequest(TEST_REGTEST_ATTESTOR_APIS[0], 'requestBody')).rejects.toThrow( + new Error(`Response ${TEST_REGTEST_ATTESTOR_APIS[0]} was not OK: Bad Request`) + ); + }); + + it('should result in an error when the request fails', async () => { + jest.spyOn(global, 'fetch').mockImplementationOnce(async () => { + throw new Error('Failed to fetch'); + }); + + await expect(sendRequest(TEST_REGTEST_ATTESTOR_APIS[0], 'requestBody')).rejects.toThrow( + new Error(`Failed to fetch`) + ); + }); + }); +}); From f61cd39cf2f4ea44b4cfbb3847877f33fe9415b3 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 18 Jul 2024 16:03:50 +0200 Subject: [PATCH 6/8] feat: export attestor functions --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 4e6ba80..ad1067a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "./constants": "./dist/constants/index.js", "./models": "./dist/models/index.js", "./bitcoin-functions": "./dist/functions/bitcoin/index.js", + "./attestor-request-functions": "./dist/functions/attestor/index.js", "./ethereum-functions": "./dist/functions/ethereum/index.js" }, "scripts": { From 7decf0defa2302f8cfcf0d07f30051d28e4bed98 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 18 Jul 2024 16:03:52 +0200 Subject: [PATCH 7/8] revert: 1.0.23 package update This reverts commit 52b0270e5244af782a1216cd50edc3c6a040ba0f. --- package.json | 2 +- tests/unit/attestor-request-functions.test.ts | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/unit/attestor-request-functions.test.ts diff --git a/package.json b/package.json index ad1067a..7e07e99 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "1.0.23", + "version": "1.0.22", "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/tests/unit/attestor-request-functions.test.ts b/tests/unit/attestor-request-functions.test.ts new file mode 100644 index 0000000..78e221b --- /dev/null +++ b/tests/unit/attestor-request-functions.test.ts @@ -0,0 +1,51 @@ +import * as attestorRequestFunctions from '../../src/functions/attestor/attestor-request.functions.js'; +import { submitFundingPSBT } from '../../src/functions/attestor/attestor-request.functions.js'; + +describe('Attestor ', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('submitFundingPSBT', () => { + it('should not throw an error if all requests were succesful', async () => { + jest + .spyOn(attestorRequestFunctions, 'sendRequest') + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => true); + + await expect( + submitFundingPSBT( + ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], + { + vaultUUID: 'vaultUUID', + fundingPSBT: 'fundingPSBT', + userEthereumAddress: 'userEthereumAddress', + attestorChainID: 'evm-arbitrum', + userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', + } + ) + ).resolves.not.toThrow(); + }); + + it('should not throw an error if not all requests were succesful', async () => { + jest + .spyOn(attestorRequestFunctions, 'sendRequest') + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => true) + .mockImplementationOnce(async () => false); + + await expect( + submitFundingPSBT( + ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], + { + vaultUUID: 'vaultUUID', + fundingPSBT: 'fundingPSBT', + userEthereumAddress: 'userEthereumAddress', + attestorChainID: 'evm-arbitrum', + userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', + } + ) + ).resolves.not.toThrow(); + }); + }); +}); From 5ac535212be25950c7eb5c227dac4e34a2583cc9 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 18 Jul 2024 16:12:53 +0200 Subject: [PATCH 8/8] 2.0.0 --- package.json | 2 +- tests/unit/attestor-request-functions.test.ts | 51 ------------------- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 tests/unit/attestor-request-functions.test.ts diff --git a/package.json b/package.json index 7e07e99..bd90b8c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "1.0.22", + "version": "2.0.0", "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/tests/unit/attestor-request-functions.test.ts b/tests/unit/attestor-request-functions.test.ts deleted file mode 100644 index 78e221b..0000000 --- a/tests/unit/attestor-request-functions.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as attestorRequestFunctions from '../../src/functions/attestor/attestor-request.functions.js'; -import { submitFundingPSBT } from '../../src/functions/attestor/attestor-request.functions.js'; - -describe('Attestor ', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('submitFundingPSBT', () => { - it('should not throw an error if all requests were succesful', async () => { - jest - .spyOn(attestorRequestFunctions, 'sendRequest') - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => true); - - await expect( - submitFundingPSBT( - ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], - { - vaultUUID: 'vaultUUID', - fundingPSBT: 'fundingPSBT', - userEthereumAddress: 'userEthereumAddress', - attestorChainID: 'evm-arbitrum', - userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', - } - ) - ).resolves.not.toThrow(); - }); - - it('should not throw an error if not all requests were succesful', async () => { - jest - .spyOn(attestorRequestFunctions, 'sendRequest') - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => true) - .mockImplementationOnce(async () => false); - - await expect( - submitFundingPSBT( - ['http://localhost:3000', 'http://localhost:4000', 'http://localhost:5000'], - { - vaultUUID: 'vaultUUID', - fundingPSBT: 'fundingPSBT', - userEthereumAddress: 'userEthereumAddress', - attestorChainID: 'evm-arbitrum', - userBitcoinTaprootPublicKey: 'userBitcoinTaprootPublicKey', - } - ) - ).resolves.not.toThrow(); - }); - }); -});