@totalis/hip4-client 0.0.0-stage → 0.65.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +1541 -0
  2. package/LICENSE +202 -0
  3. package/README.md +143 -2
  4. package/dist/api-keys.d.ts +118 -0
  5. package/dist/api-keys.js +104 -0
  6. package/dist/audience-contract.d.ts +52 -0
  7. package/dist/audience-contract.js +1 -0
  8. package/dist/base.d.ts +41 -0
  9. package/dist/base.js +60 -0
  10. package/dist/client.d.ts +16 -0
  11. package/dist/client.js +15 -0
  12. package/dist/controller.d.ts +50 -0
  13. package/dist/controller.js +60 -0
  14. package/dist/errors.d.ts +3 -0
  15. package/dist/errors.js +3 -0
  16. package/dist/flow-errors.d.ts +61 -0
  17. package/dist/flow-errors.js +70 -0
  18. package/dist/generated/account-read.d.ts +2009 -0
  19. package/dist/generated/account-read.js +1 -0
  20. package/dist/generated/api-keys.d.ts +1303 -0
  21. package/dist/generated/api-keys.js +1 -0
  22. package/dist/generated/browse.d.ts +938 -0
  23. package/dist/generated/browse.js +1 -0
  24. package/dist/generated/client.d.ts +73 -0
  25. package/dist/generated/client.js +1 -0
  26. package/dist/generated/errors.d.ts +404 -0
  27. package/dist/generated/errors.js +1342 -0
  28. package/dist/generated/maker.d.ts +47 -0
  29. package/dist/generated/maker.js +1 -0
  30. package/dist/generated/openapi.d.ts +8044 -0
  31. package/dist/generated/openapi.js +1 -0
  32. package/dist/generated/operator.d.ts +7 -0
  33. package/dist/generated/operator.js +1 -0
  34. package/dist/generated/realtime.d.ts +1416 -0
  35. package/dist/generated/realtime.js +1 -0
  36. package/dist/generated/vault-abi.d.ts +39 -0
  37. package/dist/generated/vault-abi.js +56 -0
  38. package/dist/generated/version.d.ts +23 -0
  39. package/dist/generated/version.js +143 -0
  40. package/dist/hypercore.d.ts +111 -0
  41. package/dist/hypercore.js +132 -0
  42. package/dist/maker.d.ts +205 -0
  43. package/dist/maker.js +608 -0
  44. package/dist/money.d.ts +59 -0
  45. package/dist/money.js +66 -0
  46. package/dist/operator.d.ts +4 -0
  47. package/dist/operator.js +4 -0
  48. package/dist/pagination.d.ts +20 -0
  49. package/dist/pagination.js +27 -0
  50. package/dist/partner.d.ts +59 -0
  51. package/dist/partner.js +57 -0
  52. package/dist/realtime.d.ts +19 -0
  53. package/dist/realtime.js +10 -0
  54. package/dist/request.d.ts +112 -0
  55. package/dist/request.js +187 -0
  56. package/dist/signer.d.ts +51 -0
  57. package/dist/signer.js +90 -0
  58. package/dist/stream.d.ts +133 -0
  59. package/dist/stream.js +318 -0
  60. package/dist/totalis.d.ts +656 -0
  61. package/dist/totalis.js +1160 -0
  62. package/dist/typed-data.d.ts +330 -0
  63. package/dist/typed-data.js +340 -0
  64. package/dist/ui.d.ts +54 -0
  65. package/dist/ui.js +77 -0
  66. package/dist/wallet-session.d.ts +45 -0
  67. package/dist/wallet-session.js +61 -0
  68. package/examples/browser-wallet.html +69 -0
  69. package/examples/browser-wallet.mjs +57 -0
  70. package/examples/node-bot.mjs +57 -0
  71. package/examples/onboard.mjs +132 -0
  72. package/examples/partner-frontend.mjs +44 -0
  73. package/examples/partner-server.mjs +22 -0
  74. package/package.json +104 -4
package/dist/base.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { type ClientOptions } from "openapi-fetch";
2
+ import { type RetryPolicy } from "./request.js";
3
+ export declare const publicServers: {
4
+ readonly production: "https://hip4-api.totalis.trade";
5
+ readonly staging: "https://hip4-api-staging.totalis.trade";
6
+ readonly local: "http://localhost:8080";
7
+ };
8
+ /**
9
+ * Where each environment serves its WebSockets (`/v1/stream` and
10
+ * `/v1/market-stream`). Production and staging have a dedicated stream host;
11
+ * the API host still serves both paths during the migration.
12
+ */
13
+ export declare const publicStreamServers: {
14
+ readonly production: "wss://hip4-ws.totalis.trade";
15
+ readonly staging: "wss://hip4-ws-staging.totalis.trade";
16
+ readonly local: "ws://localhost:8080";
17
+ };
18
+ export type PublicEnvironment = keyof typeof publicServers;
19
+ /** A bearer credential, or a function that returns the current one (for sessions that renew). */
20
+ export type AccessToken = string | (() => string | Promise<string>);
21
+ export type ApiClientOptions = Omit<ClientOptions, "baseUrl"> & {
22
+ accessToken?: AccessToken;
23
+ /** Replace the environment's origin, for example a local stack. */
24
+ baseUrl?: string;
25
+ /** Resend policy for network failures and BACKOFF errors; `false` sends once. */
26
+ retry?: RetryPolicy | false;
27
+ /**
28
+ * A partner's publishable browser key (`tpb_v1_…`), sent as
29
+ * `X-Totalis-Partner` from the partner's registered origins. It is public
30
+ * and only labels the traffic; it is never the `tpk_` server key.
31
+ */
32
+ partnerKey?: string;
33
+ };
34
+ /** The X-Totalis-Partner header for a publishable key, refusing anything else, above all a server key. */
35
+ export declare function partnerHeader(partnerKey: string | undefined): Record<string, string> | undefined;
36
+ /** Request headers as a plain record, for the stream handshake outside a browser. */
37
+ export declare function headerRecord(headers: ClientOptions["headers"]): Record<string, string>;
38
+ export declare function serverUrl(environment: PublicEnvironment, baseUrl?: string): string;
39
+ /** A custom `baseUrl` serves the stream paths too, so it stays the stream origin unless `streamBaseUrl` is set. */
40
+ export declare function streamServerUrl(environment: PublicEnvironment, baseUrl?: string, streamBaseUrl?: string): string;
41
+ export declare function audienceClient<Paths extends Record<string, unknown>>(environment: PublicEnvironment, options?: ApiClientOptions): import("openapi-fetch").Client<Paths, `${string}/${string}`>;
package/dist/base.js ADDED
@@ -0,0 +1,60 @@
1
+ import createOpenApiClient, { mergeHeaders } from "openapi-fetch";
2
+ import { retryingFetch } from "./request.js";
3
+ export const publicServers = {
4
+ production: "https://hip4-api.totalis.trade",
5
+ staging: "https://hip4-api-staging.totalis.trade",
6
+ local: "http://localhost:8080",
7
+ };
8
+ /**
9
+ * Where each environment serves its WebSockets (`/v1/stream` and
10
+ * `/v1/market-stream`). Production and staging have a dedicated stream host;
11
+ * the API host still serves both paths during the migration.
12
+ */
13
+ export const publicStreamServers = {
14
+ production: "wss://hip4-ws.totalis.trade",
15
+ staging: "wss://hip4-ws-staging.totalis.trade",
16
+ local: "ws://localhost:8080",
17
+ };
18
+ const browserKey = /^tpb_v1_(dev|stg|prd)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
19
+ /** The X-Totalis-Partner header for a publishable key, refusing anything else, above all a server key. */
20
+ export function partnerHeader(partnerKey) {
21
+ if (partnerKey === undefined)
22
+ return undefined;
23
+ if (partnerKey.startsWith("tpk_"))
24
+ throw new Error("partnerKey takes the publishable tpb_ key; a tpk_ server key must stay on your server");
25
+ if (!browserKey.test(partnerKey))
26
+ throw new Error("partnerKey is not a publishable tpb_v1 key");
27
+ return { "X-Totalis-Partner": partnerKey };
28
+ }
29
+ /** Request headers as a plain record, for the stream handshake outside a browser. */
30
+ export function headerRecord(headers) {
31
+ const record = {};
32
+ mergeHeaders(headers).forEach((value, name) => { record[name] = value; });
33
+ return record;
34
+ }
35
+ export function serverUrl(environment, baseUrl) {
36
+ return baseUrl ?? publicServers[environment];
37
+ }
38
+ /** A custom `baseUrl` serves the stream paths too, so it stays the stream origin unless `streamBaseUrl` is set. */
39
+ export function streamServerUrl(environment, baseUrl, streamBaseUrl) {
40
+ return (streamBaseUrl ?? baseUrl ?? publicStreamServers[environment]).replace(/^http/, "ws");
41
+ }
42
+ export function audienceClient(environment, options = {}) {
43
+ const { accessToken, headers, baseUrl, retry, partnerKey, fetch: fetchImpl, ...rest } = options;
44
+ const resolvedHeaders = mergeHeaders(headers, partnerHeader(partnerKey), typeof accessToken === "string" ? { Authorization: `Bearer ${accessToken}` } : undefined);
45
+ const client = createOpenApiClient({
46
+ ...rest,
47
+ baseUrl: serverUrl(environment, baseUrl),
48
+ headers: resolvedHeaders,
49
+ fetch: retry === false ? (fetchImpl ?? ((request) => globalThis.fetch(request))) : retryingFetch(fetchImpl, retry),
50
+ });
51
+ if (typeof accessToken === "function") {
52
+ client.use({
53
+ async onRequest({ request }) {
54
+ request.headers.set("Authorization", `Bearer ${await accessToken()}`);
55
+ return request;
56
+ },
57
+ });
58
+ }
59
+ return client;
60
+ }
@@ -0,0 +1,16 @@
1
+ import { type ApiClientOptions, type PublicEnvironment } from "./base.js";
2
+ import type { paths } from "./generated/client.js";
3
+ export { publicServers, publicStreamServers } from "./base.js";
4
+ export type { components, operations, paths } from "./generated/client.js";
5
+ export type { AccessToken, ApiClientOptions, PublicEnvironment } from "./base.js";
6
+ /** The typed HTTP client for the `client` audience. `createTotalis` adds the flows. */
7
+ export declare function createClient(environment: PublicEnvironment, options?: ApiClientOptions): import("openapi-fetch").Client<paths, `${string}/${string}`>;
8
+ export { bestBid, bestQuote, createTotalis, EntryError, entryFailure, follow, type Acceptance, type AcceptanceResult, type Amount, type BridgeState, type CashoutBid, type Chooser, type Deployment, type DepositAccepted, type EntryCash, type EntryFailure, type EntryQuote, type CoreExit, type LegacySignedEntry, type Leg, type NotAdmitted, type RecordListener, type Operation, type Quote, type ResponseOf, type SellBackQuote, type SignedAcceptance, type SignedCashout, type SignedCoreExit, type SignedDeposit, type SignedEntry, type SignedReturnToCore, type Totalis, type TotalisOptions, type TransferQuote, type WalletCall, type Withdrawal, } from "./totalis.js";
9
+ export { bridgeAction, bridgeGasBuffer, classifyHypercoreAnswer, hyperevmUsdcBridge, hyperliquidExchange, hyperliquidNetwork, hypercoreNonce, hypercoreSignature, sendAssetTypedData, settleHypercore, signSendAsset, submitHypercore, usdcText, type BridgedCash, type HypercoreAnswer, type HypercoreSignature, type HypercoreTransport, type HyperliquidNetwork, type SendAssetAction, type SignedSendAsset, } from "./hypercore.js";
10
+ export { ApiError, envelope, NetworkError, randomBytes32, retryingFetch, sendOnce, unwrap, uuidv7, type Fetch, type Repaired, type RetryPolicy } from "./request.js";
11
+ export { items, pages, type Page, type PageQuery } from "./pagination.js";
12
+ export { toTotalisSigner, type SignerInput, type TotalisSigner } from "./signer.js";
13
+ export { BalanceUnknownError, ExitUnavailableError, normalizeError, type NormalizedError } from "./flow-errors.js";
14
+ export { netCashout, netOfProfitFee, netPayout, netStake, spendableCash } from "./money.js";
15
+ export { createUiAdapter, type AccountResource, type QuoteView, type UiAdapter, type UiAdapterOptions } from "./ui.js";
16
+ export { StreamClosedError, TotalisStream, type StreamEvent, type StreamOptions, type StreamStatus, type StreamSubscription } from "./stream.js";
package/dist/client.js ADDED
@@ -0,0 +1,15 @@
1
+ import { audienceClient } from "./base.js";
2
+ export { publicServers, publicStreamServers } from "./base.js";
3
+ /** The typed HTTP client for the `client` audience. `createTotalis` adds the flows. */
4
+ export function createClient(environment, options) {
5
+ return audienceClient(environment, options);
6
+ }
7
+ export { bestBid, bestQuote, createTotalis, EntryError, entryFailure, follow, } from "./totalis.js";
8
+ export { bridgeAction, bridgeGasBuffer, classifyHypercoreAnswer, hyperevmUsdcBridge, hyperliquidExchange, hyperliquidNetwork, hypercoreNonce, hypercoreSignature, sendAssetTypedData, settleHypercore, signSendAsset, submitHypercore, usdcText, } from "./hypercore.js";
9
+ export { ApiError, envelope, NetworkError, randomBytes32, retryingFetch, sendOnce, unwrap, uuidv7 } from "./request.js";
10
+ export { items, pages } from "./pagination.js";
11
+ export { toTotalisSigner } from "./signer.js";
12
+ export { BalanceUnknownError, ExitUnavailableError, normalizeError } from "./flow-errors.js";
13
+ export { netCashout, netOfProfitFee, netPayout, netStake, spendableCash } from "./money.js";
14
+ export { createUiAdapter } from "./ui.js";
15
+ export { StreamClosedError, TotalisStream } from "./stream.js";
@@ -0,0 +1,50 @@
1
+ import type { components } from "./generated/openapi.js";
2
+ import { type Hex, type TotalisDeployment, type UsdcDeployment } from "./typed-data.js";
3
+ /**
4
+ * A zero-value transaction for a wallet to sign and send itself: the API's
5
+ * `TransactionCall` (maker funding, collateral reductions), for any deployment
6
+ * chain and with hex-typed fields. The SDK never sends transactions or holds a
7
+ * gas key.
8
+ */
9
+ export type TransactionCall = Omit<components["schemas"]["TransactionCall"], "chain_id" | "to" | "data"> & {
10
+ readonly chain_id: string;
11
+ readonly to: Hex;
12
+ readonly data: Hex;
13
+ };
14
+ type Input = {
15
+ readonly name: string;
16
+ readonly type: string;
17
+ };
18
+ type Value = bigint | string;
19
+ /** Calldata from the API, or a TypeError. */
20
+ export declare function calldata(value: string): Hex;
21
+ /** ABI-encode a call whose arguments are all static words (addresses and unsigned integers). */
22
+ export declare function encodeCall(name: string, inputs: readonly Input[], values: readonly Value[]): Hex;
23
+ /**
24
+ * Open a maker vault. A maker is one wallet: send this from `wallet`, which
25
+ * becomes both its controller and its quote signer. A wallet opens at most one
26
+ * maker: read the new maker ID with the vault's `makerOf(wallet)`, never
27
+ * predict it.
28
+ */
29
+ export declare function openMakerVaultTransaction(deployment: TotalisDeployment, wallet: string): TransactionCall;
30
+ /**
31
+ * Vault transactions for one maker, built from the canonical ABI. The maker
32
+ * wallet sends all of them except `approveUsdc` and `deposit`, which come from
33
+ * whichever wallet funds the deposit. `controller` is the maker wallet (`owner`
34
+ * in GET /v1/makers/{maker_id}/capital): the vault refuses a deposit naming any
35
+ * other.
36
+ */
37
+ export declare function controllerTransactions(deployment: TotalisDeployment & UsdcDeployment, makerId: string, controller: string): {
38
+ /** USDC `approve(vault, amount)`, sent by the funding wallet before `deposit`. */
39
+ approveUsdc: (amount: Value) => TransactionCall;
40
+ /** `depositMaker`: pull approved USDC from the sending wallet into this maker's vault. */
41
+ deposit: (amount: Value) => TransactionCall;
42
+ /** `requestMakerWithdraw`: start the withdrawal delay for `amount` to `to`. */
43
+ requestWithdraw: (amount: Value, to: string) => TransactionCall;
44
+ /** `executeMakerWithdraw`: pay out the pending request once its delay has passed. */
45
+ executeWithdraw: () => TransactionCall;
46
+ cancelWithdraw: () => TransactionCall;
47
+ resetDiscount: () => TransactionCall;
48
+ };
49
+ export type ControllerTransactions = ReturnType<typeof controllerTransactions>;
50
+ export {};
@@ -0,0 +1,60 @@
1
+ import { keccak_256 } from "@noble/hashes/sha3";
2
+ import { controllerFunctions } from "./generated/vault-abi.js";
3
+ import { address } from "./typed-data.js";
4
+ const hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
5
+ function word(type, value) {
6
+ if (type === "address")
7
+ return address(String(value)).slice(2).toLowerCase().padStart(64, "0");
8
+ const bits = /^uint(\d+)$/.exec(type)?.[1];
9
+ if (bits === undefined)
10
+ throw new TypeError(`unsupported ABI type ${type}`);
11
+ const integer = BigInt(value);
12
+ if (integer < 0n || integer >= 1n << BigInt(bits))
13
+ throw new RangeError(`${value} does not fit ${type}`);
14
+ return integer.toString(16).padStart(64, "0");
15
+ }
16
+ /** Calldata from the API, or a TypeError. */
17
+ export function calldata(value) {
18
+ if (!/^0x(?:[0-9a-fA-F]{2})*$/.test(value))
19
+ throw new TypeError(`not calldata: ${value}`);
20
+ return value;
21
+ }
22
+ /** ABI-encode a call whose arguments are all static words (addresses and unsigned integers). */
23
+ export function encodeCall(name, inputs, values) {
24
+ if (values.length !== inputs.length)
25
+ throw new TypeError(`${name} takes ${inputs.length} arguments`);
26
+ const selector = hex(keccak_256(new TextEncoder().encode(`${name}(${inputs.map((input) => input.type).join(",")})`)).slice(0, 4));
27
+ return `0x${selector}${inputs.map((input, index) => word(input.type, values[index])).join("")}`;
28
+ }
29
+ const call = (deployment, to, data) => ({ chain_id: deployment.chain_id, to: address(to.toLowerCase()), data, value: "0" });
30
+ const vaultCall = (deployment, name, values) => call(deployment, deployment.vault_address, encodeCall(name, controllerFunctions[name], values));
31
+ /**
32
+ * Open a maker vault. A maker is one wallet: send this from `wallet`, which
33
+ * becomes both its controller and its quote signer. A wallet opens at most one
34
+ * maker: read the new maker ID with the vault's `makerOf(wallet)`, never
35
+ * predict it.
36
+ */
37
+ export function openMakerVaultTransaction(deployment, wallet) {
38
+ return vaultCall(deployment, "openMakerVault", [wallet]);
39
+ }
40
+ /**
41
+ * Vault transactions for one maker, built from the canonical ABI. The maker
42
+ * wallet sends all of them except `approveUsdc` and `deposit`, which come from
43
+ * whichever wallet funds the deposit. `controller` is the maker wallet (`owner`
44
+ * in GET /v1/makers/{maker_id}/capital): the vault refuses a deposit naming any
45
+ * other.
46
+ */
47
+ export function controllerTransactions(deployment, makerId, controller) {
48
+ return {
49
+ /** USDC `approve(vault, amount)`, sent by the funding wallet before `deposit`. */
50
+ approveUsdc: (amount) => call(deployment, deployment.usdc.address, encodeCall("approve", [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }], [deployment.vault_address, amount])),
51
+ /** `depositMaker`: pull approved USDC from the sending wallet into this maker's vault. */
52
+ deposit: (amount) => vaultCall(deployment, "depositMaker", [makerId, controller, amount]),
53
+ /** `requestMakerWithdraw`: start the withdrawal delay for `amount` to `to`. */
54
+ requestWithdraw: (amount, to) => vaultCall(deployment, "requestMakerWithdraw", [makerId, amount, to]),
55
+ /** `executeMakerWithdraw`: pay out the pending request once its delay has passed. */
56
+ executeWithdraw: () => vaultCall(deployment, "executeMakerWithdraw", [makerId]),
57
+ cancelWithdraw: () => vaultCall(deployment, "cancelMakerWithdraw", [makerId]),
58
+ resetDiscount: () => vaultCall(deployment, "resetDiscount", [makerId]),
59
+ };
60
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./generated/errors.js";
2
+ export { ApiError, NetworkError } from "./request.js";
3
+ export { BalanceUnknownError, EntryError, ExitUnavailableError, normalizeError, type NormalizedError } from "./flow-errors.js";
package/dist/errors.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./generated/errors.js";
2
+ export { ApiError, NetworkError } from "./request.js";
3
+ export { BalanceUnknownError, EntryError, ExitUnavailableError, normalizeError } from "./flow-errors.js";
@@ -0,0 +1,61 @@
1
+ import type { Retry } from "./generated/errors.js";
2
+ /**
3
+ * An entry that stopped while planning its funding, before anything was sent,
4
+ * so nothing traded: `BALANCE_UNKNOWN` or `INSUFFICIENT_FUNDS`.
5
+ */
6
+ export declare class EntryError extends Error {
7
+ readonly code: "BALANCE_UNKNOWN" | "INSUFFICIENT_FUNDS";
8
+ readonly name = "EntryError";
9
+ constructor(code: "BALANCE_UNKNOWN" | "INSUFFICIENT_FUNDS", message: string);
10
+ }
11
+ /**
12
+ * A withdrawal, exit or return to HyperCore needs a balance the venue has no
13
+ * current read of (`component` is not `CURRENT` in `GET /v1/me/balances`).
14
+ * Nothing was signed or sent; read the balances again and retry. An entry
15
+ * throws `EntryError` `BALANCE_UNKNOWN` instead.
16
+ */
17
+ export declare class BalanceUnknownError extends Error {
18
+ readonly component: "VAULT_FREE" | "WALLET_USDC";
19
+ readonly name = "BalanceUnknownError";
20
+ readonly code = "BALANCE_UNKNOWN";
21
+ constructor(component: "VAULT_FREE" | "WALLET_USDC", message: string);
22
+ }
23
+ /**
24
+ * This deployment has no exit to HyperCore (`deployment: null` on
25
+ * `GET /v1/me/core-exits`, or the venue's 503 `CORE_EXIT_UNAVAILABLE`;
26
+ * staging runs without one). Retrying
27
+ * will not help: hide the action instead. `cause` is the venue's refusal when
28
+ * there was one.
29
+ */
30
+ export declare class ExitUnavailableError extends Error {
31
+ readonly name = "ExitUnavailableError";
32
+ readonly code = "EXIT_UNAVAILABLE";
33
+ constructor(options?: {
34
+ cause: unknown;
35
+ });
36
+ }
37
+ /** Any SDK failure in one shape, for a UI or a log line. */
38
+ export type NormalizedError = {
39
+ /** The venue's registered code, `EntryError`'s code, `BALANCE_UNKNOWN`, `EXIT_UNAVAILABLE`, `NETWORK`, `STREAM_CLOSED` (with the close reason in `message`), `TIMEOUT`, `ABORTED` or `UNKNOWN`. */
40
+ readonly code: string;
41
+ readonly message: string;
42
+ /** Trying again can succeed: the same request later (BACKOFF, a lost response) or a fresh read and a new request (REFRESH, an unknown balance). */
43
+ readonly retryable: boolean;
44
+ /** The venue's `retry` for an `ApiError`. */
45
+ readonly retry: Retry | undefined;
46
+ /** The venue's request ID, to quote to support. */
47
+ readonly requestId: string | undefined;
48
+ readonly status: number | undefined;
49
+ /**
50
+ * The command may have reached the venue and may still execute: its
51
+ * response was lost, it was aborted or timed out in flight, it was still
52
+ * BACKOFF after the resends (an earlier send may have been lost), or a
53
+ * proxy answered 5xx without the venue's error envelope. Never sign a new
54
+ * command; `resend` it or read its state.
55
+ */
56
+ readonly mayStillRun: boolean;
57
+ /** Sends the identical request again, when the flow attached it. */
58
+ readonly resend: (() => Promise<unknown>) | undefined;
59
+ readonly cause: unknown;
60
+ };
61
+ export declare function normalizeError(error: unknown): NormalizedError;
@@ -0,0 +1,70 @@
1
+ import { ApiError, NetworkError } from "./request.js";
2
+ import { StreamClosedError } from "./stream.js";
3
+ /**
4
+ * An entry that stopped while planning its funding, before anything was sent,
5
+ * so nothing traded: `BALANCE_UNKNOWN` or `INSUFFICIENT_FUNDS`.
6
+ */
7
+ export class EntryError extends Error {
8
+ code;
9
+ name = "EntryError";
10
+ constructor(code, message) {
11
+ super(message);
12
+ this.code = code;
13
+ }
14
+ }
15
+ /**
16
+ * A withdrawal, exit or return to HyperCore needs a balance the venue has no
17
+ * current read of (`component` is not `CURRENT` in `GET /v1/me/balances`).
18
+ * Nothing was signed or sent; read the balances again and retry. An entry
19
+ * throws `EntryError` `BALANCE_UNKNOWN` instead.
20
+ */
21
+ export class BalanceUnknownError extends Error {
22
+ component;
23
+ name = "BalanceUnknownError";
24
+ code = "BALANCE_UNKNOWN";
25
+ constructor(component, message) {
26
+ super(message);
27
+ this.component = component;
28
+ }
29
+ }
30
+ /**
31
+ * This deployment has no exit to HyperCore (`deployment: null` on
32
+ * `GET /v1/me/core-exits`, or the venue's 503 `CORE_EXIT_UNAVAILABLE`;
33
+ * staging runs without one). Retrying
34
+ * will not help: hide the action instead. `cause` is the venue's refusal when
35
+ * there was one.
36
+ */
37
+ export class ExitUnavailableError extends Error {
38
+ name = "ExitUnavailableError";
39
+ code = "EXIT_UNAVAILABLE";
40
+ constructor(options) {
41
+ super("exits to HyperCore are not configured on this deployment", options);
42
+ }
43
+ }
44
+ export function normalizeError(error) {
45
+ const base = { retry: undefined, requestId: undefined, status: undefined, mayStillRun: false, resend: undefined, cause: error };
46
+ const message = error instanceof Error ? error.message : String(error);
47
+ if (error instanceof ApiError) {
48
+ return {
49
+ ...base, code: error.code, message, retryable: error.retry !== "NEVER", retry: error.retry, requestId: error.requestId, status: error.status,
50
+ // A command still BACKOFF after its resends (it carries `resend`) may hide an earlier send that was lost after reaching the venue.
51
+ mayStillRun: error.resend !== undefined || (error.code.startsWith("HTTP_") && error.status >= 500), resend: error.resend,
52
+ };
53
+ }
54
+ if (error instanceof NetworkError)
55
+ return { ...base, code: "NETWORK", message, retryable: true, mayStillRun: true, resend: error.resend };
56
+ if (error instanceof EntryError)
57
+ return { ...base, code: error.code, message, retryable: error.code === "BALANCE_UNKNOWN" };
58
+ if (error instanceof BalanceUnknownError)
59
+ return { ...base, code: error.code, message, retryable: true };
60
+ if (error instanceof ExitUnavailableError)
61
+ return { ...base, code: error.code, message, retryable: false };
62
+ if (error instanceof StreamClosedError)
63
+ return { ...base, code: "STREAM_CLOSED", message: error.reason, retryable: false };
64
+ // An aborted or timed-out request may still have reached the venue.
65
+ if (error instanceof Error && error.name === "TimeoutError")
66
+ return { ...base, code: "TIMEOUT", message, retryable: true, mayStillRun: true };
67
+ if (error instanceof Error && error.name === "AbortError")
68
+ return { ...base, code: "ABORTED", message, retryable: false, mayStillRun: true };
69
+ return { ...base, code: "UNKNOWN", message, retryable: false };
70
+ }