@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.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/contracts/foundry.toml +9 -0
- package/contracts/src/ColdVault.sol +206 -0
- package/contracts/src/DepositFactory.sol +202 -0
- package/contracts/src/DepositProxy.sol +72 -0
- package/contracts/src/IERC20.sol +7 -0
- package/contracts/src/MockTokens.sol +32 -0
- package/contracts/src/SafeTransferLib.sol +31 -0
- package/contracts/test/Custody.t.sol +361 -0
- package/contracts/test/Vectors.t.sol +45 -0
- package/dist/audit.d.ts +265 -0
- package/dist/audit.js +291 -0
- package/dist/backup.d.ts +243 -0
- package/dist/backup.js +302 -0
- package/dist/calendar.d.ts +136 -0
- package/dist/calendar.js +129 -0
- package/dist/compliance.d.ts +172 -0
- package/dist/compliance.js +168 -0
- package/dist/finance/binance.d.ts +27 -0
- package/dist/finance/binance.js +452 -0
- package/dist/finance/chain-addresses.d.ts +130 -0
- package/dist/finance/chain-addresses.js +462 -0
- package/dist/finance/chain-deposits.d.ts +193 -0
- package/dist/finance/chain-deposits.js +596 -0
- package/dist/finance/chain-reconcile.d.ts +112 -0
- package/dist/finance/chain-reconcile.js +76 -0
- package/dist/finance/chain-withdrawals.d.ts +223 -0
- package/dist/finance/chain-withdrawals.js +631 -0
- package/dist/finance/chain.d.ts +116 -0
- package/dist/finance/chain.js +316 -0
- package/dist/finance/commission.d.ts +155 -0
- package/dist/finance/commission.js +419 -0
- package/dist/finance/custody.d.ts +68 -0
- package/dist/finance/custody.js +107 -0
- package/dist/finance/derive.d.ts +115 -0
- package/dist/finance/derive.js +116 -0
- package/dist/finance/discounts.d.ts +98 -0
- package/dist/finance/discounts.js +90 -0
- package/dist/finance/ledger.d.ts +221 -0
- package/dist/finance/ledger.js +308 -0
- package/dist/finance/market.d.ts +209 -0
- package/dist/finance/market.js +112 -0
- package/dist/finance/money.d.ts +118 -0
- package/dist/finance/money.js +176 -0
- package/dist/finance/rates.d.ts +178 -0
- package/dist/finance/rates.js +292 -0
- package/dist/finance/storage.d.ts +113 -0
- package/dist/finance/storage.js +226 -0
- package/dist/finance/tax.d.ts +132 -0
- package/dist/finance/tax.js +291 -0
- package/dist/finance/transfers.d.ts +153 -0
- package/dist/finance/transfers.js +292 -0
- package/dist/finance/venues.d.ts +190 -0
- package/dist/finance/venues.js +251 -0
- package/dist/identity.d.ts +115 -0
- package/dist/identity.js +111 -0
- package/dist/importers.d.ts +87 -0
- package/dist/importers.js +250 -0
- package/dist/jobs.d.ts +171 -0
- package/dist/jobs.js +250 -0
- package/dist/notify-templates.d.ts +11 -0
- package/dist/notify-templates.js +254 -0
- package/dist/notify.d.ts +172 -0
- package/dist/notify.js +122 -0
- package/dist/openssh.d.ts +36 -0
- package/dist/openssh.js +106 -0
- package/dist/otpauth.d.ts +57 -0
- package/dist/otpauth.js +223 -0
- package/dist/outbox.d.ts +234 -0
- package/dist/outbox.js +236 -0
- package/dist/passkey.d.ts +120 -0
- package/dist/passkey.js +105 -0
- package/dist/phrase.d.ts +87 -0
- package/dist/phrase.js +87 -0
- package/dist/pipeline.d.ts +137 -0
- package/dist/pipeline.js +121 -0
- package/dist/queue.d.ts +243 -0
- package/dist/queue.js +246 -0
- package/dist/schema-typebox.d.ts +24 -0
- package/dist/schema-typebox.js +201 -0
- package/dist/schema.d.ts +134 -0
- package/dist/schema.js +169 -0
- package/dist/serial.d.ts +54 -0
- package/dist/serial.js +40 -0
- package/dist/slip10.d.ts +37 -0
- package/dist/slip10.js +74 -0
- package/dist/snp.d.ts +115 -0
- package/dist/snp.js +109 -0
- package/dist/ssh-agent.d.ts +70 -0
- package/dist/ssh-agent.js +141 -0
- package/dist/ssh-cert.d.ts +73 -0
- package/dist/ssh-cert.js +111 -0
- package/dist/totp.d.ts +104 -0
- package/dist/totp.js +143 -0
- package/package.json +248 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screening — the stage that already has a seat.
|
|
3
|
+
*
|
|
4
|
+
* `@forgezero/runtime/finance/transfers` reserved position zero and has been running a
|
|
5
|
+
* pass-everything placeholder there since before this file existed. That
|
|
6
|
+
* ordering is the whole reason this is a drop-in: nothing about deposits or
|
|
7
|
+
* withdrawals changes, and the audit trail already records the stage running.
|
|
8
|
+
*
|
|
9
|
+
* ## Screening is a DECISION RECORD, not a boolean
|
|
10
|
+
*
|
|
11
|
+
* The value of compliance work six months later is being able to say why a
|
|
12
|
+
* transfer was allowed — not just that it was. A screen that returns true or
|
|
13
|
+
* false gives an auditor nothing, so every check produces a verdict with the
|
|
14
|
+
* rules that fired, the risk it scored and the list version it was screened
|
|
15
|
+
* against. That record is the deliverable; the refusal is a side effect.
|
|
16
|
+
*
|
|
17
|
+
* ## Fail CLOSED on an unavailable list, and say so
|
|
18
|
+
*
|
|
19
|
+
* A sanctions list that cannot be reached is not "no hits". Treating an
|
|
20
|
+
* unreachable provider as a pass is how sanctioned money moves during an
|
|
21
|
+
* outage, and it is invisible afterwards because the trail says allowed. So an
|
|
22
|
+
* unavailable list refuses with a retryable status — the transfer waits rather
|
|
23
|
+
* than proceeding unscreened.
|
|
24
|
+
*
|
|
25
|
+
* ## What this deliberately does NOT do
|
|
26
|
+
*
|
|
27
|
+
* No list is bundled. A sanctions list embedded in a package is out of date the
|
|
28
|
+
* day it publishes, and being out of date is the only failure mode that
|
|
29
|
+
* matters. The provider is injected, and `staticList` exists for tests and for
|
|
30
|
+
* an operator's own denylist — never as the primary source.
|
|
31
|
+
*/
|
|
32
|
+
export declare class ComplianceError extends Error {
|
|
33
|
+
readonly code: 'LIST_UNAVAILABLE' | 'BAD_SUBJECT';
|
|
34
|
+
constructor(code: 'LIST_UNAVAILABLE' | 'BAD_SUBJECT', message: string);
|
|
35
|
+
}
|
|
36
|
+
export declare const RISK_LEVELS: readonly ["low", "medium", "high", "prohibited"];
|
|
37
|
+
export type RiskLevel = (typeof RISK_LEVELS)[number];
|
|
38
|
+
export declare const VERIFICATION_TIERS: readonly ["none", "basic", "verified", "enhanced"];
|
|
39
|
+
export type VerificationTier = (typeof VERIFICATION_TIERS)[number];
|
|
40
|
+
export interface Subject {
|
|
41
|
+
/** The account being screened. */
|
|
42
|
+
owner: string;
|
|
43
|
+
/** Counterparty address, for a transfer. */
|
|
44
|
+
address?: string;
|
|
45
|
+
network?: string;
|
|
46
|
+
/** Value in USD, so a threshold rule can fire. */
|
|
47
|
+
usdValue?: number;
|
|
48
|
+
direction?: 'deposit' | 'withdrawal';
|
|
49
|
+
/** How far the account has verified. Drives the tier rules. */
|
|
50
|
+
tier?: VerificationTier;
|
|
51
|
+
/** Anything a rule wants — country, name, date of birth. */
|
|
52
|
+
attributes?: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
export interface ListEntry {
|
|
55
|
+
/** Address, name or identifier this entry matches. */
|
|
56
|
+
value: string;
|
|
57
|
+
kind: 'address' | 'name' | 'country';
|
|
58
|
+
/** Which list it came from — OFAC, an internal denylist, a chain analytics feed. */
|
|
59
|
+
source: string;
|
|
60
|
+
reason?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface ScreeningList {
|
|
63
|
+
/** Bumped whenever the list content changes. Recorded on every verdict. */
|
|
64
|
+
readonly version: string;
|
|
65
|
+
/** Throws `ComplianceError('LIST_UNAVAILABLE')` rather than returning empty. */
|
|
66
|
+
match(subject: Subject): Promise<ListEntry[]>;
|
|
67
|
+
}
|
|
68
|
+
export interface RuleHit {
|
|
69
|
+
rule: string;
|
|
70
|
+
risk: RiskLevel;
|
|
71
|
+
detail: string;
|
|
72
|
+
}
|
|
73
|
+
export interface Rule {
|
|
74
|
+
name: string;
|
|
75
|
+
/** Returns a hit, or undefined when the rule does not fire. */
|
|
76
|
+
check(subject: Subject): RuleHit | undefined | Promise<RuleHit | undefined>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A transfer above a threshold for the tier the account has reached.
|
|
80
|
+
*
|
|
81
|
+
* Tiered rather than one global limit, because the whole point of verification
|
|
82
|
+
* is that it raises what an account may move. A single threshold means either
|
|
83
|
+
* verified accounts are throttled or unverified ones are not.
|
|
84
|
+
*/
|
|
85
|
+
export declare const tierLimitRule: (limits: Partial<Record<VerificationTier, number>>) => Rule;
|
|
86
|
+
/** A jurisdiction the platform will not serve. */
|
|
87
|
+
export declare const countryRule: (prohibited: readonly string[]) => Rule;
|
|
88
|
+
/**
|
|
89
|
+
* A deposit from an address that has never been seen, above a threshold.
|
|
90
|
+
*
|
|
91
|
+
* Weak on its own and useful in combination — it is the kind of signal that
|
|
92
|
+
* raises a transfer to review rather than refusing it, which is why it scores
|
|
93
|
+
* `medium` and not higher.
|
|
94
|
+
*/
|
|
95
|
+
export declare const newCounterpartyRule: (args: {
|
|
96
|
+
aboveUsd: number;
|
|
97
|
+
isKnown: (address: string) => boolean | Promise<boolean>;
|
|
98
|
+
}) => Rule;
|
|
99
|
+
export interface Verdict {
|
|
100
|
+
decision: 'allow' | 'review' | 'refuse';
|
|
101
|
+
risk: RiskLevel;
|
|
102
|
+
hits: RuleHit[];
|
|
103
|
+
listMatches: ListEntry[];
|
|
104
|
+
/** Which list version this was screened against. The auditable part. */
|
|
105
|
+
listVersion: string;
|
|
106
|
+
screenedAtMs: number;
|
|
107
|
+
subject: Subject;
|
|
108
|
+
}
|
|
109
|
+
export interface ScreenOptions {
|
|
110
|
+
list?: ScreeningList;
|
|
111
|
+
rules?: readonly Rule[];
|
|
112
|
+
/** At or above this, the transfer is held for a human rather than refused. */
|
|
113
|
+
reviewAt?: RiskLevel;
|
|
114
|
+
now?: () => number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Screen a subject and produce a verdict.
|
|
118
|
+
*
|
|
119
|
+
* A list match is always `prohibited` — that is what a sanctions list means,
|
|
120
|
+
* and softening it to "high risk, review it" is the decision nobody should be
|
|
121
|
+
* able to make quietly in a config file. Rules can only ever raise the level
|
|
122
|
+
* arrived at, never lower it.
|
|
123
|
+
*/
|
|
124
|
+
export declare function screen(subject: Subject, options?: ScreenOptions): Promise<Verdict>;
|
|
125
|
+
export interface StageOptions extends ScreenOptions {
|
|
126
|
+
/** Called for every verdict, allowed or not. The case record. */
|
|
127
|
+
record?: (verdict: Verdict) => void | Promise<void>;
|
|
128
|
+
/** Whether a `review` verdict has already been approved by a human. */
|
|
129
|
+
isApproved?: (subject: Subject) => boolean | Promise<boolean>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The screening stage, shaped for `@forgezero/runtime/finance/transfers`.
|
|
133
|
+
*
|
|
134
|
+
* Drops into the seat that was reserved at order zero. Nothing else changes —
|
|
135
|
+
* which was the entire point of building the pipeline before the policy.
|
|
136
|
+
*
|
|
137
|
+
* EVERY verdict is recorded, including allowances. A trail of refusals answers
|
|
138
|
+
* "what did we stop" and not "what did we decide", and the second question is
|
|
139
|
+
* the one an auditor asks.
|
|
140
|
+
*/
|
|
141
|
+
export declare function screeningStage(options?: StageOptions): {
|
|
142
|
+
name: string;
|
|
143
|
+
order: number;
|
|
144
|
+
run(transfer: {
|
|
145
|
+
owner: string;
|
|
146
|
+
direction: "deposit" | "withdrawal";
|
|
147
|
+
address?: string;
|
|
148
|
+
network?: string;
|
|
149
|
+
amount: {
|
|
150
|
+
units: bigint;
|
|
151
|
+
asset: string;
|
|
152
|
+
};
|
|
153
|
+
context?: Record<string, unknown>;
|
|
154
|
+
}): Promise<{
|
|
155
|
+
screenedAtMs: number;
|
|
156
|
+
risk: "low" | "medium" | "high" | "prohibited";
|
|
157
|
+
listVersion: string;
|
|
158
|
+
}>;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* A list held in memory.
|
|
162
|
+
*
|
|
163
|
+
* For tests, and for an operator's own denylist alongside a real feed — never
|
|
164
|
+
* as the primary source. A sanctions list bundled into a package is out of date
|
|
165
|
+
* the day it publishes, and being out of date is the only failure mode that
|
|
166
|
+
* matters here.
|
|
167
|
+
*/
|
|
168
|
+
export declare function staticList(entries: readonly ListEntry[], version?: string): ScreeningList;
|
|
169
|
+
/** Combine several lists. Any one being unavailable fails the whole screen. */
|
|
170
|
+
export declare function combineLists(...lists: readonly ScreeningList[]): ScreeningList;
|
|
171
|
+
/** A list that is not configured yet. Refuses, so nothing runs unscreened by accident. */
|
|
172
|
+
export declare const unavailableList: (reason: string) => ScreeningList;
|
|
@@ -0,0 +1,168 @@
|
|
|
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/compliance.ts
|
|
10
|
+
import { Refusal } from "@forgezero/access";
|
|
11
|
+
|
|
12
|
+
class ComplianceError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = "ComplianceError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
var RISK_LEVELS = ["low", "medium", "high", "prohibited"];
|
|
21
|
+
var VERIFICATION_TIERS = ["none", "basic", "verified", "enhanced"];
|
|
22
|
+
var tierLimitRule = (limits) => ({
|
|
23
|
+
name: "tier.limit",
|
|
24
|
+
check(subject) {
|
|
25
|
+
const tier = subject.tier ?? "none";
|
|
26
|
+
const limit = limits[tier];
|
|
27
|
+
if (limit === undefined || subject.usdValue === undefined)
|
|
28
|
+
return;
|
|
29
|
+
if (subject.usdValue <= limit)
|
|
30
|
+
return;
|
|
31
|
+
return {
|
|
32
|
+
rule: "tier.limit",
|
|
33
|
+
risk: "high",
|
|
34
|
+
detail: `$${subject.usdValue} exceeds the $${limit} limit for a ${tier} account.`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
var countryRule = (prohibited) => ({
|
|
39
|
+
name: "country.prohibited",
|
|
40
|
+
check(subject) {
|
|
41
|
+
const country = subject.attributes?.country?.toUpperCase();
|
|
42
|
+
if (!country || !prohibited.includes(country))
|
|
43
|
+
return;
|
|
44
|
+
return {
|
|
45
|
+
rule: "country.prohibited",
|
|
46
|
+
risk: "prohibited",
|
|
47
|
+
detail: `${country} is not served.`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
var newCounterpartyRule = (args) => ({
|
|
52
|
+
name: "counterparty.new",
|
|
53
|
+
async check(subject) {
|
|
54
|
+
if (!subject.address || (subject.usdValue ?? 0) < args.aboveUsd)
|
|
55
|
+
return;
|
|
56
|
+
if (await args.isKnown(subject.address))
|
|
57
|
+
return;
|
|
58
|
+
return {
|
|
59
|
+
rule: "counterparty.new",
|
|
60
|
+
risk: "medium",
|
|
61
|
+
detail: `First transfer with ${subject.address.slice(0, 10)}… above $${args.aboveUsd}.`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
var ORDER = { low: 0, medium: 1, high: 2, prohibited: 3 };
|
|
66
|
+
var highest = (levels) => levels.reduce((worst, level) => ORDER[level] > ORDER[worst] ? level : worst, "low");
|
|
67
|
+
async function screen(subject, options = {}) {
|
|
68
|
+
const now = options.now ?? Date.now;
|
|
69
|
+
if (!subject.owner) {
|
|
70
|
+
throw new ComplianceError("BAD_SUBJECT", "A subject needs an owner to screen.");
|
|
71
|
+
}
|
|
72
|
+
let listMatches = [];
|
|
73
|
+
let listVersion = "none";
|
|
74
|
+
if (options.list) {
|
|
75
|
+
listVersion = options.list.version;
|
|
76
|
+
listMatches = await options.list.match(subject);
|
|
77
|
+
}
|
|
78
|
+
const hits = [];
|
|
79
|
+
for (const rule of options.rules ?? []) {
|
|
80
|
+
const hit = await rule.check(subject);
|
|
81
|
+
if (hit)
|
|
82
|
+
hits.push(hit);
|
|
83
|
+
}
|
|
84
|
+
const risk = highest([
|
|
85
|
+
...listMatches.length > 0 ? ["prohibited"] : [],
|
|
86
|
+
...hits.map((hit) => hit.risk)
|
|
87
|
+
]);
|
|
88
|
+
const reviewAt = options.reviewAt ?? "high";
|
|
89
|
+
const decision = risk === "prohibited" ? "refuse" : ORDER[risk] >= ORDER[reviewAt] ? "review" : "allow";
|
|
90
|
+
return { decision, risk, hits, listMatches, listVersion, screenedAtMs: now(), subject };
|
|
91
|
+
}
|
|
92
|
+
function screeningStage(options = {}) {
|
|
93
|
+
return {
|
|
94
|
+
name: "aml.screening",
|
|
95
|
+
order: 0,
|
|
96
|
+
async run(transfer) {
|
|
97
|
+
const subject = {
|
|
98
|
+
owner: transfer.owner,
|
|
99
|
+
address: transfer.address,
|
|
100
|
+
network: transfer.network,
|
|
101
|
+
direction: transfer.direction,
|
|
102
|
+
usdValue: Number(transfer.context?.usdValue ?? 0),
|
|
103
|
+
tier: transfer.context?.tier ?? "none",
|
|
104
|
+
attributes: transfer.context?.attributes
|
|
105
|
+
};
|
|
106
|
+
let verdict;
|
|
107
|
+
try {
|
|
108
|
+
verdict = await screen(subject, options);
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
if (cause instanceof ComplianceError && cause.code === "LIST_UNAVAILABLE") {
|
|
111
|
+
throw new Refusal(503, "SCREENING_UNAVAILABLE", "Screening could not be completed. The transfer has not been processed.", {}, true);
|
|
112
|
+
}
|
|
113
|
+
throw cause;
|
|
114
|
+
}
|
|
115
|
+
await options.record?.(verdict);
|
|
116
|
+
if (verdict.decision === "refuse") {
|
|
117
|
+
throw new Refusal(451, "SCREENING_REFUSED", "This transfer cannot be processed.", {
|
|
118
|
+
risk: verdict.risk,
|
|
119
|
+
rules: verdict.hits.map((hit) => hit.rule).join(",")
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (verdict.decision === "review" && !await options.isApproved?.(subject)) {
|
|
123
|
+
throw new Refusal(409, "SCREENING_REVIEW", "This transfer is held for review.", { risk: verdict.risk }, true);
|
|
124
|
+
}
|
|
125
|
+
return { screenedAtMs: verdict.screenedAtMs, risk: verdict.risk, listVersion: verdict.listVersion };
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function staticList(entries, version = "static-1") {
|
|
130
|
+
const byValue = new Map(entries.map((entry) => [entry.value.toLowerCase(), entry]));
|
|
131
|
+
return {
|
|
132
|
+
version,
|
|
133
|
+
async match(subject) {
|
|
134
|
+
const candidates = [subject.address, subject.attributes?.name, subject.attributes?.country].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
|
|
135
|
+
return candidates.map((value) => byValue.get(value)).filter((entry) => entry !== undefined);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function combineLists(...lists) {
|
|
140
|
+
return {
|
|
141
|
+
version: lists.map((list) => list.version).join("+"),
|
|
142
|
+
async match(subject) {
|
|
143
|
+
const all = [];
|
|
144
|
+
for (const list of lists)
|
|
145
|
+
all.push(...await list.match(subject));
|
|
146
|
+
return all;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
var unavailableList = (reason) => ({
|
|
151
|
+
version: "unavailable",
|
|
152
|
+
async match() {
|
|
153
|
+
throw new ComplianceError("LIST_UNAVAILABLE", reason);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
export {
|
|
157
|
+
unavailableList,
|
|
158
|
+
tierLimitRule,
|
|
159
|
+
staticList,
|
|
160
|
+
screeningStage,
|
|
161
|
+
screen,
|
|
162
|
+
newCounterpartyRule,
|
|
163
|
+
countryRule,
|
|
164
|
+
combineLists,
|
|
165
|
+
VERIFICATION_TIERS,
|
|
166
|
+
RISK_LEVELS,
|
|
167
|
+
ComplianceError
|
|
168
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VenueError, type VenueAdapter, type MarketType, type OrderStatus } from './venues';
|
|
2
|
+
export interface BinanceCredentials {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
apiSecret: string;
|
|
5
|
+
}
|
|
6
|
+
export interface BinanceOptions {
|
|
7
|
+
credentials: BinanceCredentials;
|
|
8
|
+
/** Override for testnet, or for a test. */
|
|
9
|
+
hosts?: Partial<Record<MarketType, string>>;
|
|
10
|
+
fetch?: typeof globalThis.fetch;
|
|
11
|
+
/**
|
|
12
|
+
* How far a request may be delayed before Binance refuses it.
|
|
13
|
+
*
|
|
14
|
+
* 5s rather than the 60s maximum. A signed order that arrives a minute late
|
|
15
|
+
* is an order placed into a market that has moved, and accepting it is worse
|
|
16
|
+
* than being told to retry.
|
|
17
|
+
*/
|
|
18
|
+
recvWindowMs?: number;
|
|
19
|
+
now?: () => number;
|
|
20
|
+
}
|
|
21
|
+
/** Binance spells `BTC/USDT` as `BTCUSDT`. Denormalised here and nowhere else. */
|
|
22
|
+
export declare const binanceSymbol: (symbol: string) => string;
|
|
23
|
+
/** Binance statuses → ours. An unknown one is `rejected`, never silently `accepted`. */
|
|
24
|
+
export declare function toOrderStatus(status: string): OrderStatus;
|
|
25
|
+
export declare function createBinanceAdapter(options: BinanceOptions): VenueAdapter;
|
|
26
|
+
/** Turn a Binance error into something that names the actual cause. */
|
|
27
|
+
export declare function readBinanceError(error: unknown): VenueError | undefined;
|