@forgezero/runtime 0.1.0

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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/contracts/foundry.toml +9 -0
  4. package/contracts/src/ColdVault.sol +206 -0
  5. package/contracts/src/DepositFactory.sol +202 -0
  6. package/contracts/src/DepositProxy.sol +72 -0
  7. package/contracts/src/IERC20.sol +7 -0
  8. package/contracts/src/MockTokens.sol +32 -0
  9. package/contracts/src/SafeTransferLib.sol +31 -0
  10. package/contracts/test/Custody.t.sol +361 -0
  11. package/contracts/test/Vectors.t.sol +45 -0
  12. package/dist/audit.d.ts +265 -0
  13. package/dist/audit.js +291 -0
  14. package/dist/backup.d.ts +243 -0
  15. package/dist/backup.js +302 -0
  16. package/dist/calendar.d.ts +136 -0
  17. package/dist/calendar.js +129 -0
  18. package/dist/compliance.d.ts +172 -0
  19. package/dist/compliance.js +168 -0
  20. package/dist/finance/binance.d.ts +27 -0
  21. package/dist/finance/binance.js +452 -0
  22. package/dist/finance/chain-addresses.d.ts +130 -0
  23. package/dist/finance/chain-addresses.js +462 -0
  24. package/dist/finance/chain-deposits.d.ts +193 -0
  25. package/dist/finance/chain-deposits.js +596 -0
  26. package/dist/finance/chain-reconcile.d.ts +112 -0
  27. package/dist/finance/chain-reconcile.js +76 -0
  28. package/dist/finance/chain-withdrawals.d.ts +223 -0
  29. package/dist/finance/chain-withdrawals.js +631 -0
  30. package/dist/finance/chain.d.ts +116 -0
  31. package/dist/finance/chain.js +316 -0
  32. package/dist/finance/commission.d.ts +155 -0
  33. package/dist/finance/commission.js +419 -0
  34. package/dist/finance/custody.d.ts +68 -0
  35. package/dist/finance/custody.js +107 -0
  36. package/dist/finance/derive.d.ts +115 -0
  37. package/dist/finance/derive.js +116 -0
  38. package/dist/finance/discounts.d.ts +98 -0
  39. package/dist/finance/discounts.js +90 -0
  40. package/dist/finance/ledger.d.ts +221 -0
  41. package/dist/finance/ledger.js +308 -0
  42. package/dist/finance/market.d.ts +209 -0
  43. package/dist/finance/market.js +112 -0
  44. package/dist/finance/money.d.ts +118 -0
  45. package/dist/finance/money.js +176 -0
  46. package/dist/finance/rates.d.ts +178 -0
  47. package/dist/finance/rates.js +292 -0
  48. package/dist/finance/storage.d.ts +113 -0
  49. package/dist/finance/storage.js +226 -0
  50. package/dist/finance/tax.d.ts +132 -0
  51. package/dist/finance/tax.js +291 -0
  52. package/dist/finance/transfers.d.ts +153 -0
  53. package/dist/finance/transfers.js +292 -0
  54. package/dist/finance/venues.d.ts +190 -0
  55. package/dist/finance/venues.js +251 -0
  56. package/dist/identity.d.ts +115 -0
  57. package/dist/identity.js +111 -0
  58. package/dist/importers.d.ts +87 -0
  59. package/dist/importers.js +250 -0
  60. package/dist/jobs.d.ts +171 -0
  61. package/dist/jobs.js +250 -0
  62. package/dist/notify-templates.d.ts +11 -0
  63. package/dist/notify-templates.js +254 -0
  64. package/dist/notify.d.ts +172 -0
  65. package/dist/notify.js +122 -0
  66. package/dist/openssh.d.ts +36 -0
  67. package/dist/openssh.js +106 -0
  68. package/dist/otpauth.d.ts +57 -0
  69. package/dist/otpauth.js +223 -0
  70. package/dist/outbox.d.ts +234 -0
  71. package/dist/outbox.js +236 -0
  72. package/dist/passkey.d.ts +120 -0
  73. package/dist/passkey.js +105 -0
  74. package/dist/phrase.d.ts +87 -0
  75. package/dist/phrase.js +87 -0
  76. package/dist/pipeline.d.ts +137 -0
  77. package/dist/pipeline.js +121 -0
  78. package/dist/queue.d.ts +243 -0
  79. package/dist/queue.js +246 -0
  80. package/dist/schema-typebox.d.ts +24 -0
  81. package/dist/schema-typebox.js +201 -0
  82. package/dist/schema.d.ts +134 -0
  83. package/dist/schema.js +169 -0
  84. package/dist/serial.d.ts +54 -0
  85. package/dist/serial.js +40 -0
  86. package/dist/slip10.d.ts +37 -0
  87. package/dist/slip10.js +74 -0
  88. package/dist/snp.d.ts +115 -0
  89. package/dist/snp.js +109 -0
  90. package/dist/ssh-agent.d.ts +70 -0
  91. package/dist/ssh-agent.js +141 -0
  92. package/dist/ssh-cert.d.ts +73 -0
  93. package/dist/ssh-cert.js +111 -0
  94. package/dist/totp.d.ts +104 -0
  95. package/dist/totp.js +143 -0
  96. package/package.json +248 -0
@@ -0,0 +1,292 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/finance/money.ts
10
+ class MoneyError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = "MoneyError";
16
+ }
17
+ }
18
+ var ASSETS = [
19
+ { code: "USDT", decimals: 6 },
20
+ { code: "USDC", decimals: 6 },
21
+ { code: "BTC", decimals: 8 },
22
+ { code: "ETH", decimals: 18 },
23
+ { code: "BNB", decimals: 18 },
24
+ { code: "EUR", decimals: 2 },
25
+ { code: "USD", decimals: 2 }
26
+ ];
27
+ var REGISTRY = new Map(ASSETS.map((asset) => [asset.code, asset]));
28
+ function defineAsset(spec) {
29
+ if (spec.decimals < 0 || spec.decimals > 30 || !Number.isInteger(spec.decimals)) {
30
+ throw new MoneyError("UNKNOWN_ASSET", `${spec.code}: decimals must be an integer 0–30.`);
31
+ }
32
+ REGISTRY.set(spec.code, spec);
33
+ }
34
+ function assetSpec(code) {
35
+ const spec = REGISTRY.get(code);
36
+ if (!spec)
37
+ throw new MoneyError("UNKNOWN_ASSET", `Unknown asset "${code}". Call defineAsset first.`);
38
+ return spec;
39
+ }
40
+ var money = (units, asset) => {
41
+ assetSpec(asset);
42
+ return { units, asset };
43
+ };
44
+ var zero = (asset) => money(0n, asset);
45
+ function parseAmount(value, asset) {
46
+ const spec = assetSpec(asset);
47
+ const text = value.trim();
48
+ if (!/^-?\d+(\.\d+)?$/.test(text)) {
49
+ throw new MoneyError("NOT_FINITE", `"${value}" is not a plain decimal amount.`);
50
+ }
51
+ const negative = text.startsWith("-");
52
+ const [whole, fraction = ""] = text.replace("-", "").split(".");
53
+ if (fraction.length > spec.decimals) {
54
+ throw new MoneyError("PRECISION_LOSS", `${asset} has ${spec.decimals} decimals; "${value}" has ${fraction.length}.`);
55
+ }
56
+ const padded = fraction.padEnd(spec.decimals, "0");
57
+ const units = BigInt(whole + padded);
58
+ return { units: negative ? -units : units, asset };
59
+ }
60
+ function formatAmount(amount, options = {}) {
61
+ const spec = assetSpec(amount.asset);
62
+ const negative = amount.units < 0n;
63
+ const digits = (negative ? -amount.units : amount.units).toString().padStart(spec.decimals + 1, "0");
64
+ const whole = digits.slice(0, digits.length - spec.decimals);
65
+ let fraction = spec.decimals === 0 ? "" : digits.slice(digits.length - spec.decimals);
66
+ if (options.trim && fraction)
67
+ fraction = fraction.replace(/0+$/, "");
68
+ return `${negative ? "-" : ""}${whole}${fraction ? `.${fraction}` : ""}`;
69
+ }
70
+ function sameAsset(a, b) {
71
+ if (a.asset !== b.asset) {
72
+ throw new MoneyError("ASSET_MISMATCH", `Cannot combine ${a.asset} and ${b.asset}.`);
73
+ }
74
+ }
75
+ function add(a, b) {
76
+ sameAsset(a, b);
77
+ return { units: a.units + b.units, asset: a.asset };
78
+ }
79
+ function subtract(a, b) {
80
+ sameAsset(a, b);
81
+ return { units: a.units - b.units, asset: a.asset };
82
+ }
83
+ var negate = (amount) => ({ units: -amount.units, asset: amount.asset });
84
+ var abs = (amount) => ({
85
+ units: amount.units < 0n ? -amount.units : amount.units,
86
+ asset: amount.asset
87
+ });
88
+ var isZero = (amount) => amount.units === 0n;
89
+ var isNegative = (amount) => amount.units < 0n;
90
+ function compare(a, b) {
91
+ sameAsset(a, b);
92
+ return a.units < b.units ? -1 : a.units > b.units ? 1 : 0;
93
+ }
94
+ var equals = (a, b) => a.asset === b.asset && a.units === b.units;
95
+ var ROUNDING = ["down", "up", "half-up"];
96
+ function divideRounded(numerator, denominator, mode) {
97
+ if (denominator === 0n)
98
+ throw new MoneyError("DIVIDE_BY_ZERO", "Division by zero.");
99
+ const negative = numerator < 0n !== denominator < 0n;
100
+ const a = numerator < 0n ? -numerator : numerator;
101
+ const b = denominator < 0n ? -denominator : denominator;
102
+ const quotient = a / b;
103
+ const remainder = a % b;
104
+ if (remainder === 0n)
105
+ return negative ? -quotient : quotient;
106
+ let result = quotient;
107
+ if (mode === "up")
108
+ result += 1n;
109
+ else if (mode === "half-up" && remainder * 2n >= b)
110
+ result += 1n;
111
+ return negative ? -result : result;
112
+ }
113
+ function mulRate(amount, rate, mode = "down") {
114
+ if (!/^-?\d+(\.\d+)?$/.test(rate.trim())) {
115
+ throw new MoneyError("NOT_FINITE", `"${rate}" is not a plain decimal rate.`);
116
+ }
117
+ const [whole, fraction = ""] = rate.trim().replace("-", "").split(".");
118
+ const scale = 10n ** BigInt(fraction.length);
119
+ const scaled = BigInt(whole + fraction) * (rate.trim().startsWith("-") ? -1n : 1n);
120
+ return { units: divideRounded(amount.units * scaled, scale, mode), asset: amount.asset };
121
+ }
122
+ function convert(amount, to, rate, mode = "down") {
123
+ const from = assetSpec(amount.asset);
124
+ const target = assetSpec(to);
125
+ const asTarget = mulRate({ units: amount.units, asset: to }, rate, mode);
126
+ const shift = target.decimals - from.decimals;
127
+ if (shift === 0)
128
+ return asTarget;
129
+ if (shift > 0)
130
+ return { units: asTarget.units * 10n ** BigInt(shift), asset: to };
131
+ return { units: divideRounded(asTarget.units, 10n ** BigInt(-shift), mode), asset: to };
132
+ }
133
+ function allocate(amount, parts) {
134
+ if (parts < 1)
135
+ throw new MoneyError("NOT_FINITE", "Cannot allocate into fewer than one part.");
136
+ const each = divideRounded(amount.units, BigInt(parts), "down");
137
+ const allocated = Array.from({ length: parts }, () => each);
138
+ let remainder = amount.units - each * BigInt(parts);
139
+ const step = remainder < 0n ? -1n : 1n;
140
+ for (let index = 0;remainder !== 0n; index = (index + 1) % parts) {
141
+ allocated[index] += step;
142
+ remainder -= step;
143
+ }
144
+ return allocated.map((units) => ({ units, asset: amount.asset }));
145
+ }
146
+ function toStep(amount, step, mode = "down") {
147
+ const stepUnits = parseAmount(step, amount.asset).units;
148
+ if (stepUnits <= 0n)
149
+ throw new MoneyError("NOT_FINITE", "A step must be positive.");
150
+ return { units: divideRounded(amount.units, stepUnits, mode) * stepUnits, asset: amount.asset };
151
+ }
152
+ var VERSION = "0.1.0";
153
+
154
+ // src/finance/transfers.ts
155
+ import { Refusal, isRefusal } from "@forgezero/access";
156
+ var DIRECTIONS = ["deposit", "withdrawal"];
157
+ var SCREENING_ORDER = 0;
158
+
159
+ class PipelineError extends Error {
160
+ code;
161
+ constructor(code, message) {
162
+ super(message);
163
+ this.code = code;
164
+ this.name = "PipelineError";
165
+ }
166
+ }
167
+ function createTransferPipeline(options) {
168
+ const seen = new Set;
169
+ for (const stage of options.stages) {
170
+ if (seen.has(stage.name)) {
171
+ throw new PipelineError("DUPLICATE_STAGE", `Two stages are named "${stage.name}".`);
172
+ }
173
+ seen.add(stage.name);
174
+ }
175
+ if (options.requireScreening && !options.stages.some((stage) => stage.order === SCREENING_ORDER)) {
176
+ throw new PipelineError("NO_SCREENING", "This pipeline requires a screening stage at order 0 and has none. Register one, or set requireScreening to false and accept that transfers are unscreened.");
177
+ }
178
+ const ordered = [...options.stages].sort((a, b) => a.order - b.order);
179
+ const now = options.now ?? Date.now;
180
+ return {
181
+ stages: ordered.map((stage) => ({ name: stage.name, order: stage.order })),
182
+ async run(transfer) {
183
+ const outcomes = [];
184
+ let current = transfer;
185
+ for (const stage of ordered) {
186
+ if (stage.directions && !stage.directions.includes(current.direction))
187
+ continue;
188
+ const startedAt = now();
189
+ try {
190
+ const patch = await stage.run(current);
191
+ if (patch)
192
+ current = { ...current, context: { ...current.context, ...patch } };
193
+ const outcome = { stage: stage.name, ok: true, ms: now() - startedAt };
194
+ outcomes.push(outcome);
195
+ options.onStage?.(outcome, current);
196
+ } catch (cause) {
197
+ const refusal = isRefusal(cause) ? cause : new Refusal(500, "STAGE_FAILED", `The "${stage.name}" check could not complete.`, { stage: stage.name }, true);
198
+ const outcome = {
199
+ stage: stage.name,
200
+ ok: false,
201
+ ms: now() - startedAt,
202
+ code: refusal.code
203
+ };
204
+ outcomes.push(outcome);
205
+ options.onStage?.(outcome, current);
206
+ return { ok: false, transfer: current, stages: outcomes, refusal };
207
+ }
208
+ }
209
+ return { ok: true, transfer: current, stages: outcomes };
210
+ }
211
+ };
212
+ }
213
+ var screeningStage = (check = () => true) => ({
214
+ name: "aml.screening",
215
+ order: SCREENING_ORDER,
216
+ async run(transfer) {
217
+ const allowed = await check(transfer);
218
+ if (!allowed) {
219
+ throw new Refusal(451, "SCREENING_REFUSED", "This transfer cannot be processed.", {
220
+ direction: transfer.direction
221
+ });
222
+ }
223
+ return { screenedAtMs: transfer.atMs };
224
+ }
225
+ });
226
+ var limitsStage = (limits) => ({
227
+ name: "limits",
228
+ order: 10,
229
+ directions: limits.directions,
230
+ run(transfer) {
231
+ if (transfer.amount.units <= 0n) {
232
+ throw new Refusal(422, "AMOUNT_INVALID", "The amount must be positive.");
233
+ }
234
+ if (limits.min && transfer.amount.asset === limits.min.asset && transfer.amount.units < limits.min.units) {
235
+ throw new Refusal(422, "BELOW_MINIMUM", `The minimum is ${formatAmount(limits.min, { trim: true })} ${limits.min.asset}.`);
236
+ }
237
+ if (limits.max && transfer.amount.asset === limits.max.asset && transfer.amount.units > limits.max.units) {
238
+ throw new Refusal(422, "ABOVE_MAXIMUM", `The maximum is ${formatAmount(limits.max, { trim: true })} ${limits.max.asset}.`);
239
+ }
240
+ }
241
+ });
242
+ var balanceStage = (available) => ({
243
+ name: "balance",
244
+ order: 20,
245
+ directions: ["withdrawal"],
246
+ async run(transfer) {
247
+ const free = await available(transfer);
248
+ if (free.units < transfer.amount.units) {
249
+ throw new Refusal(409, "INSUFFICIENT_BALANCE", "The available balance does not cover this.", {
250
+ required: formatAmount(transfer.amount, { trim: true }),
251
+ available: formatAmount(free, { trim: true }),
252
+ asset: transfer.amount.asset
253
+ });
254
+ }
255
+ }
256
+ });
257
+ var approvalStage = (args) => ({
258
+ name: "approval",
259
+ order: 30,
260
+ directions: ["withdrawal"],
261
+ async run(transfer) {
262
+ if (transfer.amount.asset !== args.threshold.asset)
263
+ return;
264
+ if (transfer.amount.units < args.threshold.units)
265
+ return;
266
+ if (!await args.isApproved(transfer)) {
267
+ throw new Refusal(409, "AWAITING_APPROVAL", "This transfer is held for review.", {
268
+ threshold: formatAmount(args.threshold, { trim: true })
269
+ }, true);
270
+ }
271
+ }
272
+ });
273
+ var notFrozenStage = (isFrozen) => ({
274
+ name: "account.active",
275
+ order: 5,
276
+ async run(transfer) {
277
+ if (await isFrozen(transfer.owner)) {
278
+ throw new Refusal(423, "ACCOUNT_FROZEN", "This account is suspended. Settle the outstanding invoice to resume.");
279
+ }
280
+ }
281
+ });
282
+ export {
283
+ screeningStage,
284
+ notFrozenStage,
285
+ limitsStage,
286
+ createTransferPipeline,
287
+ balanceStage,
288
+ approvalStage,
289
+ SCREENING_ORDER,
290
+ PipelineError,
291
+ DIRECTIONS
292
+ };
@@ -0,0 +1,190 @@
1
+ import { type Money } from './money';
2
+ /**
3
+ * Trading venues, market types and symbols — as data.
4
+ *
5
+ * A trading system accumulates venue knowledge in the worst possible places:
6
+ * a lot size hard-coded in an order builder, a minimum notional in a validator,
7
+ * a symbol spelling in three files that disagree. This declares it once, so
8
+ * adding a market is data and adding a venue is one adapter.
9
+ *
10
+ * ## Why spot, margin and futures share an order model
11
+ *
12
+ * They differ in what they settle against, not in what an order IS. All three
13
+ * take a symbol, a side, a quantity and optionally a price; all three reject
14
+ * quantities off the lot step and notionals under a minimum. Modelling them
15
+ * separately means writing the same validation three times and getting it
16
+ * subtly different — and the difference will be in the one nobody trades often.
17
+ *
18
+ * What genuinely differs is expressed as fields on the market: leverage exists
19
+ * on margin and futures and not on spot, futures carry a contract type and an
20
+ * expiry. A caller that ignores those still cannot place an invalid order,
21
+ * because validation reads the market rather than the caller's intent.
22
+ *
23
+ * ## Nothing here talks to a venue
24
+ *
25
+ * `VenueAdapter` is an interface. This package validates, normalises and
26
+ * describes; the transport lives with whoever implements the adapter, so a new
27
+ * venue costs one file and no changes here.
28
+ */
29
+ export declare const MARKET_TYPES: readonly ["spot", "margin", "futures"];
30
+ export type MarketType = (typeof MARKET_TYPES)[number];
31
+ export declare const ORDER_SIDES: readonly ["buy", "sell"];
32
+ export type OrderSide = (typeof ORDER_SIDES)[number];
33
+ /**
34
+ * Order types every venue we support implements.
35
+ *
36
+ * Deliberately short. A venue-specific type — trailing stops, iceberg, OCO —
37
+ * belongs behind the adapter, because supporting it here means every other
38
+ * adapter has to reject it and the rejection is where the bugs live.
39
+ */
40
+ export declare const ORDER_TYPES: readonly ["market", "limit", "stop-limit"];
41
+ export type OrderType = (typeof ORDER_TYPES)[number];
42
+ /**
43
+ * How long an order lives.
44
+ *
45
+ * gtc rests until filled or cancelled
46
+ * ioc fills what it can immediately, cancels the rest
47
+ * fok fills entirely and immediately, or not at all
48
+ *
49
+ * `fok` matters for anything that must not partially fill — a hedge leg that
50
+ * half-executes leaves a position nobody asked for.
51
+ */
52
+ export declare const TIME_IN_FORCE: readonly ["gtc", "ioc", "fok"];
53
+ export type TimeInForce = (typeof TIME_IN_FORCE)[number];
54
+ export interface VenueSpec {
55
+ key: string;
56
+ label: string;
57
+ /** Market types this venue offers at all. */
58
+ markets: readonly MarketType[];
59
+ /**
60
+ * Requests per minute the venue permits, and how it counts them.
61
+ *
62
+ * `weight` means a request costs more than one — Binance charges by response
63
+ * size, so a naive per-request limiter is wrong by an order of magnitude on
64
+ * the endpoints that matter.
65
+ */
66
+ rateLimit: {
67
+ perMinute: number;
68
+ counts: 'requests' | 'weight';
69
+ };
70
+ /** Whether the venue supports a caller-supplied idempotency key on orders. */
71
+ clientOrderIds: boolean;
72
+ /** One clause: what a caller most needs to know before integrating. */
73
+ note: string;
74
+ }
75
+ export interface MarketSpec {
76
+ venue: string;
77
+ type: MarketType;
78
+ /** Canonical, venue-independent: `BTC/USDT`. Never a venue's own spelling. */
79
+ symbol: string;
80
+ base: string;
81
+ quote: string;
82
+ /** Quantity must be a multiple of this. */
83
+ lotStep: string;
84
+ /** Price must be a multiple of this. */
85
+ tickStep: string;
86
+ /** Smallest order value, in the quote asset. */
87
+ minNotional: string;
88
+ /** Margin and futures only. Absent on spot, and that absence is the rule. */
89
+ maxLeverage?: number;
90
+ /** Futures only. */
91
+ contract?: 'perpetual' | 'quarterly';
92
+ }
93
+ export declare const VENUES: readonly VenueSpec[];
94
+ export declare const venue: (key: string) => VenueSpec | undefined;
95
+ export declare const venuesFor: (type: MarketType) => readonly VenueSpec[];
96
+ /**
97
+ * `BTC/USDT` — one spelling, ours.
98
+ *
99
+ * Venues disagree: `BTCUSDT`, `BTC-USDT`, `btc_usdt`. Storing whichever the
100
+ * venue happened to use means a subscription created against one spelling does
101
+ * not match an order placed with another, and the mismatch is invisible until
102
+ * it is a missing position. Normalise on the way in, denormalise in the adapter.
103
+ */
104
+ export declare const symbolOf: (base: string, quote: string) => string;
105
+ export declare function parseSymbol(symbol: string): {
106
+ base: string;
107
+ quote: string;
108
+ };
109
+ export declare class VenueError extends Error {
110
+ readonly code: 'UNKNOWN_VENUE' | 'UNKNOWN_MARKET' | 'MARKET_UNSUPPORTED' | 'BAD_SYMBOL' | 'LOT_STEP' | 'TICK_STEP' | 'MIN_NOTIONAL' | 'PRICE_REQUIRED' | 'LEVERAGE_UNSUPPORTED' | 'LEVERAGE_TOO_HIGH';
111
+ constructor(code: 'UNKNOWN_VENUE' | 'UNKNOWN_MARKET' | 'MARKET_UNSUPPORTED' | 'BAD_SYMBOL' | 'LOT_STEP' | 'TICK_STEP' | 'MIN_NOTIONAL' | 'PRICE_REQUIRED' | 'LEVERAGE_UNSUPPORTED' | 'LEVERAGE_TOO_HIGH', message: string);
112
+ }
113
+ export interface OrderRequest {
114
+ venue: string;
115
+ type: MarketType;
116
+ symbol: string;
117
+ side: OrderSide;
118
+ orderType: OrderType;
119
+ /** In the BASE asset. */
120
+ quantity: Money;
121
+ /** In the QUOTE asset. Required for anything that is not a market order. */
122
+ price?: Money;
123
+ stopPrice?: Money;
124
+ timeInForce?: TimeInForce;
125
+ /** Margin and futures only. */
126
+ leverage?: number;
127
+ /**
128
+ * The caller's own idempotency key.
129
+ *
130
+ * Not optional in practice for anything automated: without it a retried
131
+ * submission after a timeout places a second order, and the operator sees one
132
+ * confirmation for two positions.
133
+ */
134
+ clientOrderId?: string;
135
+ /** Test the order against the venue without it resting. */
136
+ dryRun?: boolean;
137
+ }
138
+ export type OrderStatus = 'accepted' | 'partial' | 'filled' | 'cancelled' | 'rejected';
139
+ export interface OrderResult {
140
+ venueOrderId: string;
141
+ clientOrderId?: string;
142
+ status: OrderStatus;
143
+ filledQuantity: Money;
144
+ averagePrice?: Money;
145
+ raw?: unknown;
146
+ }
147
+ /** Notional value of an order, in the quote asset. */
148
+ export declare function notionalOf(request: OrderRequest, market: MarketSpec): Money;
149
+ /**
150
+ * Everything a venue would reject the order for, checked before it is sent.
151
+ *
152
+ * Locally rather than by submitting and reading the error, for three reasons: a
153
+ * round trip costs rate budget the caller may not have, the venue's error codes
154
+ * are inconsistent between market types, and a rejection after submission has
155
+ * already consumed an idempotency key.
156
+ */
157
+ export declare function validateOrder(request: OrderRequest, market: MarketSpec): void;
158
+ /**
159
+ * What a venue integration provides.
160
+ *
161
+ * Deliberately small. Everything that can be decided without the venue —
162
+ * validation, symbol spelling, step rounding — is decided above, so an adapter
163
+ * is transport plus translation and nothing else. That is what keeps a second
164
+ * venue cheap.
165
+ */
166
+ export interface VenueAdapter {
167
+ readonly venue: string;
168
+ /** The venue's own spelling of a canonical symbol. */
169
+ symbolFor(symbol: string, type: MarketType): string;
170
+ markets(type: MarketType): Promise<MarketSpec[]>;
171
+ placeOrder(request: OrderRequest, market: MarketSpec): Promise<OrderResult>;
172
+ cancelOrder(args: {
173
+ venueOrderId: string;
174
+ symbol: string;
175
+ type: MarketType;
176
+ }): Promise<void>;
177
+ openOrders(args: {
178
+ symbol?: string;
179
+ type: MarketType;
180
+ }): Promise<OrderResult[]>;
181
+ balances(type: MarketType): Promise<Money[]>;
182
+ }
183
+ /**
184
+ * Validate, then place.
185
+ *
186
+ * The one function a caller should use. Validating inside the adapter would
187
+ * make it every adapter's job, and the third one would get it wrong.
188
+ */
189
+ export declare function submitOrder(adapter: VenueAdapter, request: OrderRequest, market: MarketSpec): Promise<OrderResult>;
190
+ export declare const VERSION = "0.1.0";