Skip to content

Commit

Permalink
Merge pull request #267 from bob-collective/refactor/sdk
Browse files Browse the repository at this point in the history
refactor: import gateway client and updated wallet logic
  • Loading branch information
gregdhill authored Jun 25, 2024
2 parents 165b7f8 + 6ff8233 commit c191de8
Show file tree
Hide file tree
Showing 24 changed files with 881 additions and 165 deletions.
63 changes: 63 additions & 0 deletions sdk/examples/gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { GatewayApiClient } from "../src/gateway";
import * as bitcoin from "bitcoinjs-lib";
import { AddressType, getAddressInfo } from "bitcoin-address-validation";
import { createTransfer } from "../src/wallet/utxo";
import { hex } from '@scure/base';
import { Transaction as SigTx } from '@scure/btc-signer';

const BOB_TBTC_V2_TOKEN_ADDRESS = "0xBBa2eF945D523C4e2608C9E1214C2Cc64D4fc2e2";

export async function swapBtcForToken(evmAddress: string) {
const gatewayClient = new GatewayApiClient("https://onramp-api-mainnet.gobob.xyz");

const amount = 10000; // 0.0001 BTC
const { fee: _fee, onramp_address: onrampAddress, bitcoin_address: bitcoinAddress, gratuity: _gratuity } = await gatewayClient.getQuote(
BOB_TBTC_V2_TOKEN_ADDRESS,
amount
);

const orderId = await gatewayClient.createOrder(onrampAddress, evmAddress, amount);

const tx = await createTxWithOpReturn("bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", bitcoinAddress, amount, evmAddress);

// NOTE: relayer should broadcast the tx
await gatewayClient.updateOrder(orderId, tx.toHex());

}

async function createTxWithOpReturn(fromAddress: string, toAddress: string, amount: number, opReturn: string, fromPubKey?: string): Promise<bitcoin.Transaction> {
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);
const psbtHex = hex.encode(psbt);

// TODO: sign PSBT
const signedPsbtHex = psbtHex;

const signedTx = SigTx.fromPSBT(bitcoin.Psbt.fromHex(signedPsbtHex).toBuffer());
signedTx.finalize();

return bitcoin.Transaction.fromBuffer(Buffer.from(signedTx.extract()));
}
104 changes: 92 additions & 12 deletions sdk/package-lock.json

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

7 changes: 5 additions & 2 deletions sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gobob/bob-sdk",
"version": "1.2.0",
"version": "2.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
Expand Down Expand Up @@ -29,6 +29,9 @@
"yargs": "^17.5.1"
},
"dependencies": {
"bitcoinjs-lib": "^6.1.6"
"@scure/base": "^1.1.6",
"@scure/btc-signer": "^1.3.1",
"bitcoin-address-validation": "^2.2.3",
"bitcoinjs-lib": "^6.1.5"
}
}
16 changes: 8 additions & 8 deletions sdk/scripts/relay-genesis.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DefaultElectrsClient } from "../src/electrs";
import { DefaultEsploraClient } from "../src/esplora";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { exec } from "child_process";
Expand Down Expand Up @@ -50,32 +50,32 @@ function range(size: number, startAt = 0) {
return [...Array(size).keys()].map(i => i + startAt);
}

async function getRetargetHeaders(electrs: DefaultElectrsClient, nextRetargetHeight: number, proofLength: number) {
const beforeRetarget = await Promise.all(range(proofLength, nextRetargetHeight - proofLength).map(height => electrs.getBlockHeaderAt(height)));
const afterRetarget = await Promise.all(range(proofLength, nextRetargetHeight).map(height => electrs.getBlockHeaderAt(height)));
async function getRetargetHeaders(esploraClient: DefaultEsploraClient, nextRetargetHeight: number, proofLength: number) {
const beforeRetarget = await Promise.all(range(proofLength, nextRetargetHeight - proofLength).map(height => esploraClient.getBlockHeaderAt(height)));
const afterRetarget = await Promise.all(range(proofLength, nextRetargetHeight).map(height => esploraClient.getBlockHeaderAt(height)));
return beforeRetarget.concat(afterRetarget).join("");
}

async function main(): Promise<void> {
const electrs = new DefaultElectrsClient(args["network"]);
const esploraClient = new DefaultEsploraClient(args["network"]);

let initHeight = args["init-height"];
if (initHeight == "latest") {
const currentHeight = await electrs.getLatestHeight();
const currentHeight = await esploraClient.getLatestHeight();
initHeight = currentHeight - (currentHeight % 2016) - 2016;
console.log(`Using block ${initHeight}`)
}
if ((initHeight % 2016) != 0) {
throw new Error("Invalid genesis height: must be multiple of 2016");
}

const genesis = await electrs.getBlockHeaderAt(initHeight);
const genesis = await esploraClient.getBlockHeaderAt(initHeight);

const proofLength = args["proof-length"];
const nextRetargetHeight = initHeight + 2016;
console.log(`Next retarget height: ${nextRetargetHeight}`);

const retargetHeaders = await getRetargetHeaders(electrs, nextRetargetHeight, proofLength);
const retargetHeaders = await getRetargetHeaders(esploraClient, nextRetargetHeight, proofLength);

let rpcUrl: string;
let verifyOpts: string | undefined;
Expand Down
14 changes: 7 additions & 7 deletions sdk/scripts/relay-retarget.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DefaultElectrsClient } from "../src/electrs";
import { DefaultEsploraClient } from "../src/esplora";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { exec } from "child_process";
Expand Down Expand Up @@ -38,14 +38,14 @@ function range(size: number, startAt = 0) {
return [...Array(size).keys()].map(i => i + startAt);
}

async function getRetargetHeaders(electrs: DefaultElectrsClient, nextRetargetHeight: number, proofLength: number) {
const beforeRetarget = await Promise.all(range(proofLength, nextRetargetHeight - proofLength).map(height => electrs.getBlockHeaderAt(height)));
const afterRetarget = await Promise.all(range(proofLength, nextRetargetHeight).map(height => electrs.getBlockHeaderAt(height)));
async function getRetargetHeaders(esploraClient: DefaultEsploraClient, nextRetargetHeight: number, proofLength: number) {
const beforeRetarget = await Promise.all(range(proofLength, nextRetargetHeight - proofLength).map(height => esploraClient.getBlockHeaderAt(height)));
const afterRetarget = await Promise.all(range(proofLength, nextRetargetHeight).map(height => esploraClient.getBlockHeaderAt(height)));
return beforeRetarget.concat(afterRetarget).join("");
}

async function main(): Promise<void> {
const electrs = new DefaultElectrsClient(args["network"]);
const esploraClient = new DefaultEsploraClient(args["network"]);

let privateKey: string;
if (args["private-key"]) {
Expand Down Expand Up @@ -94,12 +94,12 @@ async function main(): Promise<void> {
console.log(`Next retarget height: ${nextRetargetHeight}`);

try {
await electrs.getBlockHash(nextRetargetHeight + proofLength);
await esploraClient.getBlockHash(nextRetargetHeight + proofLength);
} catch (e) {
throw new Error(`Cannot retarget without ${proofLength} headers after ${nextRetargetHeight}`);
}

const retargetHeaders = await getRetargetHeaders(electrs, nextRetargetHeight, proofLength);
const retargetHeaders = await getRetargetHeaders(esploraClient, nextRetargetHeight, proofLength);

let env = [
`RELAY_ADDRESS=${relayAddress}`,
Expand Down
Loading

0 comments on commit c191de8

Please sign in to comment.