Skip to content

Commit

Permalink
Wrap ETH address handling iniside sdk (#445)
Browse files Browse the repository at this point in the history
Closes: #426 
Closes: #428 

This PR wraps the ETH address handling inside the SDK and refactores
some modules.

Here we remove the `Staking` module and split it into 2 separate modules
`Account` and `Protocol`. The `Account` module exposes features related
to a given Bitcoin account such as initializing deposits, fetching
balance etc. The `Protocol` module provides general information related
to the Acre protocol such as fees, minimum deposit/withdrawal amount,
tvl etc.
  • Loading branch information
nkuba authored Jun 11, 2024
2 parents b1607be + 18e1fed commit ab366a9
Show file tree
Hide file tree
Showing 16 changed files with 442 additions and 457 deletions.
2 changes: 1 addition & 1 deletion dapp/src/acre-react/hooks/useStakeFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function useStakeFlow(): UseStakeFlowReturn {
async (referral: number, bitcoinRecoveryAddress?: string) => {
if (!acre || !isInitialized) throw new Error("Acre SDK not defined")

const initializedStakeFlow = await acre.staking.initializeStake(
const initializedStakeFlow = await acre.account.initializeStake(
referral,
bitcoinRecoveryAddress,
)
Expand Down
8 changes: 2 additions & 6 deletions dapp/src/hooks/sdk/useFetchBTCBalance.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { useEffect } from "react"
import { EthereumAddress } from "@acre-btc/sdk"
import { useAcreContext } from "#/acre-react/hooks"
import { logPromiseFailure } from "#/utils"
import { setEstimatedBtcBalance, setSharesBalance } from "#/store/btc"
import { ZeroAddress } from "ethers"
import { useAppDispatch } from "../store/useAppDispatch"

export function useFetchBTCBalance() {
Expand All @@ -14,11 +12,9 @@ export function useFetchBTCBalance() {
const getBtcBalance = async () => {
if (!isInitialized || !acre) return

// TODO: We should pass the Bitcoin address here once we update the SDK.
const chainIdentifier = EthereumAddress.from(ZeroAddress)
const sharesBalance = await acre.staking.sharesBalance(chainIdentifier)
const sharesBalance = await acre.account.sharesBalance()
const estimatedBitcoinBalance =
await acre.staking.estimatedBitcoinBalance(chainIdentifier)
await acre.account.estimatedBitcoinBalance()

dispatch(setSharesBalance(sharesBalance))
dispatch(setEstimatedBtcBalance(estimatedBitcoinBalance))
Expand Down
2 changes: 1 addition & 1 deletion dapp/src/hooks/sdk/useFetchDeposits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function useFetchDeposits() {
return useCallback(async () => {
if (!acre) return

const result: Activity[] = (await acre.staking.getDeposits()).map(
const result: Activity[] = (await acre.account.getDeposits()).map(
(deposit) => ({
...deposit,
status:
Expand Down
2 changes: 1 addition & 1 deletion dapp/src/hooks/sdk/useFetchMinDepositAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function useFetchMinDepositAmount() {
if (!isInitialized || !acre) return

const fetchMinDepositAmount = async () => {
const minDepositAmount = await acre.staking.minDepositAmount()
const minDepositAmount = await acre.protocol.minimumDepositAmount()

dispatch(setMinDepositAmount(minDepositAmount))
}
Expand Down
2 changes: 1 addition & 1 deletion dapp/src/hooks/useTransactionFee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function useTransactionFee(amount?: bigint) {
} else {
const getEstimatedDepositFee = async () => {
if (!acre) return
const fee = await acre.staking.estimateDepositFee(amount)
const fee = await acre.protocol.estimateDepositFee(amount)

setDepositFee(fee)
}
Expand Down
54 changes: 37 additions & 17 deletions sdk/src/acre.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { OrangeKitSdk } from "@orangekit/sdk"
import { getDefaultProvider } from "ethers"
import { AcreContracts } from "./lib/contracts"
import { EthereumNetwork, getEthereumContracts } from "./lib/ethereum"
import { StakingModule } from "./modules/staking"
import {
EthereumAddress,
EthereumNetwork,
getEthereumContracts,
} from "./lib/ethereum"
import Account from "./modules/account"
import Tbtc from "./modules/tbtc"
import { VoidSigner } from "./lib/utils"
import { BitcoinProvider, BitcoinNetwork } from "./lib/bitcoin"
import { getChainIdByNetwork } from "./lib/ethereum/network"
import AcreSubgraphApi from "./lib/api/AcreSubgraphApi"
import Protocol from "./modules/protocol"

class Acre {
readonly #tbtc: Tbtc
Expand All @@ -16,31 +21,30 @@ class Acre {

readonly #bitcoinProvider: BitcoinProvider

readonly #acreSubgraph: AcreSubgraphApi

public readonly contracts: AcreContracts

public readonly staking: StakingModule
public readonly account: Account

readonly #acreSubgraph: AcreSubgraphApi
public readonly protocol: Protocol

private constructor(
contracts: AcreContracts,
bitcoinProvider: BitcoinProvider,
orangeKit: OrangeKitSdk,
tbtc: Tbtc,
acreSubgraphApi: AcreSubgraphApi,
account: Account,
protocol: Protocol,
) {
this.contracts = contracts
this.#tbtc = tbtc
this.#orangeKit = orangeKit
this.#acreSubgraph = acreSubgraphApi
this.#bitcoinProvider = bitcoinProvider
this.staking = new StakingModule(
this.contracts,
this.#bitcoinProvider,
this.#orangeKit,
this.#tbtc,
this.#acreSubgraph,
)
this.account = account
this.protocol = protocol
}

static async initialize(
Expand All @@ -66,13 +70,15 @@ class Acre {
ethereumRpcUrl,
)

// TODO: Should we store this address in context so that we do not to
// recalculate it when necessary?
const depositOwnerEvmAddress = await orangeKit.predictAddress(
await bitcoinProvider.getAddress(),
const accountBitcoinAddress = await bitcoinProvider.getAddress()
const accountEthereumAddress = EthereumAddress.from(
await orangeKit.predictAddress(accountBitcoinAddress),
)

const signer = new VoidSigner(depositOwnerEvmAddress, ethersProvider)
const signer = new VoidSigner(
`0x${accountEthereumAddress.identifierHex}`,
ethersProvider,
)

const contracts = getEthereumContracts(signer, ethereumNetwork)

Expand All @@ -87,7 +93,21 @@ class Acre {
"https://api.studio.thegraph.com/query/73600/acre/version/latest",
)

return new Acre(contracts, bitcoinProvider, orangeKit, tbtc, subgraph)
const account = new Account(contracts, tbtc, subgraph, {
bitcoinAddress: accountBitcoinAddress,
ethereumAddress: accountEthereumAddress,
})
const protocol = new Protocol(contracts)

return new Acre(
contracts,
bitcoinProvider,
orangeKit,
tbtc,
subgraph,
account,
protocol,
)
}
}

Expand Down
9 changes: 7 additions & 2 deletions sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
export * from "./lib/bitcoin"
export * from "./lib/contracts"
export * from "./lib/ethereum"
export * from "./lib/utils"
export { DepositStatus } from "./lib/api/TbtcApi"

export * from "./modules/staking"
export * from "./modules/account"
export { default as Account } from "./modules/account"

export { default as StakeInitialization } from "./modules/staking"

export { default as Protocol } from "./modules/protocol"
export * from "./modules/protocol"

export * from "./acre"
155 changes: 155 additions & 0 deletions sdk/src/modules/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { AcreContracts, ChainIdentifier } from "../lib/contracts"
import StakeInitialization from "./staking"
import { toSatoshi } from "../lib/utils"
import Tbtc from "./tbtc"
import AcreSubgraphApi from "../lib/api/AcreSubgraphApi"
import { DepositStatus } from "../lib/api/TbtcApi"

export { DepositReceipt } from "./tbtc"

/**
* Represents the deposit data.
*/
export type Deposit = {
/**
* Unique deposit identifier represented as
* `keccak256(bitcoinFundingTxHash | fundingOutputIndex)`.
*/
id: string
/**
* Bitcoin transaction hash (or transaction ID) in the same byte order as
* used by the Bitcoin block explorers.
*/
txHash: string
/**
* Amount of Bitcoin funding transaction.
*/
amount: bigint
/**
* Status of the deposit.
*/
status: DepositStatus
/**
* Timestamp when the deposit was initialized.
*/
timestamp: number
}

/**
* Module exposing features related to the account.
*/
export default class Account {
/**
* Acre contracts.
*/
readonly #contracts: AcreContracts

/**
* tBTC Module.
*/
readonly #tbtc: Tbtc

/**
* Acre subgraph api.
*/
readonly #acreSubgraphApi: AcreSubgraphApi

readonly #bitcoinAddress: string

readonly #ethereumAddress: ChainIdentifier

constructor(
contracts: AcreContracts,
tbtc: Tbtc,
acreSubgraphApi: AcreSubgraphApi,
account: { bitcoinAddress: string; ethereumAddress: ChainIdentifier },
) {
this.#contracts = contracts
this.#tbtc = tbtc
this.#acreSubgraphApi = acreSubgraphApi
this.#bitcoinAddress = account.bitcoinAddress
this.#ethereumAddress = account.ethereumAddress
}

/**
* Initializes the Acre deposit process.
* @param referral Data used for referral program.
* @param bitcoinRecoveryAddress `P2PKH` or `P2WPKH` Bitcoin address that can
* be used for emergency recovery of the deposited funds. If
* `undefined` the bitcoin address from bitcoin provider is used as
* bitcoin recovery address - note that an address returned by bitcoin
* provider must then be `P2WPKH` or `P2PKH`. This property is
* available to let the consumer use `P2SH-P2WPKH` as the deposit owner
* and another tBTC-supported type (`P2WPKH`, `P2PKH`) address as the
* tBTC Bridge recovery address.
* @returns Object represents the deposit process.
*/
async initializeStake(
referral: number,
bitcoinRecoveryAddress?: string,
): Promise<StakeInitialization> {
// tBTC-v2 SDK will handle Bitcoin address validation and throw an error if
// address is not supported.
const finalBitcoinRecoveryAddress =
bitcoinRecoveryAddress ?? this.#bitcoinAddress

const tbtcDeposit = await this.#tbtc.initiateDeposit(
this.#ethereumAddress,
finalBitcoinRecoveryAddress,
referral,
)

return new StakeInitialization(tbtcDeposit)
}

/**
* @returns Balance of the account's stBTC shares (in 1e18 precision).
*/
async sharesBalance() {
return this.#contracts.stBTC.balanceOf(this.#ethereumAddress)
}

/**
* @returns Balance of Bitcoin position in Acre estimated based on the
* account's stBTC shares (in 1e8 satoshi precision).
*/
async estimatedBitcoinBalance() {
return toSatoshi(
await this.#contracts.stBTC.assetsBalanceOf(this.#ethereumAddress),
)
}

/**
* @returns All deposits associated with the account. They include all
* deposits: queued, initialized and finalized.
*/
async getDeposits(): Promise<Deposit[]> {
const subgraphData = await this.#acreSubgraphApi.getDepositsByOwner(
this.#ethereumAddress,
)

const initializedOrFinalizedDepositsMap = new Map(
subgraphData.map((data) => [data.depositKey, data]),
)

const tbtcData = await this.#tbtc.getDepositsByOwner(this.#ethereumAddress)

return tbtcData.map((deposit) => {
const depositFromSubgraph = initializedOrFinalizedDepositsMap.get(
deposit.depositKey,
)

const amount = toSatoshi(
depositFromSubgraph?.initialAmount ?? deposit.initialAmount,
)

return {
id: deposit.depositKey,
txHash: deposit.txHash,
amount,
status: deposit.status,
timestamp: deposit.timestamp,
}
})
}
}
Loading

0 comments on commit ab366a9

Please sign in to comment.