@forgezero/runtime 0.1.1 → 0.1.3
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.
- package/README.md +27 -4
- package/dist/audit.js +262 -23
- package/dist/identity.d.ts +25 -0
- package/dist/identity.js +99 -3
- package/dist/jobs.d.ts +3 -2
- package/dist/jobs.js +288 -13
- package/dist/pipeline.d.ts +11 -37
- package/dist/pipeline.js +0 -27
- package/dist/queue.d.ts +17 -3
- package/dist/queue.js +86 -19
- package/dist/snp.d.ts +7 -6
- package/dist/snp.js +6 -1
- package/package.json +6 -9
- package/dist/finance/binance.d.ts +0 -27
- package/dist/finance/binance.js +0 -452
- package/dist/serial.d.ts +0 -54
- package/dist/serial.js +0 -40
package/dist/finance/binance.js
DELETED
|
@@ -1,452 +0,0 @@
|
|
|
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}×tamp=${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
|
-
};
|
package/dist/serial.d.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Run work for one key strictly in order, and give the caller the result back.
|
|
3
|
-
*
|
|
4
|
-
* ## Why this is not `@forgezero/runtime/queue`
|
|
5
|
-
*
|
|
6
|
-
* The queue is for work that happens LATER: it persists a message, hands it to
|
|
7
|
-
* a handler, retries, dead-letters. It is the right answer when the caller does
|
|
8
|
-
* not need the outcome — a deposit credit, a webhook, a scan.
|
|
9
|
-
*
|
|
10
|
-
* This is for work that happens NOW and whose result the caller returns. A
|
|
11
|
-
* request writing a ledger posting has to answer with the posting; enqueuing it
|
|
12
|
-
* would mean replying "accepted" to something the caller needs to have
|
|
13
|
-
* happened. Two different problems, and using the queue for this one would mean
|
|
14
|
-
* inventing a way to wait for a message — which is a worse version of this file.
|
|
15
|
-
*
|
|
16
|
-
* It was written twice before it was written once: the audit chain needed it to
|
|
17
|
-
* keep sequence numbers gapless, and accounts needed it to stop two credits
|
|
18
|
-
* interleaving. Two copies of a concurrency primitive is two chances to get it
|
|
19
|
-
* wrong, so it lives here.
|
|
20
|
-
*
|
|
21
|
-
* ## What it does NOT give you
|
|
22
|
-
*
|
|
23
|
-
* Ordering within ONE process. Two API nodes each hold their own chain, so a
|
|
24
|
-
* durable guarantee still needs a unique index on whatever must not happen
|
|
25
|
-
* twice. This makes the common case cheap and correct; the index makes every
|
|
26
|
-
* case correct. Anything relying on this alone across a cluster is relying on
|
|
27
|
-
* there being one node, which stops being true without warning.
|
|
28
|
-
*/
|
|
29
|
-
export interface SerialOptions {
|
|
30
|
-
/**
|
|
31
|
-
* Keys to keep chains for.
|
|
32
|
-
*
|
|
33
|
-
* A chain per key is a promise per key, and a process serving a million
|
|
34
|
-
* accounts would hold a million of them forever. Idle chains are dropped once
|
|
35
|
-
* settled, so the map holds only keys with work in flight.
|
|
36
|
-
*/
|
|
37
|
-
maxKeys?: number;
|
|
38
|
-
}
|
|
39
|
-
export declare function createKeyedSerial(options?: SerialOptions): {
|
|
40
|
-
/**
|
|
41
|
-
* Run `work` after everything already queued for this key.
|
|
42
|
-
*
|
|
43
|
-
* A rejection does NOT poison the chain: the tail is always replaced with a
|
|
44
|
-
* settled promise, so one transient failure cannot take every subsequent
|
|
45
|
-
* call for that key with it — which is how a single database blip becomes a
|
|
46
|
-
* permanently stuck account.
|
|
47
|
-
*/
|
|
48
|
-
run<T>(key: string, work: () => Promise<T>): Promise<T>;
|
|
49
|
-
/** How many keys have work in flight. For a health endpoint. */
|
|
50
|
-
size: () => number;
|
|
51
|
-
/** Wait for everything currently queued. Tests, and a graceful shutdown. */
|
|
52
|
-
drain: () => Promise<void>;
|
|
53
|
-
};
|
|
54
|
-
export type KeyedSerial = ReturnType<typeof createKeyedSerial>;
|
package/dist/serial.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
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/serial.ts
|
|
10
|
-
function createKeyedSerial(options = {}) {
|
|
11
|
-
const maxKeys = options.maxKeys ?? 1e4;
|
|
12
|
-
const chains = new Map;
|
|
13
|
-
return {
|
|
14
|
-
run(key, work) {
|
|
15
|
-
const previous = chains.get(key) ?? Promise.resolve();
|
|
16
|
-
const next = previous.then(work, work);
|
|
17
|
-
const settled = next.then(() => {
|
|
18
|
-
return;
|
|
19
|
-
}, () => {
|
|
20
|
-
return;
|
|
21
|
-
});
|
|
22
|
-
chains.set(key, settled);
|
|
23
|
-
settled.then(() => {
|
|
24
|
-
if (chains.get(key) === settled)
|
|
25
|
-
chains.delete(key);
|
|
26
|
-
});
|
|
27
|
-
if (chains.size > maxKeys) {
|
|
28
|
-
console.warn(`[serial] ${chains.size} keys in flight, above the ${maxKeys} guideline.`);
|
|
29
|
-
}
|
|
30
|
-
return next;
|
|
31
|
-
},
|
|
32
|
-
size: () => chains.size,
|
|
33
|
-
drain: async () => {
|
|
34
|
-
await Promise.allSettled([...chains.values()]);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
export {
|
|
39
|
-
createKeyedSerial
|
|
40
|
-
};
|