Skip to content

Commit

Permalink
Merge pull request #221 from bob-collective/dom/op-return
Browse files Browse the repository at this point in the history
feat: extend electrs API to add transactions and block endpoints
  • Loading branch information
nud3l authored Apr 27, 2024
2 parents 113749d + 0ba73e1 commit e7f2761
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 10 deletions.
24 changes: 24 additions & 0 deletions sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# BOB SDK

The BOB SDK helps you interact with BOB and Bitcoin, including Ordinals, BRC20, Runes, and more.

## Learn more

- [Website](https://www.gobob.xyz/)
- [Docs](https://docs.gobob.xyz/)

## Installation

```shell
pnpm i @bob-sdk/sdk
```

## Test

```shell
pnpm test
```

## Contributing

BOB is an open-source project. We welcome contributions of all sorts. There are many ways to help, from reporting issues, contributing code, and helping us improve our community.
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": "1.1.1",
"version": "1.2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
Expand Down
136 changes: 131 additions & 5 deletions sdk/src/electrs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,89 @@ export const REGTEST_ESPLORA_BASE_PATH = "http://localhost:3003";
*/
export interface MerkleProof {
blockHeight: number
merkle: string,
pos: number,
merkle: string
pos: number
}

/**
* @ignore
*/
export interface UTXO {
txid: string
vout: number,
value: number,
confirmed: boolean,
vout: number
value: number
confirmed: boolean
height?: number
}

/**
* @ignore
*/
export interface Transaction {
txid: string
version: number
locktime: number
size: number
weight: number
fee: number
vin: Array<{
txid: string
vout: number
is_coinbase: boolean
scriptsig: string
scriptsig_asm: string
inner_redeemscript_asm?: string
inner_witnessscript_asm?: string
sequence?: number
witness?: string[]
prevout: {
scriptpubkey: string
scriptpubkey_asm: string
scriptpubkey_type: string
scriptpubkey_address: string
value: number
} | null
}>
vout: Array<{
scriptpubkey: string
scriptpubkey_asm?: string
scriptpubkey_type?: string
scriptpubkey_address?: string
value: number
}>
status: {
confirmed: boolean
block_height: number
block_hash: string
block_time: number
}
}

/**
* @ignore
*/
export interface Block {
id: string
height: number
version: number
timestamp: number
bits: number
nonce: number
difficulty: number
merkle_root: string
tx_count: number
size: number
weight: number
previousblockhash: string | null
mediantime: number
}

/**
*
* The `ElectrsClient` interface provides a set of methods for interacting with an Esplora API
* for Bitcoin network data retrieval.
* See https://github.com/blockstream/esplora/blob/master/API.md for more information.
*/
export interface ElectrsClient {
/**
* Get the latest block height of the Bitcoin chain.
Expand All @@ -42,6 +110,28 @@ export interface ElectrsClient {
*/
getLatestHeight(): Promise<number>;

/**
* Get the complete block data for a Bitcoin block with a given hash.
*
* @param {string} hash - The hash of the Bitcoin block.
* @returns {Promise<Block>} A promise that resolves to the block data.
*
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const electrsClient = new DefaultElectrsClient(BITCOIN_NETWORK);
* const blockHash = 'your_block_hash_here';
* electrsClient.getBlock(blockHash)
* .then((block) => {
* console.log(`Block data for block with hash ${blockHash}: ${JSON.stringify(block)}`);
* })
* .catch((error) => {
* console.error(`Error: ${error}`);
* });
* ```
*/
getBlock(hash: string): Promise<Block>;

/**
* Get the block hash of the Bitcoin block at a specific height.
*
Expand Down Expand Up @@ -88,6 +178,28 @@ export interface ElectrsClient {
*/
getBlockHeader(hash: string): Promise<string>;

/**
* Get the complete transaction data for a Bitcoin transaction with a given ID (txId).
*
* @param txId {string} - The ID of a Bitcoin transaction.
* @returns {Promise<Transaction>} A promise that resolves to the transaction data.
*
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const electrsClient = new DefaultElectrsClient(BITCOIN_NETWORK);
* const transactionId = 'your_transaction_id_here';
* electrsClient.getTransaction(transactionId)
* .then((transaction) => {
* console.log(`Transaction data for transaction with ID ${transactionId}: ${JSON.stringify(transaction)}`);
* })
* .catch((error) => {
* console.error(`Error: ${error}`);
* });
* ```
*/
getTransaction(txId: string): Promise<Transaction>;

/**
* Get the transaction data, represented as a hex string, for a Bitcoin transaction with a given ID (txId).
*
Expand Down Expand Up @@ -208,6 +320,13 @@ export class DefaultElectrsClient implements ElectrsClient {
return parseInt(await this.getText(`${this.basePath}/blocks/tip/height`), 10);
}

/**
* @ignore
*/
async getBlock(blockHash: string): Promise<Block> {
return this.getJson(`${this.basePath}/block/${blockHash}`);
}

/**
* @ignore
*/
Expand All @@ -230,6 +349,13 @@ export class DefaultElectrsClient implements ElectrsClient {
return await this.getBlockHeader(blockHash);
}

/**
* @ignore
*/
async getTransaction(txId: string): Promise<Transaction> {
return this.getJson(`${this.basePath}/tx/${txId}`);
}

/**
* @ignore
*/
Expand Down
74 changes: 72 additions & 2 deletions sdk/test/electrs.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
import { assert, describe, it } from "vitest";
import { DefaultElectrsClient } from "../src/electrs";
import { DefaultElectrsClient, Transaction, Block } from "../src/electrs";

describe("Electrs Tests", () => {
it("should get block height", async () => {
const client = new DefaultElectrsClient("testnet");
const height = await client.getLatestHeight();
assert(height > 0);
});

it("should get block", async () => {
const client = new DefaultElectrsClient("testnet");
const block = await client.getBlock("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943");
const expectedBlock: Block = {
id: '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943',
height: 0,
version: 1,
timestamp: 1296688602,
tx_count: 1,
size: 285,
weight: 1140,
merkle_root: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
previousblockhash: null,
mediantime: 1296688602,
nonce: 414098458,
bits: 486604799,
difficulty: 1
}

for (const key in expectedBlock) {
assert.equal(block[key], expectedBlock[key]);
}
});

it("should get block hash", async () => {
const client = new DefaultElectrsClient("testnet");
Expand All @@ -21,6 +50,45 @@ describe("Electrs Tests", () => {
assert.equal(blockHeader,"010000001e93aa99c8ff9749037d74a2207f299502fa81d56a4ea2ad5330ff50000000002ec2266c3249ce2e079059e0aec01a2d8d8306a468ad3f18f06051f2c3b1645435e9494dffff001d008918cf");
});

it("should get transaction", async () => {
const client = new DefaultElectrsClient("testnet");
const tx = await client.getTransaction("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b");
const expectedTransaction: Transaction = {
txid: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
version: 1,
locktime: 0,
vin: [
{
txid: '0000000000000000000000000000000000000000000000000000000000000000',
vout: 4294967295,
prevout: null,
scriptsig: '04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73',
scriptsig_asm: 'OP_PUSHBYTES_4 ffff001d OP_PUSHBYTES_1 04 OP_PUSHBYTES_69 5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73',
is_coinbase: true,
sequence: 4294967295
}
],
vout: [
{
scriptpubkey: '4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac',
scriptpubkey_asm: 'OP_PUSHBYTES_65 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG',
scriptpubkey_type: 'p2pk',
value: 5000000000
}
],
size: 204,
weight: 816,
fee: 0,
status: {
confirmed: true,
block_height: 0,
block_hash: '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943',
block_time: 1296688602
}
};
assert.deepEqual(tx, expectedTransaction);
});

it("should get tx hex", async () => {
const client = new DefaultElectrsClient("testnet");
const txHex = await client.getTransactionHex("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b");
Expand All @@ -45,6 +113,8 @@ describe("Electrs Tests", () => {
it("should get fee rate", async () => {
const client = new DefaultElectrsClient("testnet");
const feeRate = await client.getFeeEstimate(1);
assert(feeRate == 1);
assert.isAbove(feeRate, 1);
});


});
4 changes: 2 additions & 2 deletions sdk/test/ordinal-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ describe("Ordinal API Tests", () => {
previous: InscriptionId.fromString('332d3fae125de51de29e97cd9e80aab7c63025d5094944a3dceb117c556c41cci0'),
rune: null,
sat: null,
satpoint: SatPoint.fromString('3b509ea77e4809a6014110d20fd66dda26ebc6da26f291951c2aa292f5adfe54:0:207518859'),
satpoint: SatPoint.fromString('a4f11b32041419829b56fe456a976efef0c3ba557cf6041918e81e5d3265b884:2:96181932'),
timestamp: 1699246476,
value: 268048971,
value: 156405502,
};

assert.deepStrictEqual(expectedInscriptionJson, inscriptionJson);
Expand Down

0 comments on commit e7f2761

Please sign in to comment.