Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/sdk/vitest-2.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
gregdhill authored Sep 18, 2024
2 parents 77e5c8d + eba2ee1 commit d93ef78
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 30 deletions.
2 changes: 1 addition & 1 deletion docs/docs/learn/guides/bob-gateway/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ sidebar_label: BOB Gateway
It's built on a trustless, RFQ-based cross-chain [swap protocol](../../../build/examples/btc-swap/) that connects professional LPs with users through a seamless swapping experience. Essentially, LPs handle the complexities of bridging and staking on behalf of users in exchange for a fee.

:::tip Interested in providing liquidity?
If you are interested in being an LP for the BOB Gateway bridge, please send us an email at `gateway [ at ] gobob.xyz`.
If you are interested in being an LP for the BOB Gateway bridge, please send us an email at `gateway@gobob.xyz`.
:::

All you need is a Bitcoin wallet with some BTC to send and an EVM-compatible wallet to receive your Bitcoin LST/LRT or wrapped Bitcoin on BOB. We'll even send you some ETH to cover the fees of your first few transactions on BOB.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/learn/introduction/contribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ If you are keen to build on BOB or contribute to BOB itself:

## More

- Liquidity providers: If you are interested in being an LP for the BOB Gateway bridge, please send us an email at `gateway [ at ] gobob.xyz`.
- Liquidity providers: If you are interested in being an LP for the BOB Gateway bridge, please send us an email at `gateway@gobob.xyz`.
- Press kit: [BOB Press Kit](https://build-on-bitcoin.notion.site/BOB-Press-Kit-1be66c38713d480eab01000bdd164206)
- Brand assets: [BOB Brand Assets](https://drive.google.com/drive/u/0/folders/1c30QDkyWgaV8xSEpCXFWJj1WQyUjSm7N)
4 changes: 2 additions & 2 deletions sdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gobob/bob-sdk",
"version": "2.2.8",
"version": "2.3.3",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
Expand Down
21 changes: 15 additions & 6 deletions sdk/src/esplora.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,17 @@ export interface Transaction {
scriptpubkey_address?: string
value: number
}>
status: {
confirmed: boolean
block_height?: number
block_hash?: string
block_time?: number
}
status: TransactionStatus
}

/**
* @ignore
*/
export interface TransactionStatus {
confirmed: boolean
block_height?: number
block_hash?: string
block_time?: number
}

/**
Expand Down Expand Up @@ -259,6 +264,10 @@ export class EsploraClient {
return this.getJson(`${this.basePath}/tx/${txId}`);
}

async getTransactionStatus(txId: string): Promise<TransactionStatus> {
return this.getJson(`${this.basePath}/tx/${txId}/status`);
}

/**
* Get the transaction data, represented as a hex string, for a Bitcoin transaction with a given ID (txId).
*
Expand Down
71 changes: 63 additions & 8 deletions sdk/src/gateway/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
GatewayStartOrder,
GatewayStrategy,
EvmAddress,
GatewayTokensInfo,
OrderStatus,
OrderStatusType,
} from "./types";
import { SYMBOL_LOOKUP, ADDRESS_LOOKUP } from "./tokens";
import { createBitcoinPsbt } from "../wallet";
Expand Down Expand Up @@ -80,7 +83,7 @@ export class GatewayApiClient {
GatewayQuoteParams,
"amount" | "fromChain" | "fromToken" | "fromUserAddress" | "toUserAddress"
>,
): Promise<GatewayQuote> {
): Promise<GatewayQuote & GatewayTokensInfo> {
const isMainnet =
params.toChain === ChainId.BOB ||
(typeof params.toChain === "string" && params.toChain.toLowerCase() === Chain.BOB);
Expand All @@ -97,7 +100,7 @@ export class GatewayApiClient {
throw new Error("Invalid output chain");
}

let outputToken = "";
let outputTokenAddress = "";
let strategyAddress: string | undefined;

const toToken = params.toToken.toLowerCase();
Expand All @@ -106,16 +109,16 @@ export class GatewayApiClient {
}

if (toToken.startsWith("0x")) {
outputToken = toToken;
outputTokenAddress = toToken;
} else if (isMainnet && this.chain === Chain.BOB && SYMBOL_LOOKUP[ChainId.BOB][toToken]) {
outputToken = SYMBOL_LOOKUP[ChainId.BOB][toToken].address;
outputTokenAddress = SYMBOL_LOOKUP[ChainId.BOB][toToken].address;
} else if (isTestnet && this.chain === Chain.BOB_SEPOLIA && SYMBOL_LOOKUP[ChainId.BOB_SEPOLIA][toToken]) {
outputToken = SYMBOL_LOOKUP[ChainId.BOB_SEPOLIA][toToken].address;
outputTokenAddress = SYMBOL_LOOKUP[ChainId.BOB_SEPOLIA][toToken].address;
} else {
throw new Error("Unknown output token");
}

var url = new URL(`${this.baseUrl}/quote/${outputToken}`);
var url = new URL(`${this.baseUrl}/quote/${outputTokenAddress}`);
if (strategyAddress) {
url.searchParams.append("strategy", `${strategyAddress}`);
}
Expand All @@ -135,6 +138,8 @@ export class GatewayApiClient {
return {
...quote,
fee: quote.fee + (params.gasRefill || 0),
baseToken: ADDRESS_LOOKUP[quote.baseTokenAddress],
outputToken: quote.strategyAddress ? ADDRESS_LOOKUP[outputTokenAddress] : undefined,
};
}

Expand Down Expand Up @@ -252,11 +257,61 @@ export class GatewayApiClient {
* @param userAddress The user's EVM address.
* @returns {Promise<GatewayOrder[]>} The array of account orders.
*/
async getOrders(userAddress: EvmAddress): Promise<GatewayOrder[]> {
async getOrders(userAddress: EvmAddress): Promise<(GatewayOrder & GatewayTokensInfo)[]> {
const response = await this.fetchGet(`${this.baseUrl}/orders/${userAddress}`);
const orders: GatewayOrderResponse[] = await response.json();
return orders.map((order) => {
return { gasRefill: order.satsToConvertToEth, ...order };
function getFinal<L, R>(base?: L, output?: R) {
return order.status
? order.strategyAddress
? output
? output // success
: base // failed
: base // success
: order.strategyAddress // pending
? output
: base;
}
const getTokenAddress = (): string | undefined => {
return getFinal(order.baseTokenAddress, order.outputTokenAddress);
}
const getConfirmations = async (esploraClient: EsploraClient, latestHeight?: number) => {
const txStatus = await esploraClient.getTransactionStatus(order.txid);
if (!latestHeight) {
latestHeight = await esploraClient.getLatestHeight();
}
return txStatus.confirmed ? latestHeight - txStatus.block_height! + 1 : 0;
}
return {
gasRefill: order.satsToConvertToEth,
...order,
baseToken: ADDRESS_LOOKUP[order.baseTokenAddress],
outputToken: ADDRESS_LOOKUP[order.outputTokenAddress],
getTokenAddress,
getToken() {
return ADDRESS_LOOKUP[getTokenAddress()];
},
getAmount(): string | number | undefined {
const baseAmount = order.satoshis - order.fee;
return getFinal(baseAmount, order.outputTokenAmount);
},
getConfirmations,
async getStatus(esploraClient: EsploraClient, latestHeight?: number): Promise<OrderStatus> {
const confirmations = await getConfirmations(esploraClient, latestHeight);
const hasEnoughConfirmations = confirmations >= order.txProofDifficultyFactor;
const data = {
confirmations,
confirmed: hasEnoughConfirmations
};
return order.status
? order.strategyAddress
? order.outputTokenAddress
? { status: OrderStatusType.Success, data }
: { status: OrderStatusType.Failed, data }
: { status: OrderStatusType.Success, data }
: { status: OrderStatusType.Pending, data };
},
};
});
}

Expand Down
9 changes: 8 additions & 1 deletion sdk/src/gateway/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
export { GatewayApiClient as GatewaySDK } from "./client";
export { GatewayQuoteParams, GatewayQuote, GatewayOrder, GatewayStrategyContract } from "./types";
export {
GatewayQuoteParams,
GatewayQuote,
GatewayOrder,
GatewayStrategyContract,
OrderStatusType,
OrderStatus,
} from "./types";
51 changes: 48 additions & 3 deletions sdk/src/gateway/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { EsploraClient } from "../esplora";

type ChainSlug = string | number;
type TokenSymbol = string;

Expand Down Expand Up @@ -160,6 +162,8 @@ export interface GatewayStrategyContract {
export type GatewayQuote = {
/** @description The gateway address */
gatewayAddress: EvmAddress;
/** @description The base token address (e.g. wBTC or tBTC) */
baseTokenAddress: EvmAddress;
/** @description The minimum amount of Bitcoin to send */
dustThreshold: number;
/** @description The satoshi output amount */
Expand All @@ -185,11 +189,27 @@ export type GatewayCreateOrderRequest = {
satoshis: number;
};

export type GatewayOrderResponse = {
export type OrderStatusData = {
confirmations: number;
confirmed: boolean;
};

export enum OrderStatusType {
Success = "Success",
Failed = "Failed",
Pending = "Pending",
}

export type OrderStatus =
| { status: OrderStatusType.Success; data: OrderStatusData }
| { status: OrderStatusType.Failed; data: OrderStatusData }
| { status: OrderStatusType.Pending; data: OrderStatusData };

export interface GatewayOrderResponse {
/** @description The gateway address */
gatewayAddress: EvmAddress;
/** @description The token address */
tokenAddress: EvmAddress;
/** @description The base token address (e.g. wBTC or tBTC) */
baseTokenAddress: EvmAddress;
/** @description The Bitcoin txid */
txid: string;
/** @description True when the order was executed on BOB */
Expand All @@ -208,6 +228,24 @@ export type GatewayOrderResponse = {
strategyAddress?: EvmAddress;
/** @description The gas refill in satoshis */
satsToConvertToEth: number;
/** @description The amount of ETH received */
outputEthAmount?: string;
/** @description The output token (from strategies) */
outputTokenAddress?: EvmAddress;
/** @description The output amount (from strategies) */
outputTokenAmount?: string;
/** @description The tx hash on the EVM chain */
txHash?: string;
/** @description Get the actual token address received */
getTokenAddress(): string | undefined;
/** @description Get the actual token received */
getToken(): Token | undefined;
/** @description Get the actual amount received of the token */
getAmount(): string | number | undefined;
/** @description Get the number of confirmations */
getConfirmations(esploraClient: EsploraClient, latestHeight?: number): Promise<number>;
/** @description Get the actual order status */
getStatus(esploraClient: EsploraClient, latestHeight?: number): Promise<OrderStatus>;
};

/** Order given by the Gateway API once the bitcoin tx is submitted */
Expand All @@ -219,6 +257,13 @@ export type GatewayOrder = Omit<
"satsToConvertToEth"
>;

export type GatewayTokensInfo = {
/** @description The base token (e.g. wBTC or tBTC) */
baseToken: Token,
/** @description The output token (e.g. uniBTC or SolvBTC.BBN) */
outputToken?: Token,
};

/** @dev Internal */
export type GatewayCreateOrderResponse = {
uuid: string;
Expand Down
1 change: 1 addition & 0 deletions sdk/src/wallet/utxo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export async function createBitcoinPsbt(
network: getBtcNetwork(network),
allowUnknownOutputs: true, // Required for OP_RETURN
allowLegacyWitnessUtxo: true, // Required for P2SH-P2WPKH
dust: BigInt(546) as any, // TODO: update scure-btc-signer
});

if (!transaction || !transaction.tx) {
Expand Down
Loading

0 comments on commit d93ef78

Please sign in to comment.