Skip to content

Commit

Permalink
Merge pull request #322 from bob-collective/feat/start-order-psbt
Browse files Browse the repository at this point in the history
feat: add psbt to start order result
  • Loading branch information
gregdhill authored Aug 27, 2024
2 parents 4d253e6 + 780c289 commit 9c52341
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 110 deletions.
49 changes: 9 additions & 40 deletions sdk/examples/gateway.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { GatewayQuoteParams, GatewaySDK } from "../src/gateway";
import { AddressType, getAddressInfo } from "bitcoin-address-validation";
import { createTransfer } from "../src/wallet/utxo";
import { base64 } from '@scure/base';
import { Transaction } from '@scure/btc-signer';

const BOB_TBTC_V2_TOKEN_ADDRESS = "0xBBa2eF945D523C4e2608C9E1214C2Cc64D4fc2e2";
Expand All @@ -9,51 +8,21 @@ export async function swapBtcForToken(evmAddress: string) {
const gatewaySDK = new GatewaySDK("bob"); // or "mainnet"

const quoteParams: GatewayQuoteParams = {
toChain: "bob",
fromChain: "Bitcoin",
fromUserAddress: "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d",
toChain: "BOB",
toUserAddress: evmAddress,
toToken: BOB_TBTC_V2_TOKEN_ADDRESS, // or "tBTC"
amount: 10000000, // 0.1 BTC
gasRefill: 10000, // 0.0001 BTC
};
const quote = await gatewaySDK.getQuote(quoteParams);
const { bitcoinAddress, satoshis } = quote;

const { uuid, opReturnHash } = await gatewaySDK.startOrder(quote, quoteParams);
const { uuid, psbtBase64 } = await gatewaySDK.startOrder(quote, quoteParams);

const tx = await createTxWithOpReturn("bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", bitcoinAddress, satoshis, opReturnHash);
// NOTE: up to implementation to sign PSBT here!
const tx = Transaction.fromPSBT(base64.decode(psbtBase64!));

// NOTE: relayer should broadcast the tx
await gatewaySDK.finalizeOrder(uuid, tx.toString("hex"));
}

async function createTxWithOpReturn(fromAddress: string, toAddress: string, amount: number, opReturn: string, fromPubKey?: string): Promise<Buffer> {
const addressType = getAddressInfo(fromAddress).type;

// Ensure this is not the P2TR address for ordinals (we don't want to spend from it)
if (addressType === AddressType.p2tr) {
throw new Error('Cannot transfer using Taproot (P2TR) address. Please use another address type.');
}

// We need the public key to generate the redeem and witness script to spend the scripts
if (addressType === (AddressType.p2sh || AddressType.p2wsh)) {
if (!fromPubKey) {
throw new Error('Public key is required to spend from the selected address type');
}
}

const unsignedTx = await createTransfer(
'mainnet',
addressType,
fromAddress,
toAddress,
amount,
fromPubKey,
opReturn,
);

const psbt = unsignedTx.toPSBT(0);
// TODO: sign PSBT
const signedTx = Transaction.fromPSBT(psbt);

return Buffer.from(signedTx.extract())
// NOTE: relayer broadcasts the tx
await gatewaySDK.finalizeOrder(uuid, tx.hex);
}
87 changes: 87 additions & 0 deletions sdk/package-lock.json

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

3 changes: 2 additions & 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.0.0",
"version": "2.1.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
Expand All @@ -22,6 +22,7 @@
"@types/yargs": "^17.0.33",
"ecpair": "^2.1.0",
"mocha": "^10.7.3",
"nock": "^14.0.0-beta.11",
"tiny-secp256k1": "^2.2.3",
"ts-node": "^10.0.0",
"typescript": "^5.5.4",
Expand Down
65 changes: 45 additions & 20 deletions sdk/src/gateway/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { ethers, AbiCoder } from "ethers";
import { GatewayQuoteParams } from "./types";
import { TOKENS_INFO, ADDRESS_LOOKUP, Token as TokenInfo } from "./tokens";
import { SYMBOL_LOOKUP, ADDRESS_LOOKUP, Token as TokenInfo } from "./tokens";
import { createBitcoinPsbt } from "../wallet";

export enum Chains {
// NOTE: we also support Bitcoin testnet
Bitcoin = "bitcoin",
BOB = "bob",
BOBSepolia = "bobsepolia",
};

type EvmAddress = string;

Expand All @@ -18,17 +26,17 @@ type GatewayQuote = {
/** @description The number of confirmations required to confirm the Bitcoin tx */
txProofDifficultyFactor: number;
/** @description The optional strategy address */
strategyAddress: EvmAddress | null,
strategyAddress?: EvmAddress,
};

/** @dev Internal request type used to call the Gateway API */
type GatewayCreateOrderRequest = {
gatewayAddress: EvmAddress,
strategyAddress: EvmAddress | null,
strategyAddress?: EvmAddress,
satsToConvertToEth: number,
userAddress: EvmAddress,
gatewayExtraData: string | null,
strategyExtraData: string | null,
gatewayExtraData?: string,
strategyExtraData?: string,
satoshis: number,
};

Expand All @@ -52,7 +60,7 @@ type GatewayOrderResponse = {
/** @description The number of confirmations required to confirm the Bitcoin tx */
txProofDifficultyFactor: number;
/** @description The optional strategy address */
strategyAddress: EvmAddress | null,
strategyAddress?: EvmAddress,
/** @description The gas refill in satoshis */
satsToConvertToEth: number,
};
Expand All @@ -72,6 +80,7 @@ type GatewayCreateOrderResponse = {
type GatewayStartOrderResult = GatewayCreateOrderResponse & {
bitcoinAddress: string,
satoshis: number;
psbtBase64?: string;
};

/**
Expand Down Expand Up @@ -104,11 +113,13 @@ export class GatewayApiClient {
*/
constructor(networkOrUrl: string = "mainnet") {
switch (networkOrUrl) {
case "mainnet" || "bob":
case "mainnet":
case "bob":
this.network = Network.Mainnet;
this.baseUrl = MAINNET_GATEWAY_BASE_URL;
break;
case "testnet" || "bobSepolia":
case "testnet":
case "bobSepolia":
this.network = Network.Testnet;
this.baseUrl = TESTNET_GATEWAY_BASE_URL;
break;
Expand All @@ -123,17 +134,18 @@ export class GatewayApiClient {
* @param params The parameters for the quote.
*/
async getQuote(params: GatewayQuoteParams): Promise<GatewayQuote> {
const isMainnet = params.toChain == "bob" || params.toChain == 60808;
const isTestnet = params.toChain == "bobSepolia" || params.toChain == 808813;
const isMainnet = params.toChain === 60808 || typeof params.toChain === "string" && params.toChain.toLowerCase() === Chains.BOB;
const isTestnet = params.toChain === 808813 || typeof params.toChain === "string" && params.toChain.toLowerCase() === Chains.BOBSepolia;

const toToken = params.toToken.toLowerCase();
let outputToken = "";
if (params.toToken.startsWith("0x")) {
outputToken = params.toToken;
} else if (params.toToken in TOKENS_INFO) {
if (toToken.startsWith("0x")) {
outputToken = toToken;
} else if (toToken in SYMBOL_LOOKUP) {
if (isMainnet && this.network === Network.Mainnet) {
outputToken = TOKENS_INFO[params.toToken].bob;
outputToken = SYMBOL_LOOKUP[toToken].bob;
} else if (isTestnet && this.network === Network.Testnet) {
outputToken = TOKENS_INFO[params.toToken].bobSepolia;
outputToken = SYMBOL_LOOKUP[toToken].bobSepolia;
} else {
throw new Error('Unknown network');
}
Expand Down Expand Up @@ -164,11 +176,11 @@ export class GatewayApiClient {
const request: GatewayCreateOrderRequest = {
gatewayAddress: gatewayQuote.gatewayAddress,
strategyAddress: gatewayQuote.strategyAddress,
satsToConvertToEth: params.gasRefill,
satsToConvertToEth: params.gasRefill || 0,
userAddress: params.toUserAddress,
// TODO: figure out how to get extra data
gatewayExtraData: null,
strategyExtraData: null,
gatewayExtraData: undefined,
strategyExtraData: undefined,
satoshis: gatewayQuote.satoshis,
};

Expand All @@ -191,11 +203,24 @@ export class GatewayApiClient {
throw new Error('Invalid OP_RETURN hash');
}

let psbtBase64: string;
if (params.fromUserAddress && typeof params.fromChain === "string" && params.fromChain.toLowerCase() === Chains.Bitcoin) {
psbtBase64 = await createBitcoinPsbt(
params.fromUserAddress,
gatewayQuote.bitcoinAddress,
gatewayQuote.satoshis,
params.fromUserPublicKey,
data.opReturnHash,
gatewayQuote.txProofDifficultyFactor
);
}

return {
uuid: data.uuid,
opReturnHash: data.opReturnHash,
bitcoinAddress: gatewayQuote.bitcoinAddress,
satoshis: gatewayQuote.satoshis,
psbtBase64,
}
}

Expand Down Expand Up @@ -284,8 +309,8 @@ function calculateOpReturnHash(req: GatewayCreateOrderRequest) {
req.strategyAddress || ethers.ZeroAddress,
req.satsToConvertToEth,
req.userAddress,
req.gatewayExtraData,
req.strategyExtraData
req.gatewayExtraData || "0x",
req.strategyExtraData || "0x"
]
))
}
Loading

0 comments on commit 9c52341

Please sign in to comment.