Root: @c8ntinuum/infinity-interop-sdk
Browser-safe: runs in a browser and in Node 20+.
Client
| Export | What it is |
|---|---|
createInteropClient({ transport, subject?, catalogTtlMs? }) | the client: every flow on this site, over one transport. subject is the X-Infinity-Subject sent by default on the seven routes that read it; a per-call subject overrides it. Without either, the transport’s own goes out: hubTransport’s subject (default), or projectTransport’s visitor id |
proxyTransport({ url, headers?, credentials?, fetch?, timeouts? }) | the browser’s transport through your proxy. headers carry your session, never a hub credential. From an https page it refuses, before any call, an http:// url other than localhost (the browser would block it as mixed content). Waits 15 s on reads, 30 s on quotes, 35 s on submissions |
projectTransport({ baseUrl, projectId, subject?, apiPrefix?, fetch?, timeouts? }) | the browser’s transport with your public project id (c8n_pk_…), straight to the gateway: x-project-id on every call, and X-Infinity-Subject on the seven routes that require it. baseUrl is the gateway’s origin (a trailing /v1 is accepted). The subject is subject, or else one random id per browser and project (visitor- and a UUID, kept in localStorage): it labels a visitor and proves nothing. It refuses a secret key and a route outside PROJECT_ROUTES, before any call |
PROJECT_ROUTES | the routes open to a project id: every business route but GET /orders (the catalog, the three quote routes, POST /order, POST /order/openfor, both status views and the four on-chain views) |
createHubApi(transport, { subject? }) | one typed method per hub route, answers unchanged (below); subject is the default X-Infinity-Subject, as on the client |
CallOptions | subject?, signal?, timeoutMs? on any call. subject goes out as X-Infinity-Subject on the seven routes that read it, and must match the hub’s rule (^[A-Za-z0-9._@+~-]{1,128}$) |
Transport, TransportRequest, TransportResult, Timeouts, FetchLike | the transport contract, to write your own |
Quotes and checks
| Export | What it is |
|---|---|
Quote, toQuote, QuoteClock | a quote with its lane, amounts and countdown; the clock that anchors countdowns on first receipt |
quoteTimeLeftMs(quote, now?) | milliseconds left, by the client’s clock |
baseQuoteRequest(request) | a prepared quote’s request, without the prepare-only fields |
MAX_QUOTE_VALIDITY_MS | 30 000 |
detectLane(response) | the lane a quote answer discloses; never throws (unsupported, with a reason) |
disclosureKindOf, signPayloadShapeOf, typedDataOf, parseExecutionTx | readers for a disclosure’s parts |
vetQuote(quote, context), VetError, VetReport | the pre-signing checks |
PERMIT2_ADDRESS, MAX_TOKEN_IN_SLIPPAGE_BPS, TOKEN_IN_ROUNDING_UNITS | the checks’ constants |
CatalogIndex | the catalog over one snapshot |
Execution
| Export | What it is |
|---|---|
executeQuote(context, options) | what client.executeQuote runs (Executing orders) |
ReviewRequiredError, isQuoteNotWorse(fresh, reviewed) | the re-quote rule |
DEFAULT_MIN_SIGNING_TIME_MS | 15 000 |
ensurePermit2Allowance, ensureExactAllowance, readAllowance | the approval steps, on their own |
evmEscrowEnvelope(signature) | a Permit2 order’s signature as the hub takes it: 0x00 followed by the 65-byte signature |
eip3009Envelope(signature) | an EIP-3009 signature as the hub takes it: the raw 65 bytes |
solanaEnvelope(signature, publicKey) | a Solana order’s signature as the hub takes it: 0x00 ‖ signature ‖ public key, base64 |
signSolanaOrderMessage(wallet, message, strategy) | signs a Solana order’s message with a strategy |
verifyEd25519(publicKey, signature, message) | checks an Ed25519 signature with Web Crypto (null where the runtime lacks Ed25519) |
Orders
| Export | What it is |
|---|---|
submitOpenFor(api, body, options?) | POST /order/openfor with the lost-answer recovery. Its options, like those of trackOrder, waitForOrder, walkOrders and listAllOrders, take CallOptions, subject included |
deriveHubOrderId(quoteId) | the hub’s durable alias for the order submitted against a quote (below) |
isLostSubmission(error) | whether an error says nothing about the submission’s outcome (a transport failure, or an availability or busy code, the gateway’s included) |
trackOrder, waitForOrder, waitForHubOrder | following an order (Tracking) |
TERMINAL_ORDER_STATES, isTerminalOrderState, orderOutcome | the aggregator’s terminal states: settled, finalized, failed, refunded |
isHubOrderSettled, hubOrderOutcome | the same for the open flow’s lifecycle view |
walkOrders, listAllOrders, summarizeOrder, sortOrdersNewestFirst | order history |
ORDERS_PAGE_LIMIT | 500 |
Refunds
| Export | What it is |
|---|---|
checkEvmRefund, prepareEvmRefund, verifyEvmRefundOnchain, refundEvmOrder | EVM refunds |
prepareSolanaRefund, refundSolanaOrder, solanaRefundRefusal | Solana refunds |
ESCROW_STATUS | the settler’s orderStatus values: none 0, deposited 1, claimed 2, refunded 3 |
SOLANA_REFUND_DISCRIMINATOR, SPL_TOKEN_PROGRAM | what the Solana refund check compares against |
Wallets
| Export | What it is |
|---|---|
evmWalletFromEip1193(provider, options?), jsonRpcProvider(url) | an EIP-1193 wallet; a read provider from a URL (Wallets) |
EvmWallet, SolanaWallet, Wallets | the adapter interfaces |
typedDataJson(typedData) | the JSON the wallet signs: the disclosed members unchanged, plus EIP712Domain when absent |
Errors
InteropError, HubError (with status, isCredentialRefused and isClientBug),
HubUnavailableError, ProxyError, WalletError, QuoteExpiredError, OutcomeUnknownError,
RefundError, isAbortError, classifyHubCode, CLIENT_BUG_CODES, adviseError,
adviseHubError and DEFAULT_ERROR_MESSAGES: see Errors.
Bytes, units and addresses
| Export | What it is |
|---|---|
toBaseUnits(text, decimals), formatUnits(amount, decimals), isWireAmount(value) | readable amounts ↔ base units (bigint, or the wire’s decimal strings) |
parseAssetRef, familyOfChainDomain, evmChainIdOf | <chainDomain>:<address> references and chain domains |
sameAddress(family, a, b), isEvmAddress, evmAddressOf, solanaKeyBytes, toBase58Key, toHexKey | address comparison and conversion, per chain family |
decodeStandardOrder, decodeProxyCall, encodeApprove, encodeAllowance, SELECTORS, PROXY_SELECTORS, MAX_UINT256 | the EVM encodings the SDK reads and writes |
parseSolanaMessage, parseSolanaTransaction, unsignedTransaction, feePayerOf, requiredSignersOf, instructionFactsOf, encodeCompactU16 | Solana message and transaction bytes |
base58Encode/Decode, bytesToBase64, base64ToBytes, bytesToHex, hexToBytes, sha256, sha256Hex, newRequestId | encodings, hashing, and a UUID v4 (a fresh preparationId, for one) |
The wire contract
| Export | What it is |
|---|---|
ROUTES, RouteName, buildRoutePath, matchRoute | every hub route: method, path under /v1, timeout class, answer type, and whether the hub reads X-Infinity-Subject there (subject) |
API_PREFIX | /v1 |
BROWSER_ROUTES | the routes a browser may reach through a proxy: every business route, never the OpenAPI document |
DEFAULT_TIMEOUTS, TimeoutClass | read 12 s, quote 25 s, submit 15 s: a little longer than the gateway’s 10 s and 20 s |
the wire types (QuoteRequest, QuoteResponse, OpenForRequest, OrderListEntry, OnchainFill…) | the hub’s request and answer shapes, with the OpenAPI-generated paths, components and operations |
Server: @c8ntinuum/infinity-interop-sdk/server
For your backend: it holds your secret key.
| Export | What it is |
|---|---|
hubTransport({ baseUrl, apiKey, subject?, apiPrefix?, fetch?, timeouts?, dangerouslyAllowBrowser? }) | the server’s transport: your secret key (c8n_sk_ and 43 characters) in x-api-key on every call, under /v1. No sign-in, no session. baseUrl is the hub’s origin (a trailing /v1 is accepted). subject (default default) is the X-Infinity-Subject of the calls that name none of your users, on the seven routes where the hub requires one. It refuses a value that is not a secret key (a project id, an old cif_… key, a key of the wrong shape) with a message that says so, and refuses to start in a web page unless dangerouslyAllowBrowser: true: for a test key on a hub that accepts browser calls (the hub’s gateway does not); it then warns in the console |
HubTransport, HubTransportOptions | its types; transport.baseUrl is the hub’s origin |
createInteropProxy(options), toNodeListener(handler) | the partner proxy, and its Node http adapter |
InteropProxyOptions | transport, resolveUser, basePath?, userScope? ("ownership", the default, or "subject"), subjectOf? (the subject for a user, default user.id), routes?, ownership?, enforceWalletOwnership?, filterOrdersByWallet?, recoverOpenFor?, openForRetryDelayMs?, publicCatalog?, catalogTtlMs?, maxBodyBytes?, historyMaxPages?, beforeForward?, onError? (Keeping your users apart) |
InteropProxy, ProxyContext, ProxyUser, FetchHandler | the handler (with checkSubjectScoping(), the subject canary on demand), what beforeForward and onError receive, the user resolveUser returns |
verifySubjectScoping(api), SubjectScopingCheck | the subject canary: lists orders for a never-used subject, which must come back empty and echo it; { ok, detail } |
memoryOwnershipStore(options?), OwnershipStore | which user owns which quote and order: the proxy’s own record (userScope: "ownership", and GET /status/{orderId} either way) |
createInteropClient | also exported here, for scripts that use only this entry point |
EVM: @c8ntinuum/infinity-interop-sdk/evm
Needs viem (2.21 or later).
| Export | What it is |
|---|---|
evmWalletFromViem({ walletClient, publicClient?, chains? }) | an EvmWallet over a viem or wagmi wallet client |
evmWalletFromAccount({ account, rpcUrls, chainInstances? }) | an EvmWallet over a viem local account, sending through your RPCs |
Solana: @c8ntinuum/infinity-interop-sdk/solana
No dependencies.
| Export | What it is |
|---|---|
solanaWalletFromStandard(wallet, account, { chain, rpc? }) | a SolanaWallet over a Wallet Standard wallet |
solanaWalletFromKeypair({ secretKey, rpc? }) | a SolanaWallet over a key you hold (async) |
solanaRpc(url) | a minimal JSON-RPC client: broadcast, confirmation, blockhash, genesis |
HubApi
client.api (or createHubApi(transport)) has one method per hub route. Answers come back as the
hub sent them, and every method takes CallOptions last.
| Method | Route |
|---|---|
getCatalog() | GET /catalog |
quote(body) | POST /quote |
previewQuotes(body) | POST /quote/preview |
prepareQuote(body) | POST /quote/prepare |
openFor(body) | POST /order/openfor |
createOrder(body) | POST /order |
getOrderStatus(orderId) | GET /order/{orderId}/status |
getHubOrderStatus(orderId) | GET /status/{orderId} |
listOrders({ since?, limit? }) | GET /orders: the orders of the call’s subject; under hubTransport’s shared one (default), every order made that way, so never hand it to a browser unfiltered; with a user’s subject, only that user’s |
getOnchainFill(onchainOrderId) | GET /onchain/{orderId}/fill |
getOnchainOrder(onchainOrderId) | GET /onchain/{orderId}/order |
getOnchainRefund(onchainOrderId, { unwrap? }) | GET /onchain/{orderId}/refund |
findOrderByTx(chainDomain, txHash) | GET /onchain/tx/{chainDomain}/{txHash}/order |
call(route, { params?, query?, body? }) | any route by name, returning { data, requestId }; call("openapi") returns GET /openapi.yaml as text, with hubTransport only |
call keeps the answer’s requestId, which the typed methods drop. Use it when you want to log it,
for example next to a quote.
deriveHubOrderId
const hubOrderId = await deriveHubOrderId(quote.quoteId);
// hex(sha256("infinity:v1:order:" + quoteId)): 64 lowercase hex characters, no 0x
GET /order/{id}/status answers it like the
aggregator’s order id. It is how you find an order whose submission answer was lost, without
asking the user to sign again.