@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,116 @@
1
+ /**
2
+ * Which chains exist, and what it takes to believe a transfer on one.
3
+ *
4
+ * Data an admin edits, never a constant. The prior art here got it wrong in a
5
+ * specific and expensive way: two BSC entries hard-coded in a TypeScript
6
+ * constant, so listing a token was a deploy. Two exchanges will not share one
7
+ * chain list and neither will wait for a release to add an asset.
8
+ *
9
+ * ## Confirmations are the whole point
10
+ *
11
+ * Every other field is bookkeeping. `confirmations` is the number that decides
12
+ * whether the platform pays out on a transaction that later disappears in a
13
+ * reorg — credit too early and a deep reorg is a real loss, credit too late and
14
+ * deposits take an hour nobody understands.
15
+ *
16
+ * It is per chain AND per asset value band, because the right depth for a $20
17
+ * transfer is not the right depth for a $2 million one. A single number is
18
+ * either too slow for small deposits or too shallow for large ones, and it is
19
+ * always the large one that costs money.
20
+ */
21
+ export declare class ChainError extends Error {
22
+ readonly code: 'UNKNOWN_CHAIN' | 'UNKNOWN_ASSET' | 'BAD_ADDRESS' | 'BAD_CONFIRMATIONS';
23
+ constructor(code: 'UNKNOWN_CHAIN' | 'UNKNOWN_ASSET' | 'BAD_ADDRESS' | 'BAD_CONFIRMATIONS', message: string);
24
+ }
25
+ export declare const ADDRESS_SCHEMES: readonly ["evm", "bitcoin", "tron", "solana"];
26
+ export type AddressScheme = (typeof ADDRESS_SCHEMES)[number];
27
+ /**
28
+ * How many confirmations a value band needs.
29
+ *
30
+ * Bands are open-ended upward: the last one applies to everything above its
31
+ * threshold. Ordered by `aboveUsd` ascending, and the lookup takes the LAST
32
+ * match — so adding a deeper band for larger amounts cannot accidentally
33
+ * shadow a shallower one.
34
+ */
35
+ export interface ConfirmationBand {
36
+ /** Applies to transfers worth at least this much, in USD. */
37
+ aboveUsd: number;
38
+ confirmations: number;
39
+ }
40
+ export interface ChainSpec {
41
+ key: string;
42
+ label: string;
43
+ scheme: AddressScheme;
44
+ /** EVM chain id. Absent for non-EVM chains, which have no such concept. */
45
+ chainId?: number;
46
+ /** The chain's own coin — the one gas is paid in. */
47
+ nativeAsset: string;
48
+ /** Seconds between blocks. Used to estimate how long a deposit will take. */
49
+ blockSeconds: number;
50
+ /**
51
+ * Ordered ascending by `aboveUsd`, first band starting at 0.
52
+ *
53
+ * The depth for a $20 transfer is not the depth for a $2 million one, and a
54
+ * single number is always wrong for one end.
55
+ */
56
+ confirmations: readonly ConfirmationBand[];
57
+ /** `https://bscscan.com/tx/` — the trailing slash is part of it. */
58
+ explorerTx: string;
59
+ explorerAddress: string;
60
+ enabled: boolean;
61
+ }
62
+ export interface ChainAsset {
63
+ chain: string;
64
+ asset: string;
65
+ /** Absent for the chain's native coin. Present for every token. */
66
+ contract?: string;
67
+ /** Minimum worth depositing. Below this, gas to sweep it exceeds its value. */
68
+ minDeposit: string;
69
+ enabled: boolean;
70
+ }
71
+ /**
72
+ * Is this a plausible address on this chain?
73
+ *
74
+ * Shape only — it cannot tell a real address from a well-formed one that
75
+ * nobody holds. That limit is worth stating, because the check people want is
76
+ * "will this arrive", and no offline check answers it. What this does prevent
77
+ * is a withdrawal to an address from the WRONG chain, which is the common and
78
+ * unrecoverable mistake: an EVM address pasted into a Bitcoin withdrawal is
79
+ * refused here rather than broadcast into nothing.
80
+ */
81
+ export declare function validateAddress(address: string, chain: ChainSpec): void;
82
+ export declare function assertChain(chain: ChainSpec): void;
83
+ /**
84
+ * How deep to wait for a transfer of this value.
85
+ *
86
+ * Takes the LAST matching band, so bands added later for larger amounts cannot
87
+ * shadow the ones below them.
88
+ */
89
+ export declare function confirmationsFor(chain: ChainSpec, usdValue: number): number;
90
+ /** Roughly how long that will take. For a screen that says "about 4 minutes". */
91
+ export declare const estimatedSeconds: (chain: ChainSpec, usdValue: number) => number;
92
+ export declare function createChainRegistry(args: {
93
+ chains: readonly ChainSpec[];
94
+ assets: readonly ChainAsset[];
95
+ }): {
96
+ all: () => ChainSpec[];
97
+ byKey(key: string): ChainSpec;
98
+ /** Every asset that can be deposited on a chain. */
99
+ assetsOn: (chain: string) => ChainAsset[];
100
+ /** Every chain an asset can arrive on. USDT exists on several. */
101
+ chainsFor: (asset: string) => ChainSpec[];
102
+ asset(chain: string, asset: string): ChainAsset;
103
+ explorerTx: (chain: string, hash: string) => string;
104
+ explorerAddress: (chain: string, address: string) => string;
105
+ validate(chain: string, address: string): void;
106
+ };
107
+ export type ChainRegistry = ReturnType<typeof createChainRegistry>;
108
+ /**
109
+ * Sensible starting values, for a fresh install to edit.
110
+ *
111
+ * A SEED, not a source of truth — the registry is a store and this is what
112
+ * populates it the first time. The distinction matters: a constant that the
113
+ * code reads directly is a deploy every time a token is listed.
114
+ */
115
+ export declare const SEED_CHAINS: readonly ChainSpec[];
116
+ export declare const SEED_CHAIN_ASSETS: readonly ChainAsset[];
@@ -0,0 +1,316 @@
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/chain.ts
155
+ class ChainError extends Error {
156
+ code;
157
+ constructor(code, message) {
158
+ super(message);
159
+ this.code = code;
160
+ this.name = "ChainError";
161
+ }
162
+ }
163
+ var ADDRESS_SCHEMES = ["evm", "bitcoin", "tron", "solana"];
164
+ var SCHEME_PATTERNS = {
165
+ evm: /^0x[a-fA-F0-9]{40}$/,
166
+ bitcoin: /^(bc1[a-z0-9]{25,62}|[13][a-km-zA-HJ-NP-Z1-9]{25,34})$/,
167
+ tron: /^T[a-zA-Z0-9]{33}$/,
168
+ solana: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/
169
+ };
170
+ function validateAddress(address, chain) {
171
+ if (!SCHEME_PATTERNS[chain.scheme].test(address.trim())) {
172
+ throw new ChainError("BAD_ADDRESS", `"${address.slice(0, 12)}…" is not a valid ${chain.label} address. Check you have not pasted an address for a different chain — that is the mistake that cannot be undone.`);
173
+ }
174
+ }
175
+ function assertChain(chain) {
176
+ if (chain.confirmations.length === 0) {
177
+ throw new ChainError("BAD_CONFIRMATIONS", `${chain.key} declares no confirmation depth.`);
178
+ }
179
+ if (chain.confirmations[0].aboveUsd !== 0) {
180
+ throw new ChainError("BAD_CONFIRMATIONS", `${chain.key} must have a band starting at 0, or small transfers match no rule.`);
181
+ }
182
+ let previous = -1;
183
+ for (const band of chain.confirmations) {
184
+ if (band.aboveUsd <= previous) {
185
+ throw new ChainError("BAD_CONFIRMATIONS", `${chain.key} bands must ascend; ${band.aboveUsd} follows ${previous}.`);
186
+ }
187
+ if (!Number.isInteger(band.confirmations) || band.confirmations < 1) {
188
+ throw new ChainError("BAD_CONFIRMATIONS", `${chain.key} band above $${band.aboveUsd} needs at least one confirmation.`);
189
+ }
190
+ previous = band.aboveUsd;
191
+ }
192
+ }
193
+ function confirmationsFor(chain, usdValue) {
194
+ let depth = chain.confirmations[0].confirmations;
195
+ for (const band of chain.confirmations) {
196
+ if (usdValue >= band.aboveUsd)
197
+ depth = band.confirmations;
198
+ }
199
+ return depth;
200
+ }
201
+ var estimatedSeconds = (chain, usdValue) => confirmationsFor(chain, usdValue) * chain.blockSeconds;
202
+ function createChainRegistry(args) {
203
+ for (const chain of args.chains)
204
+ assertChain(chain);
205
+ const chains = new Map(args.chains.map((chain) => [chain.key, chain]));
206
+ return {
207
+ all: () => [...chains.values()].filter((chain) => chain.enabled),
208
+ byKey(key) {
209
+ const chain = chains.get(key);
210
+ if (!chain)
211
+ throw new ChainError("UNKNOWN_CHAIN", `No chain "${key}" is configured.`);
212
+ return chain;
213
+ },
214
+ assetsOn: (chain) => args.assets.filter((asset) => asset.chain === chain && asset.enabled),
215
+ chainsFor: (asset) => args.assets.filter((entry) => entry.asset === asset && entry.enabled).map((entry) => chains.get(entry.chain)).filter((chain) => chain?.enabled === true),
216
+ asset(chain, asset) {
217
+ const entry = args.assets.find((row) => row.chain === chain && row.asset === asset);
218
+ if (!entry) {
219
+ throw new ChainError("UNKNOWN_ASSET", `${asset} is not configured on ${chain}.`);
220
+ }
221
+ assetSpec(asset);
222
+ return entry;
223
+ },
224
+ explorerTx: (chain, hash) => {
225
+ const spec = chains.get(chain);
226
+ return spec ? `${spec.explorerTx}${hash}` : hash;
227
+ },
228
+ explorerAddress: (chain, address) => {
229
+ const spec = chains.get(chain);
230
+ return spec ? `${spec.explorerAddress}${address}` : address;
231
+ },
232
+ validate(chain, address) {
233
+ validateAddress(address, this.byKey(chain));
234
+ }
235
+ };
236
+ }
237
+ var SEED_CHAINS = [
238
+ {
239
+ key: "bsc",
240
+ label: "BNB Smart Chain",
241
+ scheme: "evm",
242
+ chainId: 56,
243
+ nativeAsset: "BNB",
244
+ blockSeconds: 3,
245
+ confirmations: [
246
+ { aboveUsd: 0, confirmations: 15 },
247
+ { aboveUsd: 1e4, confirmations: 30 },
248
+ { aboveUsd: 1e5, confirmations: 60 }
249
+ ],
250
+ explorerTx: "https://bscscan.com/tx/",
251
+ explorerAddress: "https://bscscan.com/address/",
252
+ enabled: true
253
+ },
254
+ {
255
+ key: "ethereum",
256
+ label: "Ethereum",
257
+ scheme: "evm",
258
+ chainId: 1,
259
+ nativeAsset: "ETH",
260
+ blockSeconds: 12,
261
+ confirmations: [
262
+ { aboveUsd: 0, confirmations: 12 },
263
+ { aboveUsd: 50000, confirmations: 32 }
264
+ ],
265
+ explorerTx: "https://etherscan.io/tx/",
266
+ explorerAddress: "https://etherscan.io/address/",
267
+ enabled: true
268
+ },
269
+ {
270
+ key: "tron",
271
+ label: "Tron",
272
+ scheme: "tron",
273
+ nativeAsset: "TRX",
274
+ blockSeconds: 3,
275
+ confirmations: [{ aboveUsd: 0, confirmations: 20 }],
276
+ explorerTx: "https://tronscan.org/#/transaction/",
277
+ explorerAddress: "https://tronscan.org/#/address/",
278
+ enabled: true
279
+ }
280
+ ];
281
+ var SEED_CHAIN_ASSETS = [
282
+ { chain: "bsc", asset: "BNB", minDeposit: "0.001", enabled: true },
283
+ {
284
+ chain: "bsc",
285
+ asset: "USDT",
286
+ contract: "0x55d398326f99059ff775485246999027b3197955",
287
+ minDeposit: "1",
288
+ enabled: true
289
+ },
290
+ { chain: "ethereum", asset: "ETH", minDeposit: "0.005", enabled: true },
291
+ {
292
+ chain: "ethereum",
293
+ asset: "USDT",
294
+ contract: "0xdac17f958d2ee523a2206206994597c13d831ec7",
295
+ minDeposit: "20",
296
+ enabled: true
297
+ },
298
+ {
299
+ chain: "tron",
300
+ asset: "USDT",
301
+ contract: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
302
+ minDeposit: "1",
303
+ enabled: true
304
+ }
305
+ ];
306
+ export {
307
+ validateAddress,
308
+ estimatedSeconds,
309
+ createChainRegistry,
310
+ confirmationsFor,
311
+ assertChain,
312
+ SEED_CHAIN_ASSETS,
313
+ SEED_CHAINS,
314
+ ChainError,
315
+ ADDRESS_SCHEMES
316
+ };
@@ -0,0 +1,155 @@
1
+ import { allocate, type Money } from './money';
2
+ import { type Transaction, type AccountRef } from './ledger';
3
+ /**
4
+ * What the platform earns, and who it is shared with.
5
+ *
6
+ * Three rules from the owner, and each is one line of arithmetic sitting on top
7
+ * of primitives that already exist:
8
+ *
9
+ * COMMISSION a share of the tenant's PROFIT for the period, at the tier rate
10
+ * REFERRAL a share of THAT — of our income, not of their profit — to
11
+ * whoever introduced them
12
+ * PREPAY higher tiers pay half the projected term commission up front
13
+ *
14
+ * ## Profit, not volume
15
+ *
16
+ * The distinction is the whole basis of the charge and it is easy to get wrong
17
+ * under pressure: a period where a tenant traded ten million and lost money
18
+ * owes nothing. Commission on a losing period would be a fee dressed as a
19
+ * share, and the first tenant to notice would be right to leave.
20
+ *
21
+ * ## Losses carry forward
22
+ *
23
+ * A tenant who loses 50 in one week and makes 60 the next is not up 60 — they
24
+ * are up 10, and charging on 60 charges them for recovering their own money.
25
+ * The high-water mark makes that structural rather than a policy somebody has
26
+ * to remember.
27
+ */
28
+ export declare class CommissionError extends Error {
29
+ readonly code: 'BAD_RATE' | 'NO_TIER' | 'ASSET_MISMATCH' | 'NEGATIVE_PERIOD';
30
+ constructor(code: 'BAD_RATE' | 'NO_TIER' | 'ASSET_MISMATCH' | 'NEGATIVE_PERIOD', message: string);
31
+ }
32
+ export interface PeriodPerformance {
33
+ /** Equity at the start of the period, in the account's asset. */
34
+ openingEquity: Money;
35
+ closingEquity: Money;
36
+ /** Deposits during the period. Not profit — the tenant's own money. */
37
+ deposits: Money;
38
+ withdrawals: Money;
39
+ /**
40
+ * The highest equity ever charged on, net of flows.
41
+ *
42
+ * Losses carry forward against this, so a recovery is not charged twice.
43
+ */
44
+ highWaterMark: Money;
45
+ }
46
+ /**
47
+ * Profit for one period, adjusted for money moving in and out.
48
+ *
49
+ * profit = closing − opening − deposits + withdrawals
50
+ *
51
+ * Without the flow adjustment a tenant depositing a million looks like a
52
+ * million of profit, and the invoice that follows would be indefensible. This
53
+ * is the single most important line in the file.
54
+ */
55
+ export declare function periodProfit(performance: PeriodPerformance): Money;
56
+ /**
57
+ * The part of the profit that is actually chargeable.
58
+ *
59
+ * Zero when the account is still below its high-water mark. A tenant who lost
60
+ * 50 and made 60 owes on 10, not on 60 — charging on the recovery charges them
61
+ * for getting their own money back.
62
+ */
63
+ export declare function chargeableProfit(performance: PeriodPerformance): Money;
64
+ /** The new mark after a period. Never falls — that is what "high water" means. */
65
+ export declare const nextHighWaterMark: (performance: PeriodPerformance) => Money;
66
+ export interface CommissionInput {
67
+ profit: Money;
68
+ /** Platform share of profit. `'0.30'` is thirty per cent. */
69
+ commissionRate: string;
70
+ /** Referrer share of OUR income — not of the profit. */
71
+ referralRate?: string;
72
+ referrer?: string;
73
+ }
74
+ export interface CommissionSplit {
75
+ /** What the tenant owes. */
76
+ total: Money;
77
+ /** What the platform keeps after the referral share. */
78
+ platform: Money;
79
+ referral: Money;
80
+ referrer?: string;
81
+ }
82
+ /**
83
+ * Split a period's profit into what is owed and how it is shared.
84
+ *
85
+ * `allocate` does the division, so the parts always sum back to the total. A
86
+ * split that loses a unit is a transaction that cannot be posted, because the
87
+ * ledger refuses anything that does not balance — which is the correct place
88
+ * for that to fail.
89
+ */
90
+ export declare function splitCommission(input: CommissionInput): CommissionSplit;
91
+ /**
92
+ * The split as ledger entries.
93
+ *
94
+ * One transaction with three legs, so it balances or it does not post. A
95
+ * referral paid by a second transaction could succeed while the first failed,
96
+ * which is how a referrer gets paid on income the platform never collected.
97
+ */
98
+ export declare function commissionTransaction(args: {
99
+ reference: string;
100
+ owner: string;
101
+ split: CommissionSplit;
102
+ atMs: number;
103
+ revenueAccount?: AccountRef;
104
+ }): Transaction | null;
105
+ export interface PrepayInput {
106
+ /** What the tenant is subscribing with. */
107
+ subscriptionAmount: Money;
108
+ /** Expected return over the term, as a rate. `'0.20'` is twenty per cent. */
109
+ projectedReturnRate: string;
110
+ commissionRate: string;
111
+ /** Fraction of the projected commission payable now. The owner's rule is a half. */
112
+ prepayRate: string;
113
+ }
114
+ export interface Prepay {
115
+ /** What the term is projected to yield the tenant. */
116
+ projectedProfit: Money;
117
+ /** Commission on that, if the projection holds. */
118
+ projectedCommission: Money;
119
+ /** Payable up front. */
120
+ due: Money;
121
+ }
122
+ /**
123
+ * What a higher tier pays before the term starts.
124
+ *
125
+ * Every input is a rate the operator sets, including the projection — this is
126
+ * arithmetic on stated assumptions, not a forecast. Naming it `projected`
127
+ * everywhere is deliberate: a figure derived from an assumed return should
128
+ * never read on a screen as an amount somebody has earned.
129
+ *
130
+ * The prepay is a CREDIT against real commission, not an extra charge. That
131
+ * belongs to whoever settles the invoice, and the shape here makes it easy to
132
+ * do: `due` is in the same asset as the commission it offsets.
133
+ */
134
+ export declare function projectPrepay(input: PrepayInput): Prepay;
135
+ export interface ReferralShare {
136
+ referrer: string;
137
+ rate: string;
138
+ }
139
+ /**
140
+ * Split one income figure across several referrers.
141
+ *
142
+ * Multi-level is not currently used and the shape supports it anyway, because
143
+ * retrofitting a second level means changing how every historical commission
144
+ * was computed. `allocate` guarantees the parts sum to the whole, so the
145
+ * platform's remainder absorbs any rounding rather than a unit vanishing.
146
+ */
147
+ export declare function splitReferrals(income: Money, shares: readonly ReferralShare[]): {
148
+ platform: Money;
149
+ paid: {
150
+ referrer: string;
151
+ amount: Money;
152
+ }[];
153
+ };
154
+ /** Even split of a remainder, losing nothing. Re-exported so callers need one import. */
155
+ export { allocate };