@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,452 @@
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/venues.ts
155
+ var MARKET_TYPES = ["spot", "margin", "futures"];
156
+ var ORDER_SIDES = ["buy", "sell"];
157
+ var ORDER_TYPES = ["market", "limit", "stop-limit"];
158
+ var TIME_IN_FORCE = ["gtc", "ioc", "fok"];
159
+ var VENUES = [
160
+ {
161
+ key: "binance",
162
+ label: "Binance",
163
+ markets: ["spot", "margin", "futures"],
164
+ rateLimit: { perMinute: 6000, counts: "weight" },
165
+ clientOrderIds: true,
166
+ note: "Publishes no AAAA records, so every endpoint is IPv4-only and the budget is per IP."
167
+ }
168
+ ];
169
+ var venue = (key) => VENUES.find((entry) => entry.key === key);
170
+ var venuesFor = (type) => VENUES.filter((entry) => entry.markets.includes(type));
171
+ var symbolOf = (base, quote) => `${base.toUpperCase()}/${quote.toUpperCase()}`;
172
+ function parseSymbol(symbol) {
173
+ const [base, quote] = symbol.toUpperCase().split("/");
174
+ if (!base || !quote)
175
+ throw new VenueError("BAD_SYMBOL", `"${symbol}" is not BASE/QUOTE.`);
176
+ return { base, quote };
177
+ }
178
+
179
+ class VenueError extends Error {
180
+ code;
181
+ constructor(code, message) {
182
+ super(message);
183
+ this.code = code;
184
+ this.name = "VenueError";
185
+ }
186
+ }
187
+ function notionalOf(request, market) {
188
+ const price = request.price;
189
+ if (!price)
190
+ return parseAmount("0", market.quote);
191
+ const quantity = Number(formatAmount(request.quantity));
192
+ const priceText = formatAmount(price);
193
+ return parseAmount((quantity * Number(priceText)).toFixed(0) === "NaN" ? "0" : String(quantity * Number(priceText)), market.quote);
194
+ }
195
+ function validateOrder(request, market) {
196
+ const spec = venue(request.venue);
197
+ if (!spec)
198
+ throw new VenueError("UNKNOWN_VENUE", `Unknown venue "${request.venue}".`);
199
+ if (!spec.markets.includes(request.type)) {
200
+ throw new VenueError("MARKET_UNSUPPORTED", `${spec.label} does not offer ${request.type}.`);
201
+ }
202
+ if (market.venue !== request.venue || market.type !== request.type || market.symbol !== request.symbol) {
203
+ throw new VenueError("UNKNOWN_MARKET", "The market does not describe this order.");
204
+ }
205
+ if (request.orderType !== "market" && !request.price) {
206
+ throw new VenueError("PRICE_REQUIRED", `A ${request.orderType} order needs a price.`);
207
+ }
208
+ const snapped = toStep(request.quantity, market.lotStep);
209
+ if (compare(snapped, request.quantity) !== 0) {
210
+ throw new VenueError("LOT_STEP", `Quantity ${formatAmount(request.quantity, { trim: true })} is not a multiple of ${market.lotStep}.`);
211
+ }
212
+ if (request.price) {
213
+ const onTick = toStep(request.price, market.tickStep);
214
+ if (compare(onTick, request.price) !== 0) {
215
+ throw new VenueError("TICK_STEP", `Price ${formatAmount(request.price, { trim: true })} is not a multiple of ${market.tickStep}.`);
216
+ }
217
+ const notional = notionalOf(request, market);
218
+ if (compare(notional, parseAmount(market.minNotional, market.quote)) < 0) {
219
+ throw new VenueError("MIN_NOTIONAL", `Order is worth ${formatAmount(notional, { trim: true })} ${market.quote}; the minimum is ${market.minNotional}.`);
220
+ }
221
+ }
222
+ if (request.leverage !== undefined) {
223
+ if (request.type === "spot" || market.maxLeverage === undefined) {
224
+ throw new VenueError("LEVERAGE_UNSUPPORTED", `${request.type} markets have no leverage.`);
225
+ }
226
+ if (request.leverage < 1 || request.leverage > market.maxLeverage) {
227
+ throw new VenueError("LEVERAGE_TOO_HIGH", `Leverage ${request.leverage}× exceeds the ${market.maxLeverage}× maximum on this market.`);
228
+ }
229
+ }
230
+ }
231
+ async function submitOrder(adapter, request, market) {
232
+ validateOrder(request, market);
233
+ return adapter.placeOrder(request, market);
234
+ }
235
+ var VERSION2 = "0.1.0";
236
+
237
+ // src/finance/binance.ts
238
+ import { createHttpClient, binanceWeight } from "@forgezero/providers/http";
239
+ var BUDGET_HOST = "binance";
240
+ var DEFAULT_HOSTS = {
241
+ spot: "https://api.binance.com",
242
+ margin: "https://api.binance.com",
243
+ futures: "https://fapi.binance.com"
244
+ };
245
+ var binanceSymbol = (symbol) => {
246
+ const { base, quote } = parseSymbol(symbol);
247
+ return `${base}${quote}`;
248
+ };
249
+ var PATHS = {
250
+ spot: {
251
+ order: "/api/v3/order",
252
+ openOrders: "/api/v3/openOrders",
253
+ account: "/api/v3/account",
254
+ exchangeInfo: "/api/v3/exchangeInfo"
255
+ },
256
+ margin: {
257
+ order: "/sapi/v1/margin/order",
258
+ openOrders: "/sapi/v1/margin/openOrders",
259
+ account: "/sapi/v1/margin/account",
260
+ exchangeInfo: "/api/v3/exchangeInfo"
261
+ },
262
+ futures: {
263
+ order: "/fapi/v1/order",
264
+ openOrders: "/fapi/v1/openOrders",
265
+ account: "/fapi/v2/account",
266
+ exchangeInfo: "/fapi/v1/exchangeInfo"
267
+ }
268
+ };
269
+ function toOrderStatus(status) {
270
+ switch (status) {
271
+ case "NEW":
272
+ case "PENDING_NEW":
273
+ return "accepted";
274
+ case "PARTIALLY_FILLED":
275
+ return "partial";
276
+ case "FILLED":
277
+ return "filled";
278
+ case "CANCELED":
279
+ case "PENDING_CANCEL":
280
+ case "EXPIRED":
281
+ case "EXPIRED_IN_MATCH":
282
+ return "cancelled";
283
+ default:
284
+ return "rejected";
285
+ }
286
+ }
287
+ var SIDE = { buy: "BUY", sell: "SELL" };
288
+ var TYPE = { market: "MARKET", limit: "LIMIT", "stop-limit": "STOP_LOSS_LIMIT" };
289
+ async function sign(secret, query) {
290
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
291
+ const mac = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(query));
292
+ return [...new Uint8Array(mac)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
293
+ }
294
+ function createBinanceAdapter(options) {
295
+ const hosts = { ...DEFAULT_HOSTS, ...options.hosts };
296
+ const recvWindow = options.recvWindowMs ?? 5000;
297
+ const now = options.now ?? Date.now;
298
+ const clients = new Map;
299
+ const clientFor = (type) => {
300
+ const base = hosts[type];
301
+ const existing = clients.get(base);
302
+ if (existing)
303
+ return existing;
304
+ const client = createHttpClient({
305
+ baseUrl: base,
306
+ fetch: options.fetch,
307
+ costOf: binanceWeight,
308
+ budget: { host: BUDGET_HOST, limit: 6000, windowMs: 60000, defaultCost: 1, headroom: 0.9 }
309
+ });
310
+ clients.set(base, client);
311
+ return client;
312
+ };
313
+ async function signed(args) {
314
+ const entries = Object.entries(args.params).filter(([, value]) => value !== undefined);
315
+ const base = new URLSearchParams(entries.map(([name, value]) => [name, String(value)])).toString();
316
+ const withTiming = `${base}${base ? "&" : ""}recvWindow=${recvWindow}&timestamp=${now()}`;
317
+ const signature = await sign(options.credentials.apiSecret, withTiming);
318
+ const response = await clientFor(args.type).call({
319
+ path: `${args.path}?${withTiming}&signature=${signature}`,
320
+ method: args.method,
321
+ weight: args.weight,
322
+ headers: { "X-MBX-APIKEY": options.credentials.apiKey }
323
+ });
324
+ return response.body;
325
+ }
326
+ return {
327
+ venue: "binance",
328
+ symbolFor: (symbol) => binanceSymbol(symbol),
329
+ async markets(type) {
330
+ const info = await clientFor(type).call({ path: PATHS[type].exchangeInfo, weight: 20 });
331
+ return (info.body.symbols ?? []).filter((entry) => entry.status === "TRADING").map((entry) => {
332
+ const filter = (name) => entry.filters.find((candidate) => candidate.filterType === name);
333
+ return {
334
+ venue: "binance",
335
+ type,
336
+ symbol: `${entry.baseAsset}/${entry.quoteAsset}`,
337
+ base: entry.baseAsset,
338
+ quote: entry.quoteAsset,
339
+ lotStep: filter("LOT_SIZE")?.stepSize ?? "0.00000001",
340
+ tickStep: filter("PRICE_FILTER")?.tickSize ?? "0.00000001",
341
+ minNotional: filter("NOTIONAL")?.minNotional ?? filter("MIN_NOTIONAL")?.minNotional ?? "0",
342
+ ...type === "spot" ? {} : { maxLeverage: 125 }
343
+ };
344
+ });
345
+ },
346
+ async placeOrder(request, market) {
347
+ if (request.type === "futures" && request.leverage) {
348
+ await signed({
349
+ type: "futures",
350
+ path: "/fapi/v1/leverage",
351
+ method: "POST",
352
+ params: { symbol: binanceSymbol(request.symbol), leverage: request.leverage }
353
+ });
354
+ }
355
+ const raw = await signed({
356
+ type: request.type,
357
+ path: PATHS[request.type].order,
358
+ method: "POST",
359
+ weight: 1,
360
+ params: {
361
+ symbol: binanceSymbol(request.symbol),
362
+ side: SIDE[request.side],
363
+ type: TYPE[request.orderType],
364
+ quantity: formatAmount(request.quantity, { trim: true }),
365
+ price: request.price ? formatAmount(request.price, { trim: true }) : undefined,
366
+ stopPrice: request.stopPrice ? formatAmount(request.stopPrice, { trim: true }) : undefined,
367
+ timeInForce: request.orderType === "market" ? undefined : (request.timeInForce ?? "gtc").toUpperCase(),
368
+ newClientOrderId: request.clientOrderId,
369
+ ...request.type === "margin" ? { sideEffectType: "NO_SIDE_EFFECT" } : {},
370
+ ...request.dryRun ? { test: "true" } : {}
371
+ }
372
+ });
373
+ return readOrder(raw, market);
374
+ },
375
+ async cancelOrder(args) {
376
+ await signed({
377
+ type: args.type,
378
+ path: PATHS[args.type].order,
379
+ method: "DELETE",
380
+ params: { symbol: binanceSymbol(args.symbol), orderId: args.venueOrderId }
381
+ });
382
+ },
383
+ async openOrders(args) {
384
+ const raw = await signed({
385
+ type: args.type,
386
+ path: PATHS[args.type].openOrders,
387
+ method: "GET",
388
+ weight: args.symbol ? 3 : 40,
389
+ params: { symbol: args.symbol ? binanceSymbol(args.symbol) : undefined }
390
+ });
391
+ return (raw ?? []).map((entry) => readOrder(entry, undefined));
392
+ },
393
+ async balances(type) {
394
+ const raw = await signed({ type, path: PATHS[type].account, method: "GET", weight: 10, params: {} });
395
+ const rows = raw.balances ?? raw.userAssets ?? (raw.assets ?? []).map((entry) => ({ asset: entry.asset, free: entry.availableBalance }));
396
+ return rows.filter((entry) => Number(entry.free) > 0).map((entry) => {
397
+ try {
398
+ return parseAmount(entry.free, entry.asset);
399
+ } catch {
400
+ return null;
401
+ }
402
+ }).filter((amount) => amount !== null);
403
+ }
404
+ };
405
+ }
406
+ function readOrder(raw, market) {
407
+ const asset = market?.base ?? "BTC";
408
+ const executed = String(raw.executedQty ?? raw.origQty ?? "0");
409
+ let filledQuantity;
410
+ try {
411
+ filledQuantity = parseAmount(executed, asset);
412
+ } catch {
413
+ filledQuantity = zero(asset);
414
+ }
415
+ const quoteFilled = Number(raw.cummulativeQuoteQty ?? 0);
416
+ const filled = Number(executed);
417
+ return {
418
+ venueOrderId: String(raw.orderId ?? ""),
419
+ clientOrderId: raw.clientOrderId ? String(raw.clientOrderId) : undefined,
420
+ status: toOrderStatus(String(raw.status ?? "NEW")),
421
+ filledQuantity,
422
+ ...market && filled > 0 && quoteFilled > 0 ? {
423
+ averagePrice: (() => {
424
+ try {
425
+ return parseAmount((quoteFilled / filled).toFixed(assetSpec(market.quote).decimals), market.quote);
426
+ } catch {
427
+ return;
428
+ }
429
+ })()
430
+ } : {},
431
+ raw
432
+ };
433
+ }
434
+ function readBinanceError(error) {
435
+ const body = error.body;
436
+ if (!body?.code)
437
+ return;
438
+ const known = {
439
+ [-1013]: ["MIN_NOTIONAL", "The order is below the venue minimum, or off its lot or tick step."],
440
+ [-2010]: ["MIN_NOTIONAL", "Rejected: insufficient balance, or below the minimum."],
441
+ [-1111]: ["LOT_STEP", "More decimal places than this market accepts."],
442
+ [-1121]: ["UNKNOWN_MARKET", "That symbol is not traded on this venue."]
443
+ };
444
+ const match = known[body.code];
445
+ return match ? new VenueError(match[0], `${match[1]} (${body.msg})`) : undefined;
446
+ }
447
+ export {
448
+ toOrderStatus,
449
+ readBinanceError,
450
+ createBinanceAdapter,
451
+ binanceSymbol
452
+ };
@@ -0,0 +1,130 @@
1
+ import { depositAddress, toChecksumAddress } from './custody';
2
+ import type { AddressScheme, ChainSpec } from './chain';
3
+ /**
4
+ * One deposit address per user per chain, derived rather than allocated.
5
+ *
6
+ * The naive design gives each user a real account created on-chain. That costs
7
+ * gas per user before they have deposited anything, so onboarding a hundred
8
+ * thousand people costs real money for the ones who never fund. Worse, the
9
+ * address list becomes state: lose it and the deposits keep arriving somewhere
10
+ * nobody is watching.
11
+ *
12
+ * A counterfactual CREATE2 address costs nothing. The contract that would live
13
+ * there is not deployed until there is something to sweep out of it, and until
14
+ * then it is a pure function of (factory, salt, init code hash). The whole
15
+ * address book is therefore reproducible from a backup of three values, and
16
+ * "restore the deposit addresses" stops being a recovery procedure at all.
17
+ *
18
+ * ## What this adds over `custody.ts`
19
+ *
20
+ * Almost nothing, deliberately. `custody.depositAddress` already mirrors the
21
+ * deployed factory byte for byte, and `chain.validateAddress` already knows the
22
+ * shape of every scheme. What was missing is the CONVENTION on top: which salt
23
+ * a given user on a given chain gets, and the refusal to answer at all for a
24
+ * chain whose scheme has no counterfactual derivation.
25
+ *
26
+ * Re-exporting rather than reimplementing is the point. Two functions that both
27
+ * compute a deposit address are two chances to disagree, and the one that
28
+ * disagrees sends money somewhere unreachable.
29
+ */
30
+ export { validateAddress, type AddressScheme } from './chain';
31
+ /**
32
+ * Which schemes can derive an address without touching the chain.
33
+ *
34
+ * Only EVM, today. Bitcoin's equivalent is an HD derivation path and Solana's
35
+ * is a program-derived address — both are real designs and neither is this one,
36
+ * so a chain using them must not silently fall through to a formula written for
37
+ * something else. Listing what IS supported rather than excluding what is not
38
+ * means adding a chain fails closed.
39
+ */
40
+ export declare const COUNTERFACTUAL_SCHEMES: readonly AddressScheme[];
41
+ export declare class AddressError extends Error {
42
+ readonly code: 'SCHEME_UNSUPPORTED' | 'CHAIN_DISABLED' | 'MISSING_DEPLOYMENT' | 'MALFORMED';
43
+ constructor(code: 'SCHEME_UNSUPPORTED' | 'CHAIN_DISABLED' | 'MISSING_DEPLOYMENT' | 'MALFORMED', message: string);
44
+ }
45
+ /**
46
+ * Where a chain's factory lives, and what it deploys.
47
+ *
48
+ * `proxyInitCodeHash` is read from the DEPLOYED factory rather than compiled in.
49
+ * A Solidity version bump changes the init code and therefore every address
50
+ * derived from it; a hardcoded constant would keep deriving addresses the live
51
+ * factory cannot sweep, and the failure appears only once somebody has already
52
+ * sent funds to one.
53
+ */
54
+ export interface ChainDeployment {
55
+ chain: string;
56
+ factory: string;
57
+ proxyInitCodeHash: string;
58
+ }
59
+ /**
60
+ * The salt for one owner on one chain.
61
+ *
62
+ * The CHAIN is inside the salt even though each chain has its own factory. Two
63
+ * chains that deploy the factory to the same address — which happens, because a
64
+ * deterministic deployer is the normal way to deploy one — would otherwise
65
+ * derive the same deposit address for the same user on both. That is not
66
+ * immediately a loss, but it makes the address ambiguous in every log, alert
67
+ * and support conversation afterwards, and it costs nothing to prevent here.
68
+ */
69
+ export declare const depositSaltFor: (args: {
70
+ chain: string;
71
+ owner: string;
72
+ }) => string;
73
+ /**
74
+ * The deposit address for one owner on one chain.
75
+ *
76
+ * Pure: same inputs, same answer, forever. That property is what lets a scanner
77
+ * recompute the whole address book on every pass instead of trusting a stored
78
+ * list, and what makes a stored list a cache rather than a record.
79
+ */
80
+ export declare function deriveDepositAddress(args: {
81
+ chain: ChainSpec;
82
+ deployment: ChainDeployment;
83
+ owner: string;
84
+ }): string;
85
+ /**
86
+ * The raw CREATE2 formula, exposed for anything deriving something else.
87
+ *
88
+ * A thin alias over `custody.depositAddress` so callers with their own salt
89
+ * convention — a per-invoice address, say — do not reach past this module and
90
+ * end up with a second copy of the formula.
91
+ */
92
+ export declare const create2Address: typeof depositAddress;
93
+ /**
94
+ * Every address to watch on one chain, for a set of owners.
95
+ *
96
+ * The scanner needs the whole set on every pass, keyed for lookup rather than
97
+ * listed: matching a block's transfers against an array is a scan per transfer,
98
+ * and a busy block against a large user base is where that becomes the reason a
99
+ * deposit is late.
100
+ *
101
+ * Keyed LOWERCASE. Chains report addresses in whatever case they please, and a
102
+ * checksum-cased key silently fails to match a lowercase one — a deposit that
103
+ * arrives, is seen, and is not credited.
104
+ */
105
+ export declare function depositAddressBook(args: {
106
+ chain: ChainSpec;
107
+ deployment: ChainDeployment;
108
+ owners: readonly string[];
109
+ }): Map<string, string>;
110
+ /**
111
+ * Whose address is this, if anyone's?
112
+ *
113
+ * Case-insensitive for the reason above. Returns `null` rather than throwing:
114
+ * most addresses in any block belong to nobody here, and that is the normal
115
+ * case rather than an error.
116
+ */
117
+ export declare const ownerOfAddress: (book: Map<string, string>, address: string) => string | null;
118
+ /**
119
+ * Check a derived address against what the chain actually reports.
120
+ *
121
+ * Worth doing once per deployment, not per address: if these disagree the
122
+ * formula and the factory have diverged, and every address derived since is
123
+ * wrong. Comparing case-insensitively because the checksum casing is a display
124
+ * convention — a mismatch that is only casing is not a divergence.
125
+ */
126
+ export declare function assertDerivationMatches(args: {
127
+ derived: string;
128
+ reportedByChain: string;
129
+ }): void;
130
+ export { toChecksumAddress };