@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
230 lines (215 loc) • 8.07 kB
text/typescript
import {BitcoinTokens, Token} from "../types/Token";
import {TokenAmount, toTokenAmount} from "../types/TokenAmount";
import {LNURLPay, LNURLPayParamsWithUrl} from "../types/lnurl/LNURLPay";
import {LNURLWithdraw, LNURLWithdrawParamsWithUrl} from "../types/lnurl/LNURLWithdraw";
import {parseApiInput} from "./ApiParser";
import {Swapper} from "../swapper/Swapper";
/**
* Unified amount type for all API responses
*
* @category API
*/
export type ApiAmount = {
/** Decimal format of the amount, e.g. "1.5" */
amount: string;
/** Raw base units as string, e.g. "1500000000000000000" */
rawAmount: string;
/** Token decimals, e.g. 18 */
decimals: number;
/** Token ticker, e.g. "STRK" */
symbol: string;
/** Chain identifier, e.g. "STARKNET", "BITCOIN", "LIGHTNING" */
chain: string;
}
/**
* Serializable token representation for API responses
*
* @category API
*/
export type ApiToken = {
/** Canonical token identifier accepted by the API, e.g. "BITCOIN-BTC", "LIGHTNING-BTC", "STARKNET-STRK" */
id: string;
/** Chain identifier, e.g. "STARKNET", "BITCOIN", "LIGHTNING" */
chainId: string;
/** Token ticker, e.g. "STRK" */
ticker: string;
/** Full token name */
name: string;
/** Decimal places of the token */
decimals: number;
/** Token contract address, or empty string for BTC on Bitcoin/Lightning */
address: string;
}
/**
* Serializable LNURL-pay representation for API responses
*
* @category API
*/
export type ApiLNURLPay = {
/** Marks the LNURL payload as an LNURL-pay response. */
type: "pay";
/** Minimum payable amount supported by the LNURL-pay endpoint. */
min: ApiAmount;
/** Maximum payable amount supported by the LNURL-pay endpoint. */
max: ApiAmount;
/** Maximum comment length accepted by the LNURL-pay endpoint. */
commentMaxLength: number;
/** Short human-readable description of the payee, when provided by the LNURL service. */
shortDescription?: string;
/** Longer human-readable description of the payee, when provided by the LNURL service. */
longDescription?: string;
/** Optional icon for the payee, usually encoded as a data URL. */
icon?: string;
/** Raw LNURL-pay metadata and callback parameters. */
params: LNURLPayParamsWithUrl;
}
/**
* Serializable LNURL-withdraw representation for API responses
*
* @category API
*/
export type ApiLNURLWithdraw = {
/** Marks the LNURL payload as an LNURL-withdraw response. */
type: "withdraw";
/** Minimum withdrawable amount supported by the LNURL-withdraw endpoint. */
min: ApiAmount;
/** Maximum withdrawable amount supported by the LNURL-withdraw endpoint. */
max: ApiAmount;
/** Raw LNURL-withdraw metadata and callback parameters. */
params: LNURLWithdrawParamsWithUrl;
}
/**
* Serializable LNURL representation for API responses
*
* @category API
*/
export type ApiLNURL = ApiLNURLPay | ApiLNURLWithdraw;
/**
* Converts a TokenAmount to the serializable ApiAmount format
*
* @category API
*/
export function toApiAmount(tokenAmount: TokenAmount): ApiAmount {
return {
amount: tokenAmount.amount,
rawAmount: tokenAmount.rawAmount != null ? tokenAmount.rawAmount.toString() : "0",
decimals: tokenAmount.token.decimals,
symbol: tokenAmount.token.ticker,
chain: tokenAmount.token.chainId
};
}
/**
* Converts a Token to the serializable ApiToken format
*
* @category API
*/
export function toApiToken(token: Token): ApiToken {
return {
id: `${token.chainId}-${token.ticker}`,
chainId: token.chainId,
ticker: token.ticker,
name: token.name,
decimals: token.decimals,
address: token.address
};
}
/**
* Converts LNURL data to the serializable API format
*
* @category API
*/
export function toApiLNURL(lnurl: LNURLPay | LNURLWithdraw, swapper: Swapper<any>): ApiLNURL {
if(lnurl.type === "pay") {
return {
type: "pay",
min: toApiAmount(toTokenAmount(lnurl.min, BitcoinTokens.BTCLN, swapper.prices)),
max: toApiAmount(toTokenAmount(lnurl.max, BitcoinTokens.BTCLN, swapper.prices)),
commentMaxLength: lnurl.commentMaxLength,
...(lnurl.shortDescription != null ? {shortDescription: lnurl.shortDescription} : {}),
...(lnurl.longDescription != null ? {longDescription: lnurl.longDescription} : {}),
...(lnurl.icon != null ? {icon: lnurl.icon} : {}),
params: lnurl.params
};
}
return {
type: "withdraw",
min: toApiAmount(toTokenAmount(lnurl.min, BitcoinTokens.BTCLN, swapper.prices)),
max: toApiAmount(toTokenAmount(lnurl.max, BitcoinTokens.BTCLN, swapper.prices)),
params: lnurl.params
};
}
/**
* Maps a TypeScript type to its schema type string representation
*
* @category API
*/
type TypeToSchemaType<T> =
NonNullable<T> extends string ? "string" :
NonNullable<T> extends number ? "number" :
NonNullable<T> extends bigint ? "bigint" :
NonNullable<T> extends boolean ? "boolean" :
NonNullable<T> extends any[] ? "array" :
"object";
/**
* Schema definition for a single API input field.
*
* @category API
*/
export type InputSchemaField<T = unknown> = {
/** Primitive schema type inferred from the TypeScript field type. */
type: TypeToSchemaType<T>;
/** Whether the field is required by the endpoint input parser. */
required: boolean;
/** Human-readable description of the field exposed by the API schema. */
description: string;
/** Nested schema properties for object-like fields. */
properties?: T extends readonly any[] ? never :
T extends object ? {
[K in keyof T]-?: InputSchemaField<T[K]>;
} : never; // Specifies nested object properties
/** Schema definition for array items when the field is an array. */
items?: T extends readonly (infer U)[] ? InputSchemaField<U> : never; // Specifies type of the array items
/** Enumerated allowed values for string, number, or bigint fields when constrained. */
allowedValues?: NonNullable<T> extends string | number | bigint ? NonNullable<T>[] : never; // An array of allowed values for a given field
};
/**
* Schema definition describing the accepted input shape for an API endpoint.
*
* @category API
*/
export type InputSchema<TInput> = {
[K in keyof TInput]-?: InputSchemaField<TInput[K]>;
};
/**
* Typed API endpoint definition for framework-agnostic integration
*
* @category API
*/
export type ApiEndpoint<TInput, TOutput, Type extends "GET" | "POST"> = {
/** HTTP method used by the endpoint. */
type: Type;
/** Human-readable description of what this endpoint does, written for AI agent consumption. */
description: string;
/** Structured schema describing the accepted input payload. */
inputSchema: InputSchema<TInput>;
/** Typed endpoint implementation that receives already-validated input. */
callback: (input: TInput, abortSignal?: AbortSignal) => Promise<TOutput>;
/** Raw endpoint implementation that parses unknown input into the typed callback. */
callbackRaw: (input: unknown, abortSignal?: AbortSignal) => Promise<TOutput>;
}
export function createApiEndpoint<TInput, TOutput, Type extends "GET" | "POST">(
type: Type,
description: string,
callback: (input: TInput, abortSignal?: AbortSignal) => Promise<TOutput>,
inputSchema: InputSchema<TInput>
): ApiEndpoint<TInput, TOutput, Type> {
return {
type,
description,
callback,
inputSchema,
callbackRaw: (input, abortSignal?: AbortSignal) => {
return callback(parseApiInput(inputSchema, input), abortSignal);
}
}
}