Skip to content

Commit

Permalink
feat(sdk): migrate utxo-api API calls to v2 (#28)
Browse files Browse the repository at this point in the history
* refactor: migrate `/utxo/unspents` API calls to v2

* refactor: migrate `/utxo/transaction` API calls to v2

* refactor: add types for Transaction

also, moved types to relevant files

* refactor: migrate `/utxo/inscriptions` API calls to v2

* fix: bug fixes and improvements

* fix: update import

* refactor: remove txhex from fetch unspents

* refactor: update utxo api v2 URL

* refactor: update relay tx fns to use new API

* refactor: pass interface as generic and remove explicit return type

* refactor: move interface to relevant file and delete unused

* refactor: rename snake-cased props to camelCase

* refactor: move interface to relevant file

also, replace string w/ specific type

* fix: update incorrect import path

* fix: pass correct argument to RPC and update incorrect check

* fix: update true negative condition where index:0 was marked as invalid

* refactor: add default object for options in wallet.signPsbt fn

* chore: add npm command to watch and build

* chore: remove unnecessary config from vscode settings
  • Loading branch information
iamcrazycoder authored Jul 28, 2023
1 parent a0ee76e commit 5f61122
Show file tree
Hide file tree
Showing 17 changed files with 344 additions and 594 deletions.
4 changes: 0 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"material-icon-theme.files.associations": {
"index.ts": "tree",
},
"files.exclude": {
"**/.turbo": true,
"**/node_modules": true,
Expand All @@ -24,7 +21,6 @@
"**/.DS_Store": true,
"**/Thumbs.db": true
},
"typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
],
"scripts": {
"build": "tsc -b ./tsconfig.build.json",
"build:watch": "tsc -b ./tsconfig.build.json --watch",
"flush": "npm run clean && rm -rf ./node_modules",
"clean": "rm -rf ./.turbo ./dist",
"lint": "eslint ."
Expand Down
53 changes: 0 additions & 53 deletions packages/sdk/src/api/entities.ts

This file was deleted.

201 changes: 61 additions & 140 deletions packages/sdk/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { fetch as _fetch } from "cross-fetch";
import * as bitcoin from "bitcoinjs-lib";

import { apiConfig } from "../config";
import { Network } from "../config/types";
import { Inscription } from "../inscription/types";
import { Transaction, UTXO } from "../transactions/types";
import { rpc } from "./jsonrpc";
import { FetchInscriptionsOptions, FetchTxOptions, FetchTxResponse, FetchUnspentUTXOsOptions, FetchUnspentUTXOsResponse, RelayTxOptions } from "./types";

export class OrditApi {
static readonly #config = apiConfig;
Expand All @@ -11,163 +15,80 @@ export class OrditApi {
this.#network = network;
}

static async fetch<T>(uri: string, options: FetchOptions): Promise<T> {
const fullUri = this.#config.apis[options.network].batter + uri;
static async fetchUnspentUTXOs({ address, network = 'testnet', type = "spendable", rarity = ["common"] }: FetchUnspentUTXOsOptions): Promise<FetchUnspentUTXOsResponse> {
if(!address) {
throw new Error('Invalid address')
}

const utxos = await rpc[network].call<UTXO[]>('GetUnspents', {
address,
options: {
allowedrarity: rarity,
safetospend: type === "spendable",
}
}, rpc.id)

try {
const response = await _fetch(fullUri, {
method: "POST",
body: JSON.stringify(options.data),
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
});
const data = await response.json();
const { spendableUTXOs, unspendableUTXOs } = utxos.reduce((acc, utxo) => {
if(utxo.inscriptions?.length && !utxo.safeToSpend) {
acc.unspendableUTXOs.push(utxo)
} else {
acc.spendableUTXOs.push(utxo)
}

return data;
} catch (error: any) {
throw new Error(error);
return acc
}, {
spendableUTXOs: [],
unspendableUTXOs: [],
} as Record<string, UTXO[]>)

return {
totalUTXOs: utxos.length,
spendableUTXOs,
unspendableUTXOs
}
}

static async fetchAllInscriptions({ address, network = "testnet" }: FetchInscriptionsOptions) {
if (!address) {
throw new Error("Invalid options provided.");
static async fetchTx({ txId, network = "testnet", ordinals = true, hex = false, witness = true }: FetchTxOptions): Promise<FetchTxResponse> {
if(!txId) {
throw new Error("Invalid txId")
}

const fullUri = `${this.#config.apis[network].batter}/utxo/unspents`;
const payload = {
address,
const tx = await rpc[network].call<Transaction>('GetTransaction', {
txid: txId,
options: {
txhex: true,
notsafetospend: false,
allowedrarity: ["common"]
ord: ordinals,
hex,
witness
}
};

try {
const response = await _fetch(fullUri, {
method: "POST",
body: JSON.stringify(payload),
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
});

const data: UTXO = await response.json();

if (data && data.success && data.rdata && data.rdata.length) {
const inscriptions: InscriptionsEntity[] = [];

data.rdata.forEach((utxo: RdataEntity) => {
if (utxo.inscriptions && utxo.inscriptions.length) {
inscriptions.push(...utxo.inscriptions);
}
});
}, rpc.id);

return inscriptions;
} else {
throw new Error("No data found.");
}
} catch (error: any) {
throw new Error(error.message);
return {
tx,
rawTx: hex && tx.hex ? bitcoin.Transaction.fromHex(tx.hex): undefined
}
}

static async fetchInscriptionDetails({ outpoint, network = "testnet" }: FetchInscriptionDetailsOptions) {
static async fetchInscriptions({ outpoint, network = "testnet" }: FetchInscriptionsOptions) {
if (!outpoint) {
throw new Error("Invalid options provided.");
}

const fullUri = `${this.#config.apis[network].batter}/utxo/inscriptions/${outpoint}`;

try {
const response = await _fetch(fullUri);

const data: InscriptionDetailsEntity = await response.json();

return data;
} catch (error: any) {
throw new Error(error.message);
}
return rpc[network].call<Inscription[]>('GetInscriptions', {
outpoint, network
}, rpc.id);
}
}

export type FetchOptions = {
data: any;
network: Network;
};

export type FetchInscriptionsOptions = {
address: string;
network?: Network;
};

export type FetchInscriptionDetailsOptions = {
outpoint: string;
network?: Network;
};
static async relayTx({ hex, network = "testnet", maxFeeRate }: RelayTxOptions): Promise<string> {
if (!hex) {
throw new Error("Invalid tx hex");
}

export interface UTXO {
success: boolean;
message: string;
rdata?: RdataEntity[] | null;
}
export interface RdataEntity {
n: number;
txHash: string;
blockHash: string;
blockN: number;
sats: number;
scriptPubKey: ScriptPubKey;
txid: string;
value: number;
ordinals?: OrdinalsEntity[] | null;
inscriptions?: InscriptionsEntity[] | null;
safeToSpend: boolean;
confirmation: number;
}
export interface ScriptPubKey {
asm: string;
desc: string;
hex: string;
address: string;
type: string;
}
export interface OrdinalsEntity {
number: number;
decimal: string;
degree: string;
name: string;
height: number;
cycle: number;
epoch: number;
period: number;
offset: number;
rarity: string;
output: string;
start: number;
size: number;
}
export interface InscriptionsEntity {
id: string;
outpoint: string;
owner: string;
fee: number;
height: number;
number: number;
sat: number;
timestamp: number;
media_type: string;
media_size: number;
media_content: string;
meta?: Record<string, any>;
}
if(maxFeeRate && (maxFeeRate < 0 || isNaN(maxFeeRate))) {
throw new Error("Invalid max fee rate")
}

export interface InscriptionDetailsEntity {
success: boolean;
message: string;
rdata?: InscriptionsEntity[] | null;
}
return rpc[network].call<string>('SendRawTransaction', {
hex, maxFeeRate
}, rpc.id)
}
}
3 changes: 2 additions & 1 deletion packages/sdk/src/api/jsonrpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch from "cross-fetch";

import { apiConfig } from "..";
import { apiConfig } from "../config";

class JsonRpc {
constructor(readonly url: string) {}
Expand Down Expand Up @@ -34,6 +34,7 @@ class JsonRpc {
} else {
params = paramsOrId;
}

const response = await fetch(`${this.url}/rpc`, {
method: "POST",
headers: {
Expand Down
42 changes: 42 additions & 0 deletions packages/sdk/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Transaction as BTCTransaction } from 'bitcoinjs-lib'

import { Network } from "../config/types";
import { Rarity } from "../inscription/types";
import { Transaction, UTXO } from "../transactions/types";

export interface FetchUnspentUTXOsOptions {
address: string
network?: Network
type?: "all" | "spendable"
rarity?: Rarity[]
}

export interface FetchUnspentUTXOsResponse {
totalUTXOs: number
spendableUTXOs: UTXO[]
unspendableUTXOs: UTXO[]
}

export interface FetchTxOptions {
txId: string
network?: Network
ordinals?: boolean
hex?: boolean
witness?: boolean
}

export interface FetchTxResponse {
tx: Transaction
rawTx?: BTCTransaction
}

export interface FetchInscriptionsOptions {
outpoint: string
network?: Network
}

export interface RelayTxOptions {
hex: string
maxFeeRate?: number
network?: Network
}
Loading

0 comments on commit 5f61122

Please sign in to comment.