Skip to content

Commit

Permalink
WIP: Integrate withdrawals with OrangeKit and tBTC SDK
Browse files Browse the repository at this point in the history
This branch expects two dependencies changes that are not yet merged:
- @keep-network/tbtc-v2.ts: keep-network/tbtc-v2#814
- @orangekit/sdk: thesis/orangekit#83
  • Loading branch information
nkuba committed Jun 7, 2024
1 parent b916ba9 commit d32cc71
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 24 deletions.
21 changes: 9 additions & 12 deletions sdk/src/acre.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { OrangeKitSdk } from "@orangekit/sdk"
import { getDefaultProvider } from "ethers"
import { AcreContracts } from "./lib/contracts"
import {
EthereumAddress,
EthereumNetwork,
getEthereumContracts,
} from "./lib/ethereum"
import { EthereumNetwork, getEthereumContracts } from "./lib/ethereum"
import Account from "./modules/account"
import Tbtc from "./modules/tbtc"
import { VoidSigner } from "./lib/utils"
Expand Down Expand Up @@ -71,14 +67,12 @@ class Acre {
)

const accountBitcoinAddress = await bitcoinProvider.getAddress()
const accountEthereumAddress = EthereumAddress.from(
await orangeKit.predictAddress(accountBitcoinAddress),
const accountEthereumAddress = await orangeKit.predictAddress(
accountBitcoinAddress,
)
const accountPublicKey = await bitcoinProvider.getPublicKey()

const signer = new VoidSigner(
accountEthereumAddress.identifierHex,
ethersProvider,
)
const signer = new VoidSigner(accountEthereumAddress, ethersProvider)

const contracts = getEthereumContracts(signer, ethereumNetwork)

Expand All @@ -88,15 +82,18 @@ class Acre {
tbtcApiUrl,
contracts.bitcoinDepositor,
)

const subgraph = new AcreSubgraphApi(
// TODO: Set correct url based on the network
"https://api.studio.thegraph.com/query/73600/acre/version/latest",
)

const account = new Account(contracts, tbtc, subgraph, {
const account = new Account(contracts, tbtc, subgraph, orangeKit, {
bitcoinAddress: accountBitcoinAddress,
ethereumAddress: accountEthereumAddress,
publicKey: accountPublicKey,
})

const protocol = new Protocol(contracts)

return new Acre(
Expand Down
17 changes: 17 additions & 0 deletions sdk/src/lib/contracts/stbtc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ChainIdentifier } from "./chain-identifier"

export interface StBTC {
/**
* @returns The chain-specific identifier of this contract.
*/
getAddress(): ChainIdentifier

/**
* @param identifier The generic chain identifier.
* @returns Value of the basis for calculating final BTC balance.
Expand All @@ -20,4 +25,16 @@ export interface StBTC {
* @returns Deposit fee.
*/
calculateDepositFee(amount: bigint): Promise<bigint>

/**
* Calculates the amount of tBTC that will be redeemed for the given amount
* of stBTC shares.
* @param amount Amount of stBTC shares to redeem.
*/
previewRedeem(amount: bigint): Promise<bigint>

encodeRedeemToBitcoinFunctionData(
stbtcAmount: bigint,
tbtcRedemptionData: string,
): string
}
32 changes: 29 additions & 3 deletions sdk/src/lib/ethereum/stbtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ class EthereumStBTC
{
readonly #BASIS_POINT_SCALE = BigInt(1e4)

readonly #bitcoinRedeemerContractAddress: string

#cache: {
entryFeeBasisPoints?: bigint
} = { entryFeeBasisPoints: undefined }

constructor(config: EthersContractConfig, network: EthereumNetwork) {
constructor(
config: EthersContractConfig,
network: EthereumNetwork,
bitcoinRedeemerContractAddress: string,
) {
let artifact: EthersContractDeployment

switch (network) {
Expand All @@ -35,6 +41,15 @@ class EthereumStBTC
}

super(config, artifact)

this.#bitcoinRedeemerContractAddress = bitcoinRedeemerContractAddress
}

/**
* @see {StBTC#previewRedeem}
*/
previewRedeem(sharesAmount: bigint): Promise<bigint> {
return this.instance.previewRedeem(sharesAmount)
}

/**
Expand All @@ -54,11 +69,11 @@ class EthereumStBTC
/**
* @see {StBTC#calculateDepositFee}
*/
async calculateDepositFee(amount: bigint): Promise<bigint> {
async calculateDepositFee(tbtcAmount: bigint): Promise<bigint> {
const entryFeeBasisPoints = await this.#getEntryFeeBasisPoints()

return (
(amount * entryFeeBasisPoints) /
(tbtcAmount * entryFeeBasisPoints) /
(entryFeeBasisPoints + this.#BASIS_POINT_SCALE)
)
}
Expand All @@ -72,6 +87,17 @@ class EthereumStBTC

return this.#cache.entryFeeBasisPoints
}

encodeRedeemToBitcoinFunctionData(
sharesAmount: bigint,
tbtcRedemptionData: string,
): string {
return this.instance.interface.encodeFunctionData("approveAndCall", [
this.#bitcoinRedeemerContractAddress,
sharesAmount,
tbtcRedemptionData,
])
}
}

// eslint-disable-next-line import/prefer-default-export
Expand Down
47 changes: 39 additions & 8 deletions sdk/src/modules/account.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { AcreContracts, ChainIdentifier } from "../lib/contracts"
import { OrangeKitSdk } from "@orangekit/sdk"
import { AcreContracts } 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"
import WithdrawalService from "./withdrawal-service"
import { EthereumAddress } from "../lib/ethereum"

/**
* Represents the deposit data.
* Represents the deposit data
*/
export type Deposit = {
/**
Expand Down Expand Up @@ -56,19 +57,32 @@ export default class Account {

readonly #bitcoinAddress: string

readonly #ethereumAddress: ChainIdentifier
readonly #ethereumAddress: EthereumAddress

readonly #withdrawalService: WithdrawalService

constructor(
contracts: AcreContracts,
tbtc: Tbtc,
acreSubgraphApi: AcreSubgraphApi,
account: { bitcoinAddress: string; ethereumAddress: ChainIdentifier },
orangeKit: OrangeKitSdk,
accountData: {
bitcoinAddress: string
ethereumAddress: string
publicKey: string
},
) {
this.#contracts = contracts
this.#tbtc = tbtc
this.#acreSubgraphApi = acreSubgraphApi
this.#bitcoinAddress = account.bitcoinAddress
this.#ethereumAddress = account.ethereumAddress
this.#bitcoinAddress = accountData.bitcoinAddress
this.#ethereumAddress = EthereumAddress.from(accountData.ethereumAddress)
this.#withdrawalService = new WithdrawalService(
contracts,
tbtc,
orangeKit,
accountData,
)
}

/**
Expand Down Expand Up @@ -102,6 +116,23 @@ export default class Account {
return new StakeInitialization(tbtcDeposit)
}

/**
* Initializes the withdrawal process.
* @param referral Data used for referral program.
* @returns Object represents the deposit process.
*/
async initializeWithdrawal(
sharesAmount: bigint,
bitcoinSignMessageFn: (message: string) => Promise<string>,
) {
return this.#withdrawalService.initiateWithdrawal(
this.#bitcoinAddress,
sharesAmount,
bitcoinSignMessageFn,
)
}

/**
* @returns Value of the basis for calculating final BTC balance in 1e18
* precision.
Expand Down
35 changes: 34 additions & 1 deletion sdk/src/modules/tbtc/Tbtc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ChainIdentifier, TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts"
import {
ChainIdentifier,
TBTC as TbtcSdk,
RedeemerProxy as TbtcRedeemerProxy,
EthereumAddress,
} from "@keep-network/tbtc-v2.ts"

import { ethers } from "ethers"
import TbtcApi, { DepositStatus } from "../../lib/api/TbtcApi"
Expand Down Expand Up @@ -145,4 +150,32 @@ export default class Tbtc {
timestamp: deposit.createdAt,
}))
}

async initiateRedemption(
redeemerAddress: string,
destinationBitcoinAddress: string,
tbtcAmount: bigint,
handleRedemptionRequestFn: (redemptionData: string) => Promise<string>,
): Promise<string> {
const tbtcRedeemer: TbtcRedeemerProxy = {
redeemerAddress(): ChainIdentifier {
return EthereumAddress.from(redeemerAddress)
},
async requestRedemption(redemptionData: Hex): Promise<Hex> {
const txHash = await handleRedemptionRequestFn(
redemptionData.toPrefixedString(),
)
return Hex.from(txHash)
},
}

const { targetChainTxHash } =
await this.#tbtcSdk.redemptions.requestRedemptionWithProxy(
destinationBitcoinAddress,
tbtcAmount,
tbtcRedeemer,
)

return targetChainTxHash.toPrefixedString()
}
}
75 changes: 75 additions & 0 deletions sdk/src/modules/withdrawal-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { OrangeKitSdk } from "@orangekit/sdk"

import { AcreContracts } from "../lib/contracts"

import Tbtc from "./tbtc"

export default class WithdrawalService {
readonly #contracts: AcreContracts

readonly #tbtc: Tbtc

#orangeKitSdk: OrangeKitSdk

#account: {
bitcoinAddress: string
ethereumAddress: string
publicKey: string
}

constructor(
contracts: AcreContracts,
tbtc: Tbtc,
orangeKitSdk: OrangeKitSdk,
account: {
bitcoinAddress: string
ethereumAddress: string
publicKey: string
},
) {
this.#contracts = contracts
this.#tbtc = tbtc
this.#orangeKitSdk = orangeKitSdk
this.#account = account
}

async initiateWithdrawal(
destinationBitcoinAddress: string,
sharesAmount: bigint,
bitcoinSignMessageFn: (message: string) => Promise<string>,
): Promise<string> {
// Estimate the amount of tBTC released after redeeming stBTC tokens.
// The value is required by the tBTC SDK to determine the tBTC Wallet from
// which the Bitcoin will be bridged to the depositor's Bitcoin address.
const tbtcAmount = await this.#contracts.stBTC.previewRedeem(sharesAmount)

const requestRedemptionWithOrangeKit = async (
tbtcRedemptionData: string,
) => {
const safeTxData =
this.#contracts.stBTC.encodeRedeemToBitcoinFunctionData(
sharesAmount,
tbtcRedemptionData,
)

const transactionHash = await this.#orangeKitSdk.sendTransaction(
this.#contracts.stBTC.getAddress(),
"0x0",
safeTxData,
this.#account.bitcoinAddress,
this.#account.publicKey,
bitcoinSignMessageFn,
)

// TODO: CHANGE RESULT
return transactionHash
}

return this.#tbtc.initiateRedemption(
this.#account.ethereumAddress,
destinationBitcoinAddress,
tbtcAmount,
requestRedemptionWithOrangeKit,
)
}
}

0 comments on commit d32cc71

Please sign in to comment.