From 26e75b913516f6a24a9465bbfce84f3ee8bf6efb Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 13:02:48 +0200 Subject: [PATCH 01/21] Implement HttpApi class for GET and POST requests The abstract HttpApi class will be used for integration with external APIs to fetch GET and POST requests. --- sdk/src/api/HttpApi.ts | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 sdk/src/api/HttpApi.ts diff --git a/sdk/src/api/HttpApi.ts b/sdk/src/api/HttpApi.ts new file mode 100644 index 000000000..351cd8f6e --- /dev/null +++ b/sdk/src/api/HttpApi.ts @@ -0,0 +1,47 @@ +/** + * Represents an abstract HTTP API. + */ +export default abstract class HttpApi { + #apiUrl: string + + constructor(apiUrl: string) { + this.#apiUrl = apiUrl + } + + /** + * Factory function for running GET requests + * @param endpoint api endpoint + * @param requestInit additional data passed to request + * @returns api response + */ + protected async getRequest( + endpoint: string, + requestInit?: RequestInit, + ): Promise { + return fetch(new URL(endpoint, this.#apiUrl), { + credentials: "include", + ...requestInit, + }) + } + + /** + * Factory function for running POST requests + * @param endpoint api endpoint + * @param body data passed to POST request + * @param requestInit additional data passed to request + * @returns api response + */ + protected async postRequest( + endpoint: string, + body: unknown, + requestInit?: RequestInit, + ): Promise { + return fetch(new URL(endpoint, this.#apiUrl), { + method: "POST", + body: JSON.stringify(body), + credentials: "include", + headers: { "Content-Type": "application/json" }, + ...requestInit, + }) + } +} From 226465b0827a64768467834090f9fb67c644e74e Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 13:22:20 +0200 Subject: [PATCH 02/21] Implement a class for integration with tBTC API The class integrates with tBTC API to submit deposit reveal data and initiate deposit process. --- sdk/src/api/TbtcApi.ts | 118 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 sdk/src/api/TbtcApi.ts diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/api/TbtcApi.ts new file mode 100644 index 000000000..6f98ee4c3 --- /dev/null +++ b/sdk/src/api/TbtcApi.ts @@ -0,0 +1,118 @@ +import { BitcoinTxHash, BitcoinTxOutpoint } from "@keep-network/tbtc-v2.ts" +import HttpApi from "./HttpApi" + +/** + * Represents a class for integration with tBTC API. + */ +export default class TbtcApi extends HttpApi { + /** + * Register deposit data in the tBTC API. + * @param revealData Deposit data. + * @returns True if the reveal was successfully saved to the database, false + * otherwise. + */ + async saveReveal(revealData: SaveRevealRequest): Promise { + const response = await this.postRequest("reveals", revealData) + + if (!response.ok) + throw new Error( + `Reveal not saved properly in the database, response: ${response.status}`, + ) + + const { success } = (await response.json()) as { success: boolean } + + return success + } + + /** + * Initiate a bitcoin deposit in the tBTC API. + * @param depositData Data of the deposit. + * @returns Details of the initiated deposit. + */ + async createDeposit(depositData: CreateDepositRequest): Promise<{ + depositId: string + depositStatus: DepositStatus + fundingOutpoint: BitcoinTxOutpoint + }> { + const response = await this.postRequest("deposits", depositData) + if (!response.ok) + throw new Error( + `Bitcoin deposit creation failed, response: ${response.status}`, + ) + + const responseData = (await response.json()) as CreateDepositResponse + + return { + depositId: responseData.depositId, + depositStatus: responseData.newDepositStatus, + fundingOutpoint: { + transactionHash: BitcoinTxHash.from(responseData.transactionHash), + outputIndex: responseData.outputIndex, + }, + } + } +} + +/** + * Represents the metadata for a reveal operation. + */ +type RevealMetadata = { + depositOwner: string + referral: number +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the request payload for saving a reveal. + */ +type SaveRevealRequest = { + address: string + revealInfo: BitcoinDepositReceiptJson + metadata: RevealMetadata + application: string +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the JSON structure of a Bitcoin deposit receipt. + */ +type BitcoinDepositReceiptJson = { + depositor: string + blindingFactor: string + walletPublicKeyHash: string + refundPublicKeyHash: string + refundLocktime: string + extraData: string +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the request payload for creating a deposit. + */ +type CreateDepositRequest = { + depositReceipt: BitcoinDepositReceiptJson + depositOwner: string + referral: number +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the status of a deposit. + */ +enum DepositStatus { + Queued = "QUEUED", + Initialized = "INITIALIZED", + Finalized = "FINALIZED", + Cancelled = "CANCELLED", +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the response structure of a deposit creation request. + */ +type CreateDepositResponse = { + depositId: string + newDepositStatus: DepositStatus + transactionHash: string + outputIndex: number +} From c8241361581ac36f3ee2e9696157d78b24842a11 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 14:17:50 +0200 Subject: [PATCH 03/21] Implement a tBTC module to wrap tBTC API interactions The module wraps interactions with tBTC API and tBTC SDK. It should be used by staking flow to initialize a tBTC deposit and submit it to the relayer bot. --- sdk/src/api/TbtcApi.ts | 2 +- sdk/src/lib/utils/backoff.ts | 16 ++++- sdk/src/modules/tbtc/Deposit.ts | 89 +++++++++++++++++++++++++ sdk/src/modules/tbtc/Tbtc.ts | 114 ++++++++++++++++++++++++++++++++ sdk/src/modules/tbtc/index.ts | 5 ++ 5 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 sdk/src/modules/tbtc/Deposit.ts create mode 100644 sdk/src/modules/tbtc/Tbtc.ts create mode 100644 sdk/src/modules/tbtc/index.ts diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/api/TbtcApi.ts index 6f98ee4c3..786e9f0b2 100644 --- a/sdk/src/api/TbtcApi.ts +++ b/sdk/src/api/TbtcApi.ts @@ -65,7 +65,7 @@ type RevealMetadata = { /** * Represents the request payload for saving a reveal. */ -type SaveRevealRequest = { +export type SaveRevealRequest = { address: string revealInfo: BitcoinDepositReceiptJson metadata: RevealMetadata diff --git a/sdk/src/lib/utils/backoff.ts b/sdk/src/lib/utils/backoff.ts index ff5dd6676..3720d8476 100644 --- a/sdk/src/lib/utils/backoff.ts +++ b/sdk/src/lib/utils/backoff.ts @@ -2,4 +2,18 @@ import { backoffRetrier } from "@keep-network/tbtc-v2.ts" type BackoffRetrierParameters = Parameters -export { backoffRetrier, BackoffRetrierParameters } +type RetryOptions = { + /** + * The number of retries to perform before bubbling the failure out. + * @see backoffRetrier for more details. + */ + retires: BackoffRetrierParameters[0] + /** + * Initial backoff step in milliseconds that will be increased exponentially + * for subsequent retry attempts. (default = 1000 ms) + * @see backoffRetrier for more details. + */ + backoffStepMs: BackoffRetrierParameters[1] +} + +export { backoffRetrier, RetryOptions } diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts new file mode 100644 index 000000000..f5322118b --- /dev/null +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -0,0 +1,89 @@ +import { Deposit as TbtcDeposit } from "@keep-network/tbtc-v2.ts" + +import type { DepositReceipt as TbtcSdkDepositReceipt } from "@keep-network/tbtc-v2.ts" +import type { SaveRevealRequest as DepositRevealData } from "../../api/TbtcApi" + +import TbtcApi from "../../api/TbtcApi" + +import { backoffRetrier, RetryOptions } from "../../lib/utils" + +export type DepositReceipt = TbtcSdkDepositReceipt + +/** + * Represents a deposit for the tBTC protocol. + */ +export default class Deposit { + readonly #tbtcApi: TbtcApi + + readonly #tbtcDeposit: TbtcDeposit + + readonly #revealData: DepositRevealData + + constructor( + tbtcApi: TbtcApi, + tbtcDeposit: TbtcDeposit, + revealData: DepositRevealData, + ) { + this.#tbtcApi = tbtcApi + this.#tbtcDeposit = tbtcDeposit + this.#revealData = revealData + } + + /** + * Retrieves the Bitcoin address corresponding to this deposit. + * This address should be used as the destination for sending BTC to fund the + * deposit. + * @returns The Bitcoin address corresponding to this deposit. + */ + async getBitcoinAddress(): Promise { + return this.#tbtcDeposit.getBitcoinAddress() + } + + /** + * Retrieves the receipt corresponding to the tbtc deposit. + * @returns The deposit receipt. + */ + getReceipt(): DepositReceipt { + return this.#tbtcDeposit.getReceipt() + } + + /** + * Waits for the deposit to be funded with BTC. + * @param options The retry options for waiting. + * @throws Error if the deposit is not funded within the specified retries. + */ + async waitForFunding(options: RetryOptions): Promise { + await backoffRetrier( + options.retires, + options.backoffStepMs, + )(async () => { + const utxos = await this.#tbtcDeposit.detectFunding() + + if (utxos.length === 0) throw new Error("Deposit not funded yet") + }) + } + + /** + * Creates a bitcoin deposit on the tBTC API backend side. + * This function should be called after the bitcoin transaction is made to the + * deposit address. + * @throws Error if the deposit creation fails on the tBTC API side or the bitcoin + * funding transaction couldn't be found. + * @returns The tBTC API deposit ID. + */ + async createDeposit(): Promise { + const { revealInfo, metadata } = this.#revealData + const { depositOwner, referral } = metadata + + const createBitcoinDepositData = { + depositReceipt: revealInfo, + depositOwner, + referral, + } + + const response = await this.#tbtcApi.createDeposit(createBitcoinDepositData) + + // TODO: Determine return type based on dApp needs, possibly calculate depositKey. + return response.depositId + } +} diff --git a/sdk/src/modules/tbtc/Tbtc.ts b/sdk/src/modules/tbtc/Tbtc.ts new file mode 100644 index 000000000..007aa74e0 --- /dev/null +++ b/sdk/src/modules/tbtc/Tbtc.ts @@ -0,0 +1,114 @@ +import { ChainIdentifier, TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" + +import TbtcApi from "../../api/TbtcApi" +import { BitcoinDepositor } from "../../lib/contracts" +import { EthereumNetwork } from "../../lib/ethereum" + +import Deposit from "./Deposit" +import { EthereumSignerCompatibleWithEthersV5 } from "../../lib/utils" + +/** + * Represents the tBTC module. + */ +export default class Tbtc { + readonly #tbtcApi: TbtcApi + + readonly #tbtcSdk: TbtcSdk + + readonly #bitcoinDepositor: BitcoinDepositor + + constructor( + tbtcApi: TbtcApi, + tbtcSdk: TbtcSdk, + bitcoinDepositor: BitcoinDepositor, + ) { + this.#tbtcApi = tbtcApi + this.#tbtcSdk = tbtcSdk + this.#bitcoinDepositor = bitcoinDepositor + } + + /** + * Initializes the Tbtc module. + * + * @param signer The Ethereum signer compatible with ethers v5. + * @param network The Ethereum network. + * @param tbtcApiUrl The tBTC API URL. + * @param bitcoinDepositor The Bitcoin depositor contract handle. + * @returns A Promise that resolves to an instance of Tbtc. + */ + static async initialize( + signer: EthereumSignerCompatibleWithEthersV5, + network: EthereumNetwork, + tbtcApiUrl: string, + bitcoinDepositor: BitcoinDepositor, + ): Promise { + const tbtcApi = new TbtcApi(tbtcApiUrl) + + const tbtcSdk = + network === "mainnet" + ? // @ts-expect-error We require the `signer` must include the ethers v5 + // signer's methods used in tBTC-v2.ts SDK so if we pass signer from + // ethers v6 it won't break the Acre SDK initialization. + await TbtcSdk.initializeMainnet(signer) + : // @ts-expect-error We require the `signer` must include the ethers v5 + // signer's methods used in tBTC-v2.ts SDK so if we pass signer from + // ethers v6 it won't break the Acre SDK initialization. + await TbtcSdk.initializeSepolia(signer) + + return new Tbtc(tbtcApi, tbtcSdk, bitcoinDepositor) + } + + /** + * Function to initialize a tBTC deposit. It submits deposit data to the tBTC + * API and returns the deposit object. + * @param depositOwner Ethereum address of the deposit owner. + * @param bitcoinRecoveryAddress P2PKH or P2WPKH Bitcoin address that can be + * used for emergency recovery of the deposited funds. + * @param referral Deposit referral number. + */ + async initiateDeposit( + depositOwner: ChainIdentifier, + bitcoinRecoveryAddress: string, + referral: number, + ): Promise { + if (!depositOwner || !bitcoinRecoveryAddress) { + throw new Error("Ethereum or Bitcoin address is not available") + } + + const extraData = this.#bitcoinDepositor.encodeExtraData( + depositOwner, + referral, + ) + + const tbtcDeposit = await this.#tbtcSdk.deposits.initiateDepositWithProxy( + bitcoinRecoveryAddress, + this.#bitcoinDepositor, + extraData, + ) + + const receipt = tbtcDeposit.getReceipt() + + const revealData = { + address: depositOwner.identifierHex, + revealInfo: { + depositor: receipt.depositor.identifierHex, + blindingFactor: receipt.blindingFactor.toString(), + walletPublicKeyHash: receipt.walletPublicKeyHash.toString(), + refundPublicKeyHash: receipt.refundPublicKeyHash.toString(), + refundLocktime: receipt.refundLocktime.toString(), + extraData: receipt.extraData!.toString(), + }, + metadata: { + depositOwner: depositOwner.identifierHex, + referral, + }, + application: "acre", + } + + const revealSaved: boolean = await this.#tbtcApi.saveReveal(revealData) + if (!revealSaved) + throw new Error("Reveal not saved properly in the database") + + return new Deposit(this.#tbtcApi, tbtcDeposit, revealData) + } +} diff --git a/sdk/src/modules/tbtc/index.ts b/sdk/src/modules/tbtc/index.ts new file mode 100644 index 000000000..435e472f3 --- /dev/null +++ b/sdk/src/modules/tbtc/index.ts @@ -0,0 +1,5 @@ +import Tbtc from "./Tbtc" + +export * from "./Deposit" + +export default Tbtc From 3c08bd00be29996553b4a01a35091817d291128f Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 15:16:39 +0200 Subject: [PATCH 04/21] Use updated TBTC module in staking process The tBTC module wraps tBTC API and tBTC SDK and the staking process should interact with this module. In this commit we update references to match the new deposit initialization in tBTC with tBTC API instead of the custom DepositorProxy implementation. Wait for for funding transaction helper function was moved to the tBTC module already so here we remove it from the staking flow. --- sdk/src/lib/contracts/bitcoin-depositor.ts | 2 - sdk/src/lib/ethereum/bitcoin-depositor.ts | 33 ++---------- sdk/src/modules/staking/index.ts | 22 ++++---- .../modules/staking/stake-initialization.ts | 51 ++++--------------- 4 files changed, 24 insertions(+), 84 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index baa71a770..2b31f78d9 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -2,8 +2,6 @@ import { Hex } from "../utils" import { ChainIdentifier } from "./chain-identifier" import { DepositorProxy } from "./depositor-proxy" -export { DepositReceipt } from "@keep-network/tbtc-v2.ts" - export type DecodedExtraData = { depositOwner: ChainIdentifier referral: number diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 52f634c24..f7ebd36b7 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -15,10 +15,9 @@ import { ChainIdentifier, DecodedExtraData, BitcoinDepositor, - DepositReceipt, DepositFees, } from "../contracts" -import { BitcoinRawTxVectors } from "../bitcoin" + import { EthereumAddress } from "./address" import { EthersContractConfig, @@ -89,33 +88,9 @@ class EthereumBitcoinDepositor return EthereumAddress.from(vault) } - /** - * @see {BitcoinDepositor#revealDeposit} - */ - async revealDeposit( - depositTx: BitcoinRawTxVectors, - depositOutputIndex: number, - deposit: DepositReceipt, - ): Promise { - const { fundingTx, reveal, extraData } = packRevealDepositParameters( - depositTx, - depositOutputIndex, - deposit, - await this.getTbtcVaultChainIdentifier(), - ) - - if (!extraData) throw new Error("Invalid extra data") - - const { depositOwner, referral } = this.decodeExtraData(extraData) - - const tx = await this.instance.initializeDeposit( - fundingTx, - reveal, - `0x${depositOwner.identifierHex}`, - referral, - ) - - return Hex.from(tx.hash) + // eslint-disable-next-line class-methods-use-this + revealDeposit(): Promise { + throw new Error("Unsupported") } /** diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index 32e8b5363..e4e5c8c3b 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -1,8 +1,9 @@ -import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts" -import { AcreContracts, DepositorProxy, DepositFees } from "../../lib/contracts" +import { ChainIdentifier } from "@keep-network/tbtc-v2.ts" +import { AcreContracts, DepositFees } from "../../lib/contracts" import { ChainEIP712Signer } from "../../lib/eip712-signer" import { StakeInitialization } from "./stake-initialization" import { fromSatoshi, toSatoshi } from "../../lib/utils" +import Tbtc from "../tbtc" /** * Represents all total deposit fees grouped by network. @@ -28,14 +29,14 @@ class StakingModule { readonly #messageSigner: ChainEIP712Signer /** - * tBTC SDK. + * tBTC Module. */ - readonly #tbtc: TBTC + readonly #tbtc: Tbtc constructor( _contracts: AcreContracts, _messageSigner: ChainEIP712Signer, - _tbtc: TBTC, + _tbtc: Tbtc, ) { this.#contracts = _contracts this.#messageSigner = _messageSigner @@ -54,14 +55,13 @@ class StakingModule { */ async initializeStake( bitcoinRecoveryAddress: string, - staker: ChainIdentifier, + staker: ChainIdentifier, // TODO: We should resolve the address with OrangeKit SDK referral: number, - depositorProxy?: DepositorProxy, ) { - const deposit = await this.#tbtc.deposits.initiateDepositWithProxy( + const tbtcDeposit = await this.#tbtc.initiateDeposit( + staker, bitcoinRecoveryAddress, - depositorProxy ?? this.#contracts.bitcoinDepositor, - this.#contracts.bitcoinDepositor.encodeExtraData(staker, referral), + referral, ) return new StakeInitialization( @@ -69,7 +69,7 @@ class StakingModule { this.#messageSigner, bitcoinRecoveryAddress, staker, - deposit, + tbtcDeposit, ) } diff --git a/sdk/src/modules/staking/stake-initialization.ts b/sdk/src/modules/staking/stake-initialization.ts index 9ed9b31a5..9b988b67f 100644 --- a/sdk/src/modules/staking/stake-initialization.ts +++ b/sdk/src/modules/staking/stake-initialization.ts @@ -1,4 +1,7 @@ -import { Deposit as TbtcDeposit } from "@keep-network/tbtc-v2.ts" +import TbtcDeposit from "../tbtc/Deposit" + +import type { DepositReceipt } from "../tbtc" + import { ChainEIP712Signer, ChainSignedMessage, @@ -6,28 +9,8 @@ import { Message, Types, } from "../../lib/eip712-signer" -import { - AcreContracts, - DepositReceipt, - ChainIdentifier, -} from "../../lib/contracts" -import { Hex, backoffRetrier, BackoffRetrierParameters } from "../../lib/utils" +import { AcreContracts, ChainIdentifier } from "../../lib/contracts" -type StakeOptions = { - /** - * The number of retries to perform before bubbling the failure out. - * @see backoffRetrier for more details. - */ - retires: BackoffRetrierParameters[0] - /** - * Initial backoff step in milliseconds that will be increased exponentially - * for subsequent retry attempts. (default = 1000 ms) - * @see backoffRetrier for more details. - */ - backoffStepMs: BackoffRetrierParameters[1] -} - -// TODO: Rename to `DepositInitialization` to be consistent with the naming. /** * Represents an instance of the staking flow. Staking flow requires a few steps * which should be done to stake BTC. @@ -44,7 +27,7 @@ class StakeInitialization { readonly #messageSigner: ChainEIP712Signer /** - * Component representing an instance of the tBTC v2 deposit process. + * Component representing an instance of the tBTC deposit process. */ readonly #tbtcDeposit: TbtcDeposit @@ -158,30 +141,14 @@ class StakeInitialization { * @see StakeOptions for more details. * @returns Transaction hash of the stake initiation transaction. */ - async stake( - options: StakeOptions = { retires: 5, backoffStepMs: 5_000 }, - ): Promise { + async stake(options = { retires: 5, backoffStepMs: 5_000 }): Promise { if (!this.#signedMessage) { throw new Error("Sign message first") } - await this.#waitForBitcoinFundingTx(options) - - return this.#tbtcDeposit.initiateMinting() - } + await this.#tbtcDeposit.waitForFunding(options) - async #waitForBitcoinFundingTx({ - retires, - backoffStepMs, - }: StakeOptions): Promise { - await backoffRetrier( - retires, - backoffStepMs, - )(async () => { - const utxos = await this.#tbtcDeposit.detectFunding() - - if (utxos.length === 0) throw new Error("Deposit not funded yet") - }) + return this.#tbtcDeposit.createDeposit() } } From a1095d7fb42f2049c7feb6bb0b3b466a1186b115 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 15:27:51 +0200 Subject: [PATCH 05/21] Update Acre SDK initialization for tBTC module The tBTC module initialization was updated to match the tBTC module wrapper for tBTC SDK. --- sdk/src/acre.ts | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/sdk/src/acre.ts b/sdk/src/acre.ts index f7458d78d..912364816 100644 --- a/sdk/src/acre.ts +++ b/sdk/src/acre.ts @@ -1,4 +1,3 @@ -import { TBTC } from "@keep-network/tbtc-v2.ts" import { AcreContracts } from "./lib/contracts" import { ChainEIP712Signer } from "./lib/eip712-signer" import { @@ -7,10 +6,13 @@ import { getEthereumContracts, } from "./lib/ethereum" import { StakingModule } from "./modules/staking" +import Tbtc from "./modules/tbtc" import { EthereumSignerCompatibleWithEthersV5 } from "./lib/utils" +const TBTC_API_ENDPOINT = process.env.TBTC_API_ENDPOINT ?? "" + class Acre { - readonly #tbtc: TBTC + readonly #tbtc: Tbtc readonly #messageSigner: ChainEIP712Signer @@ -21,7 +23,7 @@ class Acre { constructor( _contracts: AcreContracts, _messageSigner: ChainEIP712Signer, - _tbtc: TBTC, + _tbtc: Tbtc, ) { this.contracts = _contracts this.#tbtc = _tbtc @@ -37,30 +39,17 @@ class Acre { signer: EthereumSignerCompatibleWithEthersV5, network: EthereumNetwork, ): Promise { - const tbtc = await Acre.#getTBTCEthereumSDK(signer, network) const contracts = getEthereumContracts(signer, network) const messages = new EthereumEIP712Signer(signer) - return new Acre(contracts, messages, tbtc) - } + const tbtc = await Tbtc.initialize( + signer, + network, + TBTC_API_ENDPOINT, + contracts.bitcoinDepositor, + ) - static #getTBTCEthereumSDK( - signer: EthereumSignerCompatibleWithEthersV5, - network: EthereumNetwork, - ): Promise { - switch (network) { - case "sepolia": - // @ts-expect-error We require the `signer` must include the ether v5 - // signer's methods used in tBTC-v2.ts SDK so if we pass signer from - // ethers v6 it won't break the Acre SDK initialization. - return TBTC.initializeSepolia(signer) - case "mainnet": - default: - // @ts-expect-error We require the `signer` must include the ether v5 - // signer's methods used in tBTC-v2.ts SDK so if we pass signer from - // ethers v6 it won't break the Acre SDK initialization. - return TBTC.initializeMainnet(signer) - } + return new Acre(contracts, messages, tbtc) } } From acc5b349407f3a228a54cd3afda03e8fc53195f5 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:09:35 +0200 Subject: [PATCH 06/21] Rename TbtcDeposit to TbtcSdkDeposit The name better reflect that the Deposit object is from tbtc sdk library. --- sdk/src/modules/tbtc/Deposit.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts index f5322118b..81de4b999 100644 --- a/sdk/src/modules/tbtc/Deposit.ts +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -1,4 +1,4 @@ -import { Deposit as TbtcDeposit } from "@keep-network/tbtc-v2.ts" +import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" import type { DepositReceipt as TbtcSdkDepositReceipt } from "@keep-network/tbtc-v2.ts" import type { SaveRevealRequest as DepositRevealData } from "../../api/TbtcApi" @@ -15,17 +15,17 @@ export type DepositReceipt = TbtcSdkDepositReceipt export default class Deposit { readonly #tbtcApi: TbtcApi - readonly #tbtcDeposit: TbtcDeposit + readonly #tbtcSdkDeposit: TbtcSdkDeposit readonly #revealData: DepositRevealData constructor( tbtcApi: TbtcApi, - tbtcDeposit: TbtcDeposit, + tbtcSdkDeposit: TbtcSdkDeposit, revealData: DepositRevealData, ) { this.#tbtcApi = tbtcApi - this.#tbtcDeposit = tbtcDeposit + this.#tbtcSdkDeposit = tbtcSdkDeposit this.#revealData = revealData } @@ -36,7 +36,7 @@ export default class Deposit { * @returns The Bitcoin address corresponding to this deposit. */ async getBitcoinAddress(): Promise { - return this.#tbtcDeposit.getBitcoinAddress() + return this.#tbtcSdkDeposit.getBitcoinAddress() } /** @@ -44,7 +44,7 @@ export default class Deposit { * @returns The deposit receipt. */ getReceipt(): DepositReceipt { - return this.#tbtcDeposit.getReceipt() + return this.#tbtcSdkDeposit.getReceipt() } /** @@ -57,7 +57,7 @@ export default class Deposit { options.retires, options.backoffStepMs, )(async () => { - const utxos = await this.#tbtcDeposit.detectFunding() + const utxos = await this.#tbtcSdkDeposit.detectFunding() if (utxos.length === 0) throw new Error("Deposit not funded yet") }) From a7d3213581782163eeab89ec0614fb55626b9484 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:10:39 +0200 Subject: [PATCH 07/21] Fix typo in retries property name --- sdk/src/lib/utils/backoff.ts | 2 +- sdk/src/modules/staking/stake-initialization.ts | 2 +- sdk/src/modules/tbtc/Deposit.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/lib/utils/backoff.ts b/sdk/src/lib/utils/backoff.ts index 3720d8476..5643453f5 100644 --- a/sdk/src/lib/utils/backoff.ts +++ b/sdk/src/lib/utils/backoff.ts @@ -7,7 +7,7 @@ type RetryOptions = { * The number of retries to perform before bubbling the failure out. * @see backoffRetrier for more details. */ - retires: BackoffRetrierParameters[0] + retries: BackoffRetrierParameters[0] /** * Initial backoff step in milliseconds that will be increased exponentially * for subsequent retry attempts. (default = 1000 ms) diff --git a/sdk/src/modules/staking/stake-initialization.ts b/sdk/src/modules/staking/stake-initialization.ts index 9b988b67f..12d3406e1 100644 --- a/sdk/src/modules/staking/stake-initialization.ts +++ b/sdk/src/modules/staking/stake-initialization.ts @@ -141,7 +141,7 @@ class StakeInitialization { * @see StakeOptions for more details. * @returns Transaction hash of the stake initiation transaction. */ - async stake(options = { retires: 5, backoffStepMs: 5_000 }): Promise { + async stake(options = { retries: 5, backoffStepMs: 5_000 }): Promise { if (!this.#signedMessage) { throw new Error("Sign message first") } diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts index 81de4b999..dd3011eed 100644 --- a/sdk/src/modules/tbtc/Deposit.ts +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -54,7 +54,7 @@ export default class Deposit { */ async waitForFunding(options: RetryOptions): Promise { await backoffRetrier( - options.retires, + options.retries, options.backoffStepMs, )(async () => { const utxos = await this.#tbtcSdkDeposit.detectFunding() From ffb0d7c04a819b27ea28f906155fbe545a40ee8c Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:13:08 +0200 Subject: [PATCH 08/21] Remove revealDeposit tests The revealDeposit is no longer implemented by the bitcoin depositor contract implementation as reveals are handled by the tbtc API bot. --- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 117 ------------------- 1 file changed, 117 deletions(-) diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 882ea534a..eac5c3446 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -2,7 +2,6 @@ import ethers, { Contract, ZeroAddress, getAddress } from "ethers" import { EthereumBitcoinDepositor, EthereumAddress, - Hex, EthereumSigner, DepositFees, } from "../../../src" @@ -85,122 +84,6 @@ describe("BitcoinDepositor", () => { }) }) - describe("revealDeposit", () => { - const depositOwner = EthereumAddress.from( - "0x000055d85E80A49B5930C4a77975d44f012D86C1", - ) - const bitcoinFundingTransaction = { - version: Hex.from("00000000"), - inputs: Hex.from("11111111"), - outputs: Hex.from("22222222"), - locktime: Hex.from("33333333"), - } - const depositReveal = { - fundingOutputIndex: 2, - walletPublicKeyHash: Hex.from("8db50eb52063ea9d98b3eac91489a90f738986f6"), - refundPublicKeyHash: Hex.from("28e081f285138ccbe389c1eb8985716230129f89"), - blindingFactor: Hex.from("f9f0c90d00039523"), - refundLocktime: Hex.from("60bcea61"), - depositor: depositOwner, - } - describe("when extra data is defined", () => { - const extraData = { - depositOwner, - referral: 6851, - hex: Hex.from( - "0x000055d85e80a49b5930c4a77975d44f012d86c11ac300000000000000000000", - ), - } - - const depositWithExtraData = { - ...depositReveal, - extraData: extraData.hex, - } - - const { referral } = extraData - - const mockedTx = Hex.from( - "0483fe6a05f245bccc7b10085f3c4d282d87ca42f27437d077acfd75e91183a0", - ) - let result: Hex - - beforeAll(async () => { - mockedContractInstance.initializeDeposit.mockReturnValue({ - hash: mockedTx.toPrefixedString(), - }) - - const { fundingOutputIndex, ...restDepositData } = depositWithExtraData - - result = await depositor.revealDeposit( - bitcoinFundingTransaction, - fundingOutputIndex, - restDepositData, - ) - }) - - it("should get the vault address", () => { - expect(mockedContractInstance.tbtcVault).toHaveBeenCalled() - }) - - it("should decode extra data", () => { - expect(spyOnEthersDataSlice).toHaveBeenNthCalledWith( - 1, - extraData.hex.toPrefixedString(), - 0, - 20, - ) - expect(spyOnEthersDataSlice).toHaveBeenNthCalledWith( - 2, - extraData.hex.toPrefixedString(), - 20, - 22, - ) - }) - - it("should initialize deposit", () => { - const btcTxInfo = { - version: bitcoinFundingTransaction.version.toPrefixedString(), - inputVector: bitcoinFundingTransaction.inputs.toPrefixedString(), - outputVector: bitcoinFundingTransaction.outputs.toPrefixedString(), - locktime: bitcoinFundingTransaction.locktime.toPrefixedString(), - } - - const revealInfo = { - fundingOutputIndex: depositReveal.fundingOutputIndex, - blindingFactor: depositReveal.blindingFactor.toPrefixedString(), - walletPubKeyHash: - depositReveal.walletPublicKeyHash.toPrefixedString(), - refundPubKeyHash: - depositReveal.refundPublicKeyHash.toPrefixedString(), - refundLocktime: depositReveal.refundLocktime.toPrefixedString(), - vault: `0x${vaultAddress.identifierHex}`, - } - - expect(mockedContractInstance.initializeDeposit).toHaveBeenCalledWith( - btcTxInfo, - revealInfo, - `0x${depositOwner.identifierHex}`, - referral, - ) - expect(result.toPrefixedString()).toBe(mockedTx.toPrefixedString()) - }) - }) - - describe("when extra data not defined", () => { - it("should throw an error", async () => { - const { fundingOutputIndex, ...restDepositData } = depositReveal - - await expect( - depositor.revealDeposit( - bitcoinFundingTransaction, - fundingOutputIndex, - restDepositData, - ), - ).rejects.toThrow("Invalid extra data") - }) - }) - }) - describe("encodeExtraData", () => { const spyOnSolidityPacked = jest.spyOn(ethers, "solidityPacked") From b0228d80ee9f30d7ed53a977fb681fab1ffaef94 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:35:09 +0200 Subject: [PATCH 09/21] Remove unused parameter from docs --- sdk/src/modules/staking/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index e4e5c8c3b..3fa3e4375 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -50,7 +50,6 @@ class StakingModule { * funds. * @param staker The address to which the stBTC shares will be minted. * @param referral Data used for referral program. - * @param depositorProxy Depositor proxy used to initiate the deposit. * @returns Object represents the staking process. */ async initializeStake( From 0e05c20a859054343c08e94296332291302a5f74 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:45:09 +0200 Subject: [PATCH 10/21] Update unit tests for tbtc module Here we update unit tests to reflect recent changes to the tbtc module. --- sdk/test/data/deposit.ts | 62 +++++++++ sdk/test/modules/staking.test.ts | 156 ++++------------------ sdk/test/modules/tbtc/Deposit.test.ts | 106 +++++++++++++++ sdk/test/modules/tbtc/Tbtc.test.ts | 178 ++++++++++++++++++++++++++ sdk/test/utils/mock-tbtc-sdk.ts | 33 +++++ sdk/test/utils/mock-tbtc.ts | 38 ++---- 6 files changed, 414 insertions(+), 159 deletions(-) create mode 100644 sdk/test/data/deposit.ts create mode 100644 sdk/test/modules/tbtc/Deposit.test.ts create mode 100644 sdk/test/modules/tbtc/Tbtc.test.ts create mode 100644 sdk/test/utils/mock-tbtc-sdk.ts diff --git a/sdk/test/data/deposit.ts b/sdk/test/data/deposit.ts new file mode 100644 index 000000000..42905923b --- /dev/null +++ b/sdk/test/data/deposit.ts @@ -0,0 +1,62 @@ +import { BitcoinTxHash, EthereumAddress, Hex } from "@keep-network/tbtc-v2.ts" +import { BigNumber } from "@ethersproject/bignumber" +import type { ChainIdentifier } from "../../src/lib/contracts" + +import type { DepositReceipt } from "../../src/modules/tbtc" +import type { SaveRevealRequest } from "../../src/api/TbtcApi" + +const depositTestData: { + depositOwner: ChainIdentifier + bitcoinRecoveryAddress: string + referral: number + extraData: Hex +} = { + depositOwner: EthereumAddress.from( + "0xa9B38eA6435c8941d6eDa6a46b68E3e211719699", + ), + bitcoinRecoveryAddress: "mjc2zGWypwpNyDi4ZxGbBNnUA84bfgiwYc", + referral: 23505, + extraData: Hex.from( + "0xa9b38ea6435c8941d6eda6a46b68e3e2117196995bd100000000000000000000", + ), +} + +const receiptTestData: DepositReceipt = { + depositor: EthereumAddress.from("0x2cd680318747b720d67bf4246eb7403b476adb34"), + blindingFactor: Hex.from("0xf9f0c90d00039523"), + walletPublicKeyHash: Hex.from("0x8db50eb52063ea9d98b3eac91489a90f738986f6"), + refundPublicKeyHash: Hex.from("0x28e081f285138ccbe389c1eb8985716230129f89"), + refundLocktime: Hex.from("0x60bcea61"), + extraData: depositTestData.extraData, +} + +const revealTestData: SaveRevealRequest = { + address: depositTestData.depositOwner.identifierHex, + revealInfo: { + depositor: receiptTestData.depositor.identifierHex, + blindingFactor: receiptTestData.blindingFactor.toString(), + walletPublicKeyHash: receiptTestData.walletPublicKeyHash.toString(), + refundPublicKeyHash: receiptTestData.refundPublicKeyHash.toString(), + refundLocktime: receiptTestData.refundLocktime.toString(), + extraData: depositTestData.extraData.toString(), + }, + metadata: { + depositOwner: depositTestData.depositOwner.identifierHex, + referral: depositTestData.referral, + }, + application: "acre", +} + +const fundingUtxo: { + transactionHash: BitcoinTxHash + outputIndex: number + value: BigNumber +} = { + transactionHash: BitcoinTxHash.from( + "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883", + ), + outputIndex: 1, + value: BigNumber.from(3933200), +} + +export { depositTestData, receiptTestData, revealTestData, fundingUtxo } diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index 81909295e..f269b2d31 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -5,8 +5,6 @@ import { StakingModule, Hex, StakeInitialization, - DepositorProxy, - DepositReceipt, EthereumAddress, DepositFees, DepositFee, @@ -14,7 +12,8 @@ import { import * as satoshiConverter from "../../src/lib/utils/satoshi-converter" import { MockAcreContracts } from "../utils/mock-acre-contracts" import { MockMessageSigner } from "../utils/mock-message-signer" -import { MockTBTC } from "../utils/mock-tbtc" +import { MockTbtc } from "../utils/mock-tbtc" +import { DepositReceipt } from "../../src/modules/tbtc" const stakingModuleData: { initializeStake: { @@ -73,6 +72,7 @@ const stakingModuleData: { const stakingInitializationData: { depositReceipt: Omit mockedInitializeTxHash: Hex + mockedDepositId: string fundingUtxo: { transactionHash: BitcoinTxHash outputIndex: number @@ -87,6 +87,7 @@ const stakingInitializationData: { extraData: stakingModuleData.initializeStake.extraData, }, mockedInitializeTxHash: Hex.from("999999"), + mockedDepositId: "deposit-id-1234", fundingUtxo: { transactionHash: BitcoinTxHash.from( "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", @@ -99,7 +100,7 @@ const stakingInitializationData: { describe("Staking", () => { const contracts: AcreContracts = new MockAcreContracts() const messageSigner = new MockMessageSigner() - const tbtc = new MockTBTC() + const tbtc = new MockTbtc() const staking: StakingModule = new StakingModule( contracts, @@ -115,11 +116,14 @@ describe("Staking", () => { referral, extraData, } = stakingModuleData.initializeStake + + const { mockedDepositId } = stakingInitializationData + const mockedDeposit = { getBitcoinAddress: jest.fn().mockResolvedValue(mockedDepositBTCAddress), - detectFunding: jest.fn(), + waitForFunding: jest.fn(), getReceipt: jest.fn().mockReturnValue({ extraData }), - initiateMinting: jest.fn(), + createDeposit: jest.fn().mockReturnValue(mockedDepositId), } describe("with default depositor proxy implementation", () => { @@ -136,9 +140,7 @@ describe("Staking", () => { .fn() .mockReturnValue(extraData) - tbtc.deposits.initiateDepositWithProxy = jest - .fn() - .mockReturnValue(mockedDeposit) + tbtc.initiateDeposit = jest.fn().mockReturnValue(mockedDeposit) messageSigner.sign = jest.fn().mockResolvedValue(mockedSignedMessage) @@ -149,15 +151,11 @@ describe("Staking", () => { ) }) - it("should encode extra data", () => { - expect(contracts.bitcoinDepositor.encodeExtraData(staker, referral)) - }) - it("should initiate tBTC deposit", () => { - expect(tbtc.deposits.initiateDepositWithProxy).toHaveBeenCalledWith( + expect(tbtc.initiateDeposit).toHaveBeenCalledWith( + staker, bitcoinRecoveryAddress, - contracts.bitcoinDepositor, - extraData, + referral, ) }) @@ -176,7 +174,7 @@ describe("Staking", () => { mockedDeposit.getReceipt.mockReturnValue(depositReceipt) }) - describe("getDepositAddress", () => { + describe("getBitcoinAddress", () => { it("should return bitcoin deposit address", async () => { expect(await result.getBitcoinAddress()).toBe( mockedDepositBTCAddress, @@ -261,136 +259,30 @@ describe("Staking", () => { }) describe("when message has already been signed", () => { - let tx: Hex - const { mockedInitializeTxHash: mockedTxHash, fundingUtxo } = - stakingInitializationData + let depositId: string beforeAll(async () => { - mockedDeposit.initiateMinting.mockResolvedValue(mockedTxHash) - mockedDeposit.detectFunding.mockResolvedValue([fundingUtxo]) + mockedDeposit.waitForFunding.mockResolvedValue(undefined) await result.signMessage() - tx = await result.stake() + depositId = await result.stake() }) - it("should stake tokens via tbtc depositor proxy", () => { - expect(mockedDeposit.initiateMinting).toHaveBeenCalled() + it("should wait for funding", () => { + expect(mockedDeposit.waitForFunding).toHaveBeenCalled() }) - it("should return transaction hash", () => { - expect(tx).toBe(mockedTxHash) - }) - }) - - describe("when waiting for bitcoin deposit tx", () => { - const { mockedInitializeTxHash: mockedTxHash } = - stakingInitializationData - - describe("when can't find transaction after max number of retries", () => { - beforeEach(async () => { - jest.useFakeTimers() - - mockedDeposit.initiateMinting.mockResolvedValue(mockedTxHash) - mockedDeposit.detectFunding.mockClear() - mockedDeposit.detectFunding.mockResolvedValue([]) - - await result.signMessage() - }) - - it("should throw an error", async () => { - // eslint-disable-next-line no-void - void expect(result.stake()).rejects.toThrow( - "Deposit not funded yet", - ) - - await jest.runAllTimersAsync() - - expect(mockedDeposit.detectFunding).toHaveBeenCalledTimes(6) - }) + it("should create the deposit", () => { + expect(mockedDeposit.createDeposit).toHaveBeenCalled() }) - describe("when the funding tx is available", () => { - const { fundingUtxo } = stakingInitializationData - let txPromise: Promise - - beforeAll(async () => { - jest.useFakeTimers() - - mockedDeposit.initiateMinting.mockResolvedValue(mockedTxHash) - - mockedDeposit.detectFunding.mockClear() - mockedDeposit.detectFunding - // First attempt. Deposit not funded yet. - .mockResolvedValueOnce([]) - // Second attempt. Deposit funded. - .mockResolvedValueOnce([fundingUtxo]) - - await result.signMessage() - - txPromise = result.stake() - - await jest.runAllTimersAsync() - }) - - it("should wait for deposit transaction", () => { - expect(mockedDeposit.detectFunding).toHaveBeenCalledTimes(2) - }) - - it("should stake tokens via tbtc depositor proxy", () => { - expect(mockedDeposit.initiateMinting).toHaveBeenCalled() - }) - - it("should return transaction hash", async () => { - const txHash = await txPromise - - expect(txHash).toBe(mockedTxHash) - }) + it("should return deposit id", () => { + expect(depositId).toBe(mockedDepositId) }) }) }) }) }) - - describe("with custom depositor proxy", () => { - const customDepositorProxy: DepositorProxy = { - getChainIdentifier: jest.fn(), - revealDeposit: jest.fn(), - } as unknown as DepositorProxy - - let result: StakeInitialization - - beforeEach(async () => { - contracts.bitcoinDepositor.encodeExtraData = jest - .fn() - .mockReturnValue(extraData) - - tbtc.deposits.initiateDepositWithProxy = jest - .fn() - .mockReturnValue(mockedDeposit) - - result = await staking.initializeStake( - bitcoinRecoveryAddress, - staker, - referral, - customDepositorProxy, - ) - }) - - it("should initiate tBTC deposit", () => { - expect(tbtc.deposits.initiateDepositWithProxy).toHaveBeenCalledWith( - bitcoinRecoveryAddress, - customDepositorProxy, - extraData, - ) - }) - - it("should return stake initialization object", () => { - expect(result).toBeInstanceOf(StakeInitialization) - expect(result.getBitcoinAddress).toBeDefined() - expect(result.stake).toBeDefined() - expect(result.signMessage).toBeDefined() - }) - }) }) describe("sharesBalance", () => { diff --git a/sdk/test/modules/tbtc/Deposit.test.ts b/sdk/test/modules/tbtc/Deposit.test.ts new file mode 100644 index 000000000..5c5558724 --- /dev/null +++ b/sdk/test/modules/tbtc/Deposit.test.ts @@ -0,0 +1,106 @@ +import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" +import TbtcApi, { DepositStatus } from "../../../src/api/TbtcApi" +import Deposit from "../../../src/modules/tbtc/Deposit" + +import { fundingUtxo, revealTestData } from "../../data/deposit" + +describe("Deposit", () => { + const tbtcApi: TbtcApi = new TbtcApi("https://api.acre.fi/v1/deposit/") + const tbtcSdkDeposit: TbtcSdkDeposit = jest.fn() as unknown as TbtcSdkDeposit + + let deposit: Deposit + + beforeAll(() => { + tbtcSdkDeposit.detectFunding = jest.fn() + + deposit = new Deposit(tbtcApi, tbtcSdkDeposit, revealTestData) + }) + + describe("waitForFunding", () => { + describe("when waiting for bitcoin deposit tx", () => { + describe("when can't find transaction after max number of retries", () => { + beforeAll(() => { + jest.useFakeTimers() + + jest + .spyOn(tbtcSdkDeposit, "detectFunding") + .mockClear() + .mockResolvedValue([]) + }) + + it("should throw an error", async () => { + // eslint-disable-next-line no-void + void expect( + deposit.waitForFunding({ + retries: 5, + backoffStepMs: 1234, + }), + ).rejects.toThrow("Deposit not funded yet") + + await jest.runAllTimersAsync() + + expect(tbtcSdkDeposit.detectFunding).toHaveBeenCalledTimes(6) + }) + }) + + describe("when the funding tx is available", () => { + let txPromise: Promise + + beforeAll(async () => { + jest.useFakeTimers() + + jest + .spyOn(tbtcSdkDeposit, "detectFunding") + .mockClear() + // First attempt. Deposit not funded yet. + .mockResolvedValueOnce([]) + // Second attempt. Deposit not funded yet. + .mockResolvedValueOnce([]) + // Third attempt. Deposit funded. + .mockResolvedValueOnce([fundingUtxo]) + + txPromise = deposit.waitForFunding({ + retries: 95, + backoffStepMs: 1234, + }) + + await jest.runAllTimersAsync() + }) + + it("should wait for deposit transaction", () => { + expect(tbtcSdkDeposit.detectFunding).toHaveBeenCalledTimes(3) + }) + + it("should resolve promise", async () => { + await txPromise + }) + }) + }) + }) + + describe("createDeposit", () => { + const mockedDepositId = "some-deposit-id" + + beforeAll(() => { + jest.spyOn(tbtcApi, "createDeposit").mockResolvedValue({ + depositId: mockedDepositId, + depositStatus: DepositStatus.Queued, + fundingOutpoint: fundingUtxo, + }) + }) + + afterAll(() => { + jest.spyOn(tbtcApi, "createDeposit").mockClear() + }) + + it("should create a deposit in tBTC API", async () => { + await deposit.createDeposit() + + expect(tbtcApi.createDeposit).toHaveBeenCalledWith({ + depositReceipt: revealTestData.revealInfo, + depositOwner: revealTestData.metadata.depositOwner, + referral: revealTestData.metadata.referral, + }) + }) + }) +}) diff --git a/sdk/test/modules/tbtc/Tbtc.test.ts b/sdk/test/modules/tbtc/Tbtc.test.ts new file mode 100644 index 000000000..39b3aa673 --- /dev/null +++ b/sdk/test/modules/tbtc/Tbtc.test.ts @@ -0,0 +1,178 @@ +import { + EthereumNetwork, + TBTC as TbtcSdk, + Deposit as TbtcSdkDeposit, +} from "@keep-network/tbtc-v2.ts" + +import { EthereumSignerCompatibleWithEthersV5 } from "../../../src" +import Deposit from "../../../src/modules/tbtc/Deposit" +import TbtcApi from "../../../src/api/TbtcApi" + +import Tbtc from "../../../src/modules/tbtc" +import { + depositTestData, + receiptTestData, + revealTestData, +} from "../../data/deposit" +import { MockAcreContracts } from "../../utils/mock-acre-contracts" + +import { MockTbtcSdk } from "../../utils/mock-tbtc-sdk" + +jest.mock("@keep-network/tbtc-v2.ts", (): object => ({ + TbtcSdk: jest.fn(), + ...jest.requireActual("@keep-network/tbtc-v2.ts"), +})) + +class MockEthereumSignerCompatibleWithEthersV5 extends EthereumSignerCompatibleWithEthersV5 { + getAddress = jest.fn() + + connect = jest.fn() + + signTransaction = jest.fn() + + signMessage = jest.fn() + + signTypedData = jest.fn() +} + +describe("Tbtc", () => { + const tbtcApiUrl = "https://api.acre.fi/v1/deposit/" + + const tbtcSdk: TbtcSdk = new MockTbtcSdk() + + const { bitcoinDepositor } = new MockAcreContracts() + + describe("initialize", () => { + const mockedSigner: EthereumSignerCompatibleWithEthersV5 = + new MockEthereumSignerCompatibleWithEthersV5() + + describe("when network is mainnet", () => { + const network: EthereumNetwork = "mainnet" + + let result: Tbtc + + beforeAll(async () => { + jest.spyOn(TbtcSdk, "initializeMainnet").mockResolvedValueOnce(tbtcSdk) + + result = await Tbtc.initialize( + mockedSigner, + network, + tbtcApiUrl, + bitcoinDepositor, + ) + }) + + it("should initialize TbtcSdk for mainnet", () => { + expect(TbtcSdk.initializeMainnet).toHaveBeenCalledWith(mockedSigner) + }) + + it("should return initialized Tbtc module", () => { + expect(result).toBeInstanceOf(Tbtc) + }) + }) + + describe("when network is sepolia", () => { + const network: EthereumNetwork = "sepolia" + + let result: Tbtc + + beforeAll(async () => { + jest.spyOn(TbtcSdk, "initializeSepolia").mockResolvedValueOnce(tbtcSdk) + + result = await Tbtc.initialize( + mockedSigner, + network, + tbtcApiUrl, + bitcoinDepositor, + ) + }) + + it("should initialize TbtcSdk for sepolia", () => { + expect(TbtcSdk.initializeSepolia).toHaveBeenCalledWith(mockedSigner) + }) + + it("should return initialized Tbtc module", () => { + expect(result).toBeInstanceOf(Tbtc) + }) + }) + }) + + describe("initiateDeposit", () => { + const tbtcApi: TbtcApi = new TbtcApi(tbtcApiUrl) + + let tbtc: Tbtc + + beforeAll(() => { + tbtc = new Tbtc(tbtcApi, tbtcSdk, bitcoinDepositor) + }) + + describe("when Bitcoin address is empty", () => { + it("should throw an error", async () => { + const emptyBitcoinRecoveryAddress = "" + + await expect( + tbtc.initiateDeposit( + depositTestData.depositOwner, + emptyBitcoinRecoveryAddress, + depositTestData.referral, + ), + ).rejects.toThrow("Ethereum or Bitcoin address is not available") + }) + }) + + describe("when Bitcoin address is provided", () => { + beforeAll(() => { + bitcoinDepositor.encodeExtraData = jest + .fn() + .mockReturnValue(depositTestData.extraData) + + const tbtcSdkDeposit: TbtcSdkDeposit = + jest.fn() as unknown as TbtcSdkDeposit + + tbtcSdkDeposit.getReceipt = jest.fn().mockReturnValue(receiptTestData) + + tbtcSdk.deposits.initiateDepositWithProxy = jest + .fn() + .mockReturnValue(tbtcSdkDeposit) + }) + + describe("when saveReveal succeeded", () => { + let result: Deposit + + beforeAll(async () => { + jest.spyOn(tbtcApi, "saveReveal").mockResolvedValueOnce(true) + + result = await tbtc.initiateDeposit( + depositTestData.depositOwner, + depositTestData.bitcoinRecoveryAddress, + depositTestData.referral, + ) + }) + + it("should call saveReveal", () => { + expect(tbtcApi.saveReveal).toHaveBeenCalledWith(revealTestData) + }) + + it("should initiate a deposit", () => { + expect(result).toBeInstanceOf(Deposit) + }) + }) + + describe("when saveReveal failed", () => { + beforeAll(() => { + jest.spyOn(tbtcApi, "saveReveal").mockResolvedValueOnce(false) + }) + + it("should throw an error", async () => { + await expect( + tbtc.initiateDeposit( + depositTestData.depositOwner, + depositTestData.bitcoinRecoveryAddress, + depositTestData.referral, + ), + ).rejects.toThrow("Reveal not saved properly in the database") + }) + }) + }) + }) +}) diff --git a/sdk/test/utils/mock-tbtc-sdk.ts b/sdk/test/utils/mock-tbtc-sdk.ts new file mode 100644 index 000000000..714a25782 --- /dev/null +++ b/sdk/test/utils/mock-tbtc-sdk.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +import { + BitcoinClient, + BitcoinNetwork, + DepositsService, + MaintenanceService, + RedemptionsService, + TBTC, + TBTCContracts, +} from "@keep-network/tbtc-v2.ts" + +// eslint-disable-next-line import/prefer-default-export +export class MockTbtcSdk implements TBTC { + deposits: DepositsService + + maintenance: MaintenanceService + + redemptions: RedemptionsService + + tbtcContracts: TBTCContracts + + bitcoinClient: BitcoinClient + + constructor() { + this.deposits = jest.fn() as unknown as DepositsService + this.maintenance = jest.fn() as unknown as MaintenanceService + this.redemptions = jest.fn() as unknown as RedemptionsService + this.tbtcContracts = jest.fn() as unknown as TBTCContracts + this.bitcoinClient = { + getNetwork: jest.fn().mockResolvedValue(BitcoinNetwork.Testnet), + } as unknown as BitcoinClient + } +} diff --git a/sdk/test/utils/mock-tbtc.ts b/sdk/test/utils/mock-tbtc.ts index 7e8192a41..5610f08cd 100644 --- a/sdk/test/utils/mock-tbtc.ts +++ b/sdk/test/utils/mock-tbtc.ts @@ -1,33 +1,17 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -import { - BitcoinClient, - BitcoinNetwork, - DepositsService, - MaintenanceService, - RedemptionsService, - TBTC, - TBTCContracts, -} from "@keep-network/tbtc-v2.ts" +import { TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" -// eslint-disable-next-line import/prefer-default-export -export class MockTBTC implements TBTC { - deposits: DepositsService - - maintenance: MaintenanceService - - redemptions: RedemptionsService +import Tbtc from "../../src/modules/tbtc" +import TbtcApi from "../../src/api/TbtcApi" - tbtcContracts: TBTCContracts - - bitcoinClient: BitcoinClient +import { BitcoinDepositor } from "../../src/lib/contracts" +// eslint-disable-next-line import/prefer-default-export +export class MockTbtc extends Tbtc { constructor() { - this.deposits = jest.fn() as unknown as DepositsService - this.maintenance = jest.fn() as unknown as MaintenanceService - this.redemptions = jest.fn() as unknown as RedemptionsService - this.tbtcContracts = jest.fn() as unknown as TBTCContracts - this.bitcoinClient = { - getNetwork: jest.fn().mockResolvedValue(BitcoinNetwork.Testnet), - } as unknown as BitcoinClient + const tbtcApi = jest.fn() as unknown as TbtcApi + const tbtcSdk = jest.fn() as unknown as TbtcSdk + const bitcoinDepositor = jest.fn() as unknown as BitcoinDepositor + + super(tbtcApi, tbtcSdk, bitcoinDepositor) } } From 7056272b323d774b6529774e978fdeba57ccbe26 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:46:12 +0200 Subject: [PATCH 11/21] Export DepositStatus enum --- sdk/src/api/TbtcApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/api/TbtcApi.ts index 786e9f0b2..a64c3b9fa 100644 --- a/sdk/src/api/TbtcApi.ts +++ b/sdk/src/api/TbtcApi.ts @@ -99,7 +99,7 @@ type CreateDepositRequest = { /** * Represents the status of a deposit. */ -enum DepositStatus { +export enum DepositStatus { Queued = "QUEUED", Initialized = "INITIALIZED", Finalized = "FINALIZED", From 1406659333980e006837a1138819363bcf2e12e9 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:46:35 +0200 Subject: [PATCH 12/21] Update wildcard for test files overrides Some of the test files are placed in nested directiories so we need to use `**/*` wildcard to cover them. --- sdk/.eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/.eslintrc b/sdk/.eslintrc index f330305d6..2bddd4296 100644 --- a/sdk/.eslintrc +++ b/sdk/.eslintrc @@ -3,7 +3,7 @@ "extends": ["@thesis-co"], "overrides": [ { - "files": ["test/*/*.test.ts"], + "files": ["test/**/*.test.ts"], "rules": { "@typescript-eslint/unbound-method": "off" } From 5cc7326fb9021fc3848b2f4bd8f79dbb369bfcb3 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:47:46 +0200 Subject: [PATCH 13/21] Use TS confif file for jest --- sdk/{jest.config.js => jest.config.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename sdk/{jest.config.js => jest.config.ts} (83%) diff --git a/sdk/jest.config.js b/sdk/jest.config.ts similarity index 83% rename from sdk/jest.config.js rename to sdk/jest.config.ts index ab00fc628..37ce37c74 100644 --- a/sdk/jest.config.js +++ b/sdk/jest.config.ts @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: "ts-jest", testPathIgnorePatterns: ["/dist/", "/node_modules/"], } From 8645220d1fbec31ac6afc3851a5c06b9d107c1a0 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:47:56 +0200 Subject: [PATCH 14/21] Add @ethersproject/bignumber dependency We use it in unit tests to define data for tBTC SDK, which uses BigNumber. --- sdk/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/package.json b/sdk/package.json index e29b1f487..519616401 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.23.7", + "@ethersproject/bignumber": "^5.7.0", "@thesis-co/eslint-config": "github:thesis/eslint-config#7b9bc8c", "@types/jest": "^29.5.11", "@types/node": "^20.9.4", @@ -26,8 +27,8 @@ "typescript": "^5.3.2" }, "dependencies": { - "@keep-network/tbtc-v2.ts": "2.4.0-dev.3", "@acre-btc/contracts": "workspace:*", + "@keep-network/tbtc-v2.ts": "2.4.0-dev.3", "ethers": "^6.10.0" } } From d58a851ca4e08a6c3b97b76b2862f5561ca5bdb8 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:52:58 +0200 Subject: [PATCH 15/21] Move api/ to lib/api/ We move the directory for consistency with existing components. --- sdk/src/{ => lib}/api/HttpApi.ts | 0 sdk/src/{ => lib}/api/TbtcApi.ts | 0 sdk/src/modules/tbtc/Deposit.ts | 4 ++-- sdk/src/modules/tbtc/Tbtc.ts | 2 +- sdk/test/data/deposit.ts | 2 +- sdk/test/modules/tbtc/Deposit.test.ts | 2 +- sdk/test/modules/tbtc/Tbtc.test.ts | 2 +- sdk/test/utils/mock-tbtc.ts | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename sdk/src/{ => lib}/api/HttpApi.ts (100%) rename sdk/src/{ => lib}/api/TbtcApi.ts (100%) diff --git a/sdk/src/api/HttpApi.ts b/sdk/src/lib/api/HttpApi.ts similarity index 100% rename from sdk/src/api/HttpApi.ts rename to sdk/src/lib/api/HttpApi.ts diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/lib/api/TbtcApi.ts similarity index 100% rename from sdk/src/api/TbtcApi.ts rename to sdk/src/lib/api/TbtcApi.ts diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts index dd3011eed..a719b25bb 100644 --- a/sdk/src/modules/tbtc/Deposit.ts +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -1,9 +1,9 @@ import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" import type { DepositReceipt as TbtcSdkDepositReceipt } from "@keep-network/tbtc-v2.ts" -import type { SaveRevealRequest as DepositRevealData } from "../../api/TbtcApi" +import type { SaveRevealRequest as DepositRevealData } from "../../lib/api/TbtcApi" -import TbtcApi from "../../api/TbtcApi" +import TbtcApi from "../../lib/api/TbtcApi" import { backoffRetrier, RetryOptions } from "../../lib/utils" diff --git a/sdk/src/modules/tbtc/Tbtc.ts b/sdk/src/modules/tbtc/Tbtc.ts index 007aa74e0..ddafdcc94 100644 --- a/sdk/src/modules/tbtc/Tbtc.ts +++ b/sdk/src/modules/tbtc/Tbtc.ts @@ -1,6 +1,6 @@ import { ChainIdentifier, TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" -import TbtcApi from "../../api/TbtcApi" +import TbtcApi from "../../lib/api/TbtcApi" import { BitcoinDepositor } from "../../lib/contracts" import { EthereumNetwork } from "../../lib/ethereum" diff --git a/sdk/test/data/deposit.ts b/sdk/test/data/deposit.ts index 42905923b..adf38d735 100644 --- a/sdk/test/data/deposit.ts +++ b/sdk/test/data/deposit.ts @@ -3,7 +3,7 @@ import { BigNumber } from "@ethersproject/bignumber" import type { ChainIdentifier } from "../../src/lib/contracts" import type { DepositReceipt } from "../../src/modules/tbtc" -import type { SaveRevealRequest } from "../../src/api/TbtcApi" +import type { SaveRevealRequest } from "../lib/api/TbtcApi" const depositTestData: { depositOwner: ChainIdentifier diff --git a/sdk/test/modules/tbtc/Deposit.test.ts b/sdk/test/modules/tbtc/Deposit.test.ts index 5c5558724..5e6e48937 100644 --- a/sdk/test/modules/tbtc/Deposit.test.ts +++ b/sdk/test/modules/tbtc/Deposit.test.ts @@ -1,5 +1,5 @@ import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" -import TbtcApi, { DepositStatus } from "../../../src/api/TbtcApi" +import TbtcApi, { DepositStatus } from "../../../src/lib/api/TbtcApi" import Deposit from "../../../src/modules/tbtc/Deposit" import { fundingUtxo, revealTestData } from "../../data/deposit" diff --git a/sdk/test/modules/tbtc/Tbtc.test.ts b/sdk/test/modules/tbtc/Tbtc.test.ts index 39b3aa673..043ddec7c 100644 --- a/sdk/test/modules/tbtc/Tbtc.test.ts +++ b/sdk/test/modules/tbtc/Tbtc.test.ts @@ -6,7 +6,7 @@ import { import { EthereumSignerCompatibleWithEthersV5 } from "../../../src" import Deposit from "../../../src/modules/tbtc/Deposit" -import TbtcApi from "../../../src/api/TbtcApi" +import TbtcApi from "../../../src/lib/api/TbtcApi" import Tbtc from "../../../src/modules/tbtc" import { diff --git a/sdk/test/utils/mock-tbtc.ts b/sdk/test/utils/mock-tbtc.ts index 5610f08cd..aee034ac0 100644 --- a/sdk/test/utils/mock-tbtc.ts +++ b/sdk/test/utils/mock-tbtc.ts @@ -1,7 +1,7 @@ import { TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" import Tbtc from "../../src/modules/tbtc" -import TbtcApi from "../../src/api/TbtcApi" +import TbtcApi from "../../src/lib/api/TbtcApi" import { BitcoinDepositor } from "../../src/lib/contracts" From f0a9e903a0d240acbffc69b3447c3f03220cf81b Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 08:11:31 +0200 Subject: [PATCH 16/21] Update pnpm-lock.yaml --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da6eba6e3..dca0a101e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,9 @@ importers: '@babel/preset-env': specifier: ^7.23.7 version: 7.23.7(@babel/core@7.23.3) + '@ethersproject/bignumber': + specifier: ^5.7.0 + version: 5.7.0 '@thesis-co/eslint-config': specifier: github:thesis/eslint-config#7b9bc8c version: github.com/thesis/eslint-config/7b9bc8c(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2) From 0bb9df8139bfbae1d7b11a40ec40070923384afc Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 12:37:05 +0200 Subject: [PATCH 17/21] Expect tBTC API URL to be provided on initialization To avoid hardcoding URL to the api in SDK source code we expect it to be provided from the dapp on initialization. --- sdk/src/acre.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/src/acre.ts b/sdk/src/acre.ts index 912364816..dce8d8226 100644 --- a/sdk/src/acre.ts +++ b/sdk/src/acre.ts @@ -9,8 +9,6 @@ import { StakingModule } from "./modules/staking" import Tbtc from "./modules/tbtc" import { EthereumSignerCompatibleWithEthersV5 } from "./lib/utils" -const TBTC_API_ENDPOINT = process.env.TBTC_API_ENDPOINT ?? "" - class Acre { readonly #tbtc: Tbtc @@ -38,6 +36,7 @@ class Acre { static async initializeEthereum( signer: EthereumSignerCompatibleWithEthersV5, network: EthereumNetwork, + tbtcApiUrl: string, ): Promise { const contracts = getEthereumContracts(signer, network) const messages = new EthereumEIP712Signer(signer) @@ -45,7 +44,7 @@ class Acre { const tbtc = await Tbtc.initialize( signer, network, - TBTC_API_ENDPOINT, + tbtcApiUrl, contracts.bitcoinDepositor, ) From 6d2f82aad1f4f2b195106252c697c4d78e4907c4 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 12:38:22 +0200 Subject: [PATCH 18/21] Export DepositReceipt type The types is required by the dApp to handle the receipt. --- sdk/src/modules/staking/index.ts | 2 ++ sdk/src/modules/staking/stake-initialization.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index 3fa3e4375..1892e6744 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -5,6 +5,8 @@ import { StakeInitialization } from "./stake-initialization" import { fromSatoshi, toSatoshi } from "../../lib/utils" import Tbtc from "../tbtc" +export { DepositReceipt } from "../tbtc" + /** * Represents all total deposit fees grouped by network. */ diff --git a/sdk/src/modules/staking/stake-initialization.ts b/sdk/src/modules/staking/stake-initialization.ts index 12d3406e1..722067179 100644 --- a/sdk/src/modules/staking/stake-initialization.ts +++ b/sdk/src/modules/staking/stake-initialization.ts @@ -1,6 +1,6 @@ import TbtcDeposit from "../tbtc/Deposit" -import type { DepositReceipt } from "../tbtc" +import type { DepositReceipt } from "." import { ChainEIP712Signer, From ac7685a3cb118136ef273dd649b417047d42be96 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 12:44:50 +0200 Subject: [PATCH 19/21] Pass TBTC_API_ENDPOINT to SDK initialization SDK expects tBTC API URL to be provided for initialization. We expect it ot be defined in the dApp as env variable. --- dapp/src/acre-react/contexts/AcreSdkContext.tsx | 3 +++ dapp/src/vite-env.d.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/dapp/src/acre-react/contexts/AcreSdkContext.tsx b/dapp/src/acre-react/contexts/AcreSdkContext.tsx index 6be8694a6..75dd77a60 100644 --- a/dapp/src/acre-react/contexts/AcreSdkContext.tsx +++ b/dapp/src/acre-react/contexts/AcreSdkContext.tsx @@ -2,6 +2,8 @@ import React, { useCallback, useMemo, useState } from "react" import { LedgerLiveEthereumSigner } from "#/web3" import { Acre, EthereumNetwork } from "@acre-btc/sdk" +const TBTC_API_ENDPOINT = import.meta.env.VITE_TBTC_API_ENDPOINT + type AcreSdkContextValue = { acre?: Acre init: (ethereumAddress: string, network: EthereumNetwork) => Promise @@ -25,6 +27,7 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) { const sdk = await Acre.initializeEthereum( await LedgerLiveEthereumSigner.fromAddress(ethereumAddress), network, + TBTC_API_ENDPOINT, ) setAcre(sdk) setIsInitialized(true) diff --git a/dapp/src/vite-env.d.ts b/dapp/src/vite-env.d.ts index f127eea1e..269569e7e 100644 --- a/dapp/src/vite-env.d.ts +++ b/dapp/src/vite-env.d.ts @@ -5,6 +5,7 @@ interface ImportMetaEnv { readonly VITE_ETH_HOSTNAME_HTTP: string readonly VITE_REFERRAL: number readonly VITE_DEFENDER_RELAYER_WEBHOOK_URL: string + readonly VITE_TBTC_API_ENDPOINT: string } interface ImportMeta { From 4a132b1ebb0f405335d7b23d8396fc416e296cbf Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 13:52:40 +0200 Subject: [PATCH 20/21] Remove custom depositor with defender relayer implementation We don't need to implement bitcoin depositor contract implementation using the defender relayer as we use tbtc api to relay the deposit creation requests. --- dapp/src/acre-react/hooks/useStakeFlow.ts | 4 - dapp/src/contexts/StakeFlowContext.tsx | 11 +-- dapp/src/vite-env.d.ts | 1 - dapp/src/web3/index.ts | 1 - dapp/src/web3/relayer-depositor-proxy.ts | 96 ----------------------- 5 files changed, 1 insertion(+), 112 deletions(-) delete mode 100644 dapp/src/web3/relayer-depositor-proxy.ts diff --git a/dapp/src/acre-react/hooks/useStakeFlow.ts b/dapp/src/acre-react/hooks/useStakeFlow.ts index b13c3b8a5..7b301ee4a 100644 --- a/dapp/src/acre-react/hooks/useStakeFlow.ts +++ b/dapp/src/acre-react/hooks/useStakeFlow.ts @@ -2,7 +2,6 @@ import { useCallback, useState } from "react" import { StakeInitialization, EthereumAddress, - DepositorProxy, DepositReceipt, } from "@acre-btc/sdk" import { useAcreContext } from "./useAcreContext" @@ -12,7 +11,6 @@ export type UseStakeFlowReturn = { bitcoinRecoveryAddress: string, ethereumAddress: string, referral: number, - depositor?: DepositorProxy, ) => Promise btcAddress?: string depositReceipt?: DepositReceipt @@ -36,7 +34,6 @@ export function useStakeFlow(): UseStakeFlowReturn { bitcoinRecoveryAddress: string, ethereumAddress: string, referral: number, - depositor?: DepositorProxy, ) => { if (!acre || !isInitialized) throw new Error("Acre SDK not defined") @@ -44,7 +41,6 @@ export function useStakeFlow(): UseStakeFlowReturn { bitcoinRecoveryAddress, EthereumAddress.from(ethereumAddress), referral, - depositor, ) const btcDepositAddress = await initializedStakeFlow.getBitcoinAddress() diff --git a/dapp/src/contexts/StakeFlowContext.tsx b/dapp/src/contexts/StakeFlowContext.tsx index d19cad509..adadd561c 100644 --- a/dapp/src/contexts/StakeFlowContext.tsx +++ b/dapp/src/contexts/StakeFlowContext.tsx @@ -5,8 +5,6 @@ import { useStakeFlow, } from "#/acre-react/hooks" import { REFERRAL } from "#/constants" -import { RelayerDepositorProxy } from "#/web3" -import { EthereumBitcoinDepositor } from "@acre-btc/sdk" type StakeFlowContextValue = Omit & { initStake: ( @@ -35,14 +33,7 @@ export function StakeFlowProvider({ children }: { children: React.ReactNode }) { async (bitcoinRecoveryAddress: string, ethereumAddress: string) => { if (!acre) throw new Error("Acre SDK not defined") - await acreInitStake( - bitcoinRecoveryAddress, - ethereumAddress, - REFERRAL, - RelayerDepositorProxy.fromEthereumBitcoinDepositor( - acre.contracts.bitcoinDepositor as EthereumBitcoinDepositor, - ), - ) + await acreInitStake(bitcoinRecoveryAddress, ethereumAddress, REFERRAL) }, [acreInitStake, acre], ) diff --git a/dapp/src/vite-env.d.ts b/dapp/src/vite-env.d.ts index 269569e7e..3f78f074c 100644 --- a/dapp/src/vite-env.d.ts +++ b/dapp/src/vite-env.d.ts @@ -4,7 +4,6 @@ interface ImportMetaEnv { readonly VITE_SENTRY_DSN: string readonly VITE_ETH_HOSTNAME_HTTP: string readonly VITE_REFERRAL: number - readonly VITE_DEFENDER_RELAYER_WEBHOOK_URL: string readonly VITE_TBTC_API_ENDPOINT: string } diff --git a/dapp/src/web3/index.ts b/dapp/src/web3/index.ts index 3d4e2a169..f38e7e840 100644 --- a/dapp/src/web3/index.ts +++ b/dapp/src/web3/index.ts @@ -1,2 +1 @@ export * from "./ledger-live-signer" -export * from "./relayer-depositor-proxy" diff --git a/dapp/src/web3/relayer-depositor-proxy.ts b/dapp/src/web3/relayer-depositor-proxy.ts deleted file mode 100644 index 7207df6f9..000000000 --- a/dapp/src/web3/relayer-depositor-proxy.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - BitcoinRawTxVectors, - DepositorProxy, - EthereumBitcoinDepositor, - ChainIdentifier, - DepositReceipt, - Hex, - packRevealDepositParameters, - BitcoinDepositor, -} from "@acre-btc/sdk" -import axios from "axios" - -const DEFENDER_WEBHOOK_URL = import.meta.env.VITE_DEFENDER_RELAYER_WEBHOOK_URL - -/** - * Implementation of @see DepositorProxy that redirects stake requests to the - * Open Zeppelin Defender Relayer which initializes stake on the user's behalf. - * Sends the HTTP POST request to the webhook at the provided URL with the data - * necessary to initialize stake. - */ -class RelayerDepositorProxy - implements DepositorProxy -{ - /** - * Chain-specific handle to @see BitcoinDepositor contract. - */ - #bitcoinDepositor: T - - /** - * Defines the Open Zeppelin Defender webhook URL. - */ - #defenderWebhookUrl: string - - /** - * Creates the instance of the relayer depositor proxy for Ethereum chain. - * @param bitcoinDepositor Ethereum handle to @see BitcoinDepositor contract. - * @returns Instance of @see RelayerDepositorProxy. - */ - static fromEthereumBitcoinDepositor( - bitcoinDepositor: EthereumBitcoinDepositor, - ): RelayerDepositorProxy { - return new RelayerDepositorProxy(bitcoinDepositor, DEFENDER_WEBHOOK_URL) - } - - private constructor(_bitcoinDepositor: T, _defenderWebhookUr: string) { - this.#bitcoinDepositor = _bitcoinDepositor - this.#defenderWebhookUrl = _defenderWebhookUr - } - - /** - * @see {DepositorProxy#getChainIdentifier} - */ - getChainIdentifier(): ChainIdentifier { - return this.#bitcoinDepositor.getChainIdentifier() - } - - /** - * @see {DepositorProxy#revealDeposit} - * @dev Sends HTTP POST request to Open Zeppelin Defender Relayer. - */ - async revealDeposit( - depositTx: BitcoinRawTxVectors, - depositOutputIndex: number, - deposit: DepositReceipt, - ): Promise { - const { fundingTx, reveal, extraData } = packRevealDepositParameters( - depositTx, - depositOutputIndex, - deposit, - await this.#bitcoinDepositor.getTbtcVaultChainIdentifier(), - ) - - if (!extraData) throw new Error("Invalid extra data") - - const { depositOwner, referral } = - this.#bitcoinDepositor.decodeExtraData(extraData) - - // TODO: Catch and handle errors + sentry. - const response = await axios.post<{ result: string }>( - this.#defenderWebhookUrl, - { - fundingTx, - reveal, - depositOwner: `0x${depositOwner.identifierHex}`, - referral, - }, - ) - - // Defender returns result as string so we need to parse it. - const { txHash } = JSON.parse(response.data.result) as { txHash: string } - - return Hex.from(txHash) - } -} - -export { RelayerDepositorProxy } From e1b991e5f84989999569abe70b5f4ca639a176c5 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 3 May 2024 12:53:54 +0200 Subject: [PATCH 21/21] Update documentation in HttpApi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: RafaƂ Czajkowski <57687279+r-czajkowski@users.noreply.github.com> --- sdk/src/lib/api/HttpApi.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk/src/lib/api/HttpApi.ts b/sdk/src/lib/api/HttpApi.ts index 351cd8f6e..d1759d6d2 100644 --- a/sdk/src/lib/api/HttpApi.ts +++ b/sdk/src/lib/api/HttpApi.ts @@ -9,10 +9,10 @@ export default abstract class HttpApi { } /** - * Factory function for running GET requests - * @param endpoint api endpoint - * @param requestInit additional data passed to request - * @returns api response + * Factory function for running GET requests. + * @param endpoint API endpoint. + * @param requestInit Additional data passed to request. + * @returns API response. */ protected async getRequest( endpoint: string, @@ -25,11 +25,11 @@ export default abstract class HttpApi { } /** - * Factory function for running POST requests - * @param endpoint api endpoint - * @param body data passed to POST request - * @param requestInit additional data passed to request - * @returns api response + * Factory function for running POST requests. + * @param endpoint API endpoint, + * @param body Data passed to POST request. + * @param requestInit Additional data passed to request. + * @returns API response. */ protected async postRequest( endpoint: string,