Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,588 changes: 1,588 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
"files": ["dist"],
"scripts": {
"build": "bun build ./src/index.ts --outdir ./dist --target node",
"test": "bun test",
"test": "bun test ./src/test/**/*.test.ts",
"format": "prettier --write 'src/**/*.ts'",
"lint": "eslint . && tsc -b",
"prepublishOnly": "bun run build"
},
"imports": {
"#src/*": "./src/*",
"#schemas/*": "./src/schemas/*",
"#types/*": "./src/types/*"
},
"dependencies": {
"typescript-eslint": "^8.22.0",
"zod": "^3.24.1"
Expand Down
10 changes: 10 additions & 0 deletions src/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const endpoints = {
address: (address: string) => `/address/${address}`,
block: (heightOrHash: number | string) => `/block/${heightOrHash}`,
blockcount: '/blockcount',
blockhashLatest: '/blockhash',
blockhashByHeight: (height: number) => `/blockhash/${height}`,
blockheight: '/blockheight',
blocks: '/blocks',
blocktime: '/blocktime',
} as const;
177 changes: 0 additions & 177 deletions src/index.test.ts

This file was deleted.

114 changes: 67 additions & 47 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,16 @@
import { z } from 'zod';
import { endpoints } from 'src/endpoints';
import {
BlockSchema,
BlockHashSchema,
AddressInfoSchema,
BlocksResponseSchema,
} from 'schemas';
import type { Block, BlockHash, AddressInfo, BlocksResponse } from 'types';

export const InputSchema = z.object({
previous_output: z.string(),
script_sig: z.string(),
sequence: z.number().int().nonnegative(),
witness: z.array(z.string()),
});

export const OutputSchema = z.object({
value: z.number().int().nonnegative(),
script_pubkey: z.string(),
});

export const TransactionSchema = z.object({
version: z.number().int().nonnegative(),
lock_time: z.number().int().nonnegative(),
input: z.array(InputSchema),
output: z.array(OutputSchema),
});

const isHexString = (str: string) => /^[0-9a-fA-F]+$/.test(str);

export const blockHashSchema = z
.string()
.length(64, 'Block hash must be exactly 64 characters long')
.refine(
isHexString,
'Block hash must contain only hexadecimal characters (0-9, a-f, A-F)',
);

export type BlockHash = z.infer<typeof blockHashSchema>;

export const BlockSchema = z.object({
best_height: z.number().int().nonnegative(),
hash: blockHashSchema,
height: z.number().int().nonnegative(),
inscriptions: z.array(z.string()),
runes: z.array(z.string()),
target: z.string(),
transactions: z.array(TransactionSchema),
});

export type Block = z.infer<typeof BlockSchema>;
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string };

export class OrdClient {
private headers: HeadersInit;
Expand All @@ -56,14 +25,65 @@ export class OrdClient {
};
}

async getBlock(heightOrHash: number | BlockHash): Promise<Block> {
const response = await fetch(`${this.baseUrl}/block/${heightOrHash}`, {
private async fetch<T extends z.ZodType>(
endpoint: string,
schema: T,
): Promise<z.infer<T>> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
headers: this.headers,
});

if (!response.ok) {
throw new Error();
throw new Error(`API request failed: ${response.statusText}`);
}

const text = await response.text();
const result = schema.safeParse(text);

if (!result.success) {
try {
const json = JSON.parse(text);
const jsonResult = schema.safeParse(json);
if (jsonResult.success) {
return jsonResult.data;
}
} catch {}

throw new Error(`Validation error: ${result.error.message}`);
}
const data = await response.json();
return BlockSchema.parse(data);

return result.data;
}

async getAddressInfo(address: string): Promise<AddressInfo> {
return this.fetch(endpoints.address(address), AddressInfoSchema);
}

async getBlock(heightOrHash: number | BlockHash): Promise<Block> {
return this.fetch(endpoints.block(heightOrHash), BlockSchema);
}

async getBlockCount(): Promise<number> {
return this.fetch(endpoints.blockcount, z.number().int().nonnegative());
}

async getBlockHashByHeight(height: number): Promise<BlockHash> {
return this.fetch(endpoints.blockhashByHeight(height), BlockHashSchema);
}

async getLatestBlockHash(): Promise<BlockHash> {
return this.fetch(endpoints.blockhashLatest, BlockHashSchema);
}

async getLatestBlockHeight(): Promise<number> {
return this.fetch(endpoints.blockheight, z.number().int().nonnegative());
}

async getLatestBlocks(): Promise<BlocksResponse> {
return this.fetch(endpoints.blocks, BlocksResponseSchema);
}

async getLatestBlockTime(): Promise<number> {
return this.fetch(endpoints.blocktime, z.number().int());
}
}
10 changes: 10 additions & 0 deletions src/schemas/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { z } from 'zod';

export const RuneBalanceSchema = z.tuple([z.string(), z.string(), z.string()]);

export const AddressInfoSchema = z.object({
outputs: z.array(z.string()),
inscriptions: z.array(z.string()),
sat_balance: z.number().int().nonnegative(),
runes_balances: z.array(RuneBalanceSchema),
});
28 changes: 28 additions & 0 deletions src/schemas/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { z } from 'zod';
import { TransactionSchema } from 'schemas';

const isHexString = (str: string) => /^[0-9a-fA-F]+$/.test(str);

export const BlockHashSchema = z
.string()
.length(64, 'Block hash must be exactly 64 characters long')
.refine(
isHexString,
'Block hash must contain only hexadecimal characters (0-9, a-f, A-F)',
);

export const BlockSchema = z.object({
best_height: z.number().int().nonnegative(),
hash: BlockHashSchema,
height: z.number().int().nonnegative(),
inscriptions: z.array(z.string()),
runes: z.array(z.string()),
target: z.string(),
transactions: z.array(TransactionSchema),
});

export const BlocksResponseSchema = z.object({
last: z.number().int().nonnegative(),
blocks: z.array(BlockHashSchema),
featured_blocks: z.record(BlockHashSchema, z.array(z.string())),
});
3 changes: 3 additions & 0 deletions src/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from 'schemas/transaction';
export * from 'schemas/block';
export * from 'schemas/address';
Loading