UNPKG

@ln-markets/sdk

Version:

TypeScript SDK for LNMarkets API v2

135 lines (109 loc) 3.58 kB
import camelcaseKeys from 'camelcase-keys' import { createHmac } from 'node:crypto' import { URL, URLSearchParams } from 'node:url' import { fetch } from 'undici' import { createRouter } from './routes/index.js' import { getHostname } from './utils.js' export type RestClient = ReturnType<typeof createRestClient> export type RestFetcher = <T = void>(options: RequestOptions) => Promise<T> type RequestOptions = { body?: Record<string, boolean | number | string> method: string path: string query?: Record<string, boolean | number | string> requireAuth?: boolean } type RestOptions = { headers?: Record<string, string> key?: string network?: 'mainnet' | 'testnet' passphrase?: string secret?: string } class HttpError extends Error { status: number statusText: string constructor(status: number, statusText: string, message: string) { super(message) this.name = 'HttpError' this.status = status this.statusText = statusText } } export const createRestClient = (options: RestOptions = {}) => { const { headers = {}, key = process.env.LNM_API_KEY, network = process.env.LNM_API_NETWORK ?? 'mainnet', passphrase = process.env.LNM_API_PASSPHRASE, secret = process.env.LNM_API_SECRET, } = options const hostname = process.env.LNM_API_HOSTNAME ?? getHostname(network) const request = async <T>(options: RequestOptions): Promise<T> => { const { body, method, path, query, requireAuth } = options if (requireAuth) { if (!key) { throw new Error('You need an API key to use an authenticated route') } else if (!secret) { throw new Error('You need an API secret to use an authenticated route') } else if (!passphrase) { throw new Error( 'You need an API passphrase to use an authenticated route' ) } const timestamp = Date.now() const payload = /^(GET|DELETE)$/.test(method) ? new URLSearchParams( Object.fromEntries( Object.entries(query ?? {}).map(([key, value]) => [ key, JSON.stringify(value), ]) ) ).toString() : JSON.stringify(body ?? {}) const signature = createHmac('sha256', secret) .update(`${timestamp}${method}/v2${path}${payload}`) .digest('base64') Object.assign(headers, { 'LNM-ACCESS-KEY': key, 'LNM-ACCESS-PASSPHRASE': passphrase, 'LNM-ACCESS-SIGNATURE': signature, 'LNM-ACCESS-TIMESTAMP': timestamp, }) } const url = new URL(`https://${hostname}/v2${path}`) if (query) { for (const [key, value] of Object.entries(query)) { url.searchParams.append(key, value.toString()) } } if (body && /^(POST|PUT)$/.test(method)) { Object.assign(headers, { 'Content-Type': 'application/json', }) } const payload = /^(POST|PUT)$/.test(method) ? JSON.stringify(body) : undefined const response = await fetch(url, { body: payload, headers, method, mode: 'cors', }) if (response.ok) { if (response.headers.get('content-type')?.includes('application/json')) { return response .json() .then((value) => camelcaseKeys(value as Record<string, unknown>, { deep: true }) ) as T } return response.text() as T } const text = await response.text() throw new HttpError(response.status, response.statusText, text) } return createRouter(request) }