Initial commit
This commit is contained in:
63
packages/http-api/README.md
Normal file
63
packages/http-api/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# @dbis-thirdweb/http-api
|
||||
|
||||
HTTP API client wrapper for Chain 138 with retries, timeouts, and type-safe endpoints.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Client
|
||||
|
||||
```typescript
|
||||
import { createAPIClient } from '@dbis-thirdweb/http-api';
|
||||
|
||||
const client = createAPIClient({
|
||||
apiKey: 'your-api-key',
|
||||
timeout: 30000,
|
||||
retries: 3,
|
||||
});
|
||||
|
||||
// GET request
|
||||
const data = await client.get('/endpoint');
|
||||
|
||||
// POST request
|
||||
const result = await client.post('/endpoint', { data: 'value' });
|
||||
```
|
||||
|
||||
### Chain 138 API Endpoints
|
||||
|
||||
```typescript
|
||||
import { createChain138API } from '@dbis-thirdweb/http-api';
|
||||
|
||||
const api = createChain138API({
|
||||
apiKey: 'your-api-key',
|
||||
});
|
||||
|
||||
// Get chain metadata
|
||||
const metadata = await api.getChainMetadata();
|
||||
|
||||
// Get transaction receipt
|
||||
const receipt = await api.getTransactionReceipt('0x...');
|
||||
|
||||
// Get transaction logs
|
||||
const logs = await api.getTransactionLogs('0x...');
|
||||
|
||||
// Get block
|
||||
const block = await api.getBlock(12345);
|
||||
|
||||
// Get latest block
|
||||
const latestBlock = await api.getLatestBlock();
|
||||
|
||||
// Send transaction (if supported)
|
||||
const txResult = await api.sendTransaction({
|
||||
to: '0x...',
|
||||
value: '1000000000000000000',
|
||||
});
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Standardized client wrapper with retry logic
|
||||
- Exponential backoff for retries
|
||||
- Configurable timeouts
|
||||
- Chain 138 base URL configuration
|
||||
- Type-safe request/response interfaces
|
||||
- Automatic chain ID header injection
|
||||
36
packages/http-api/package.json
Normal file
36
packages/http-api/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@dbis-thirdweb/http-api",
|
||||
"version": "0.1.0",
|
||||
"description": "HTTP API client wrapper for Chain 138",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts",
|
||||
"lint": "eslint src",
|
||||
"test": "echo \"No tests yet\""
|
||||
},
|
||||
"keywords": [
|
||||
"thirdweb",
|
||||
"http-api",
|
||||
"chain-138",
|
||||
"api-client"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dbis-thirdweb/chain": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
175
packages/http-api/src/client.ts
Normal file
175
packages/http-api/src/client.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { chain138 } from '@dbis-thirdweb/chain';
|
||||
|
||||
/**
|
||||
* HTTP API client configuration
|
||||
*/
|
||||
export interface APIClientConfig {
|
||||
/**
|
||||
* Base URL for API requests
|
||||
*/
|
||||
baseURL: string;
|
||||
|
||||
/**
|
||||
* API key (if required)
|
||||
*/
|
||||
apiKey?: string;
|
||||
|
||||
/**
|
||||
* Request timeout in milliseconds
|
||||
* @default 30000
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
/**
|
||||
* Number of retries for failed requests
|
||||
* @default 3
|
||||
*/
|
||||
retries?: number;
|
||||
|
||||
/**
|
||||
* Retry delay in milliseconds (exponential backoff base)
|
||||
* @default 1000
|
||||
*/
|
||||
retryDelay?: number;
|
||||
|
||||
/**
|
||||
* Chain ID (should be Chain 138)
|
||||
*/
|
||||
chainId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration for Chain 138
|
||||
*/
|
||||
export const defaultConfig: APIClientConfig = {
|
||||
baseURL: 'https://api.thirdweb.com',
|
||||
timeout: 30000,
|
||||
retries: 3,
|
||||
retryDelay: 1000,
|
||||
chainId: chain138.chainId,
|
||||
};
|
||||
|
||||
/**
|
||||
* Sleep utility for retry delays
|
||||
*/
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP API client with retry logic and timeout handling
|
||||
*/
|
||||
export class APIClient {
|
||||
private config: APIClientConfig;
|
||||
|
||||
constructor(config: Partial<APIClientConfig> = {}) {
|
||||
this.config = { ...defaultConfig, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP request with retries and timeout
|
||||
*/
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
const url = `${this.config.baseURL}${endpoint}`;
|
||||
const timeout = this.config.timeout || 30000;
|
||||
|
||||
// Add API key to headers if provided
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers as Record<string, string>),
|
||||
};
|
||||
|
||||
if (this.config.apiKey) {
|
||||
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
||||
}
|
||||
|
||||
// Add chain ID to headers if needed
|
||||
headers['x-chain-id'] = this.config.chainId.toString();
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
let lastError: Error | null = null;
|
||||
const maxRetries = this.config.retries || 3;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data as T;
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
lastError = error as Error;
|
||||
|
||||
// Don't retry on abort (timeout)
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw new Error(`Request timeout after ${timeout}ms`);
|
||||
}
|
||||
|
||||
// Don't retry on last attempt
|
||||
if (attempt < maxRetries) {
|
||||
const delay = this.config.retryDelay! * Math.pow(2, attempt); // Exponential backoff
|
||||
await sleep(delay);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error('Request failed after retries');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET request
|
||||
*/
|
||||
async get<T>(endpoint: string): Promise<T> {
|
||||
return this.request<T>(endpoint, { method: 'GET' });
|
||||
}
|
||||
|
||||
/**
|
||||
* POST request
|
||||
*/
|
||||
async post<T>(endpoint: string, body?: unknown): Promise<T> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'POST',
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT request
|
||||
*/
|
||||
async put<T>(endpoint: string, body?: unknown): Promise<T> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'PUT',
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE request
|
||||
*/
|
||||
async delete<T>(endpoint: string): Promise<T> {
|
||||
return this.request<T>(endpoint, { method: 'DELETE' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create API client for Chain 138
|
||||
*/
|
||||
export function createAPIClient(config?: Partial<APIClientConfig>): APIClient {
|
||||
return new APIClient(config);
|
||||
}
|
||||
119
packages/http-api/src/endpoints.ts
Normal file
119
packages/http-api/src/endpoints.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { APIClient, createAPIClient } from './client';
|
||||
|
||||
/**
|
||||
* Chain metadata response
|
||||
*/
|
||||
export interface ChainMetadata {
|
||||
chainId: number;
|
||||
name: string;
|
||||
nativeCurrency: {
|
||||
name: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
};
|
||||
rpc: string[];
|
||||
explorers?: Array<{
|
||||
name: string;
|
||||
url: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction receipt response
|
||||
*/
|
||||
export interface TransactionReceipt {
|
||||
blockHash: string;
|
||||
blockNumber: number;
|
||||
contractAddress: string | null;
|
||||
cumulativeGasUsed: string;
|
||||
effectiveGasPrice: string;
|
||||
from: string;
|
||||
gasUsed: string;
|
||||
logs: Array<{
|
||||
address: string;
|
||||
topics: string[];
|
||||
data: string;
|
||||
}>;
|
||||
logsBloom: string;
|
||||
status: number;
|
||||
to: string | null;
|
||||
transactionHash: string;
|
||||
transactionIndex: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block response
|
||||
*/
|
||||
export interface BlockResponse {
|
||||
number: number;
|
||||
hash: string;
|
||||
parentHash: string;
|
||||
timestamp: number;
|
||||
transactions: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Chain 138 API endpoints wrapper
|
||||
*/
|
||||
export class Chain138API {
|
||||
constructor(private client: APIClient) {}
|
||||
|
||||
/**
|
||||
* Fetch chain metadata for Chain 138
|
||||
*/
|
||||
async getChainMetadata(): Promise<ChainMetadata> {
|
||||
return this.client.get<ChainMetadata>('/v1/chains/138');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction receipt
|
||||
*/
|
||||
async getTransactionReceipt(txHash: string): Promise<TransactionReceipt> {
|
||||
return this.client.get<TransactionReceipt>(`/v1/chains/138/transactions/${txHash}/receipt`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction logs
|
||||
*/
|
||||
async getTransactionLogs(txHash: string): Promise<TransactionReceipt['logs']> {
|
||||
const receipt = await this.getTransactionReceipt(txHash);
|
||||
return receipt.logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block by number
|
||||
*/
|
||||
async getBlock(blockNumber: number): Promise<BlockResponse> {
|
||||
return this.client.get<BlockResponse>(`/v1/chains/138/blocks/${blockNumber}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest block
|
||||
*/
|
||||
async getLatestBlock(): Promise<BlockResponse> {
|
||||
return this.client.get<BlockResponse>('/v1/chains/138/blocks/latest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send transaction via API (if supported)
|
||||
*/
|
||||
async sendTransaction(txData: {
|
||||
to: string;
|
||||
value?: string;
|
||||
data?: string;
|
||||
gasLimit?: string;
|
||||
gasPrice?: string;
|
||||
}): Promise<{ txHash: string }> {
|
||||
return this.client.post<{ txHash: string }>('/v1/chains/138/transactions', txData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Chain 138 API client
|
||||
*/
|
||||
export function createChain138API(
|
||||
config?: Parameters<typeof createAPIClient>[0]
|
||||
): Chain138API {
|
||||
const client = createAPIClient(config);
|
||||
return new Chain138API(client);
|
||||
}
|
||||
2
packages/http-api/src/index.ts
Normal file
2
packages/http-api/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './client';
|
||||
export * from './endpoints';
|
||||
12
packages/http-api/tsconfig.json
Normal file
12
packages/http-api/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"references": [
|
||||
{ "path": "../chain" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user