@forgezero/runtime 0.1.14 → 0.1.16
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 -388
- package/dist/jobs.d.ts +5 -6
- package/dist/notify-templates.js +1 -1
- package/dist/schema-typebox.js +116 -7
- package/dist/schema.d.ts +14 -1
- package/dist/schema.js +116 -7
- package/package.json +3 -62
- package/contracts/foundry.toml +0 -9
- package/contracts/src/ColdVault.sol +0 -206
- package/contracts/src/DepositFactory.sol +0 -202
- package/contracts/src/DepositProxy.sol +0 -72
- package/contracts/src/IERC20.sol +0 -7
- package/contracts/src/MockTokens.sol +0 -32
- package/contracts/src/SafeTransferLib.sol +0 -31
- package/contracts/test/Custody.t.sol +0 -361
- package/contracts/test/Vectors.t.sol +0 -45
- package/dist/compliance.d.ts +0 -172
- package/dist/compliance.js +0 -168
- package/dist/finance/chain-addresses.d.ts +0 -130
- package/dist/finance/chain-addresses.js +0 -462
- package/dist/finance/chain-deposits.d.ts +0 -193
- package/dist/finance/chain-deposits.js +0 -600
- package/dist/finance/chain-reconcile.d.ts +0 -112
- package/dist/finance/chain-reconcile.js +0 -76
- package/dist/finance/chain-withdrawals.d.ts +0 -223
- package/dist/finance/chain-withdrawals.js +0 -635
- package/dist/finance/chain.d.ts +0 -116
- package/dist/finance/chain.js +0 -316
- package/dist/finance/commission.d.ts +0 -155
- package/dist/finance/commission.js +0 -423
- package/dist/finance/custody.d.ts +0 -68
- package/dist/finance/custody.js +0 -107
- package/dist/finance/derive.d.ts +0 -115
- package/dist/finance/derive.js +0 -116
- package/dist/finance/ledger.d.ts +0 -227
- package/dist/finance/ledger.js +0 -313
- package/dist/finance/market.d.ts +0 -209
- package/dist/finance/market.js +0 -112
- package/dist/finance/rates.d.ts +0 -178
- package/dist/finance/rates.js +0 -292
- package/dist/finance/transfers.d.ts +0 -153
- package/dist/finance/transfers.js +0 -292
- package/dist/finance/venues.d.ts +0 -190
- package/dist/finance/venues.js +0 -251
package/dist/finance/derive.d.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Declaring a value ForgeZero generates rather than the tenant supplying.
|
|
3
|
-
*
|
|
4
|
-
* A tenant that holds a signing key holds the whole risk of holding a signing
|
|
5
|
-
* key: it appears in their backups, their logs, their heap dumps and their
|
|
6
|
-
* environment variables. The alternative is that they never have it. They
|
|
7
|
-
* declare which chain they have activated, ForgeZero derives the key from the
|
|
8
|
-
* realm master seed, and the only thing that ever crosses back is an ADDRESS.
|
|
9
|
-
*
|
|
10
|
-
* This module is the pure half — the specification and the path arithmetic. It
|
|
11
|
-
* cannot derive anything, because it has no seed and must not: this runs in a
|
|
12
|
-
* browser and in a tenant's process, and neither is a place a master seed
|
|
13
|
-
* belongs. The platform half does the derivation and the signing.
|
|
14
|
-
*
|
|
15
|
-
* ## Deterministic, so a lost row is not lost money
|
|
16
|
-
*
|
|
17
|
-
* The path is a function of the declared fields. Losing the database row loses
|
|
18
|
-
* the metadata and not the key — re-declaring the same chain and index derives
|
|
19
|
-
* the same address, and whatever was sent to it is reachable again. A random
|
|
20
|
-
* key stored next to the row would make that row the only copy.
|
|
21
|
-
*/
|
|
22
|
-
export declare class DeriveError extends Error {
|
|
23
|
-
readonly code: 'BAD_SPEC' | 'BAD_INDEX' | 'UNSUPPORTED_SCHEME';
|
|
24
|
-
constructor(code: 'BAD_SPEC' | 'BAD_INDEX' | 'UNSUPPORTED_SCHEME', message: string);
|
|
25
|
-
}
|
|
26
|
-
/** Curves a chain actually verifies against. */
|
|
27
|
-
export declare const SCHEMES: readonly ["secp256k1", "ed25519"];
|
|
28
|
-
export type Scheme = (typeof SCHEMES)[number];
|
|
29
|
-
/**
|
|
30
|
-
* Who generates the key, which is a real trade-off and not a default.
|
|
31
|
-
*
|
|
32
|
-
* derived ForgeZero generates it from the realm master seed, on demand,
|
|
33
|
-
* and stores NOTHING. There is no key at rest to steal, and a lost
|
|
34
|
-
* row is recoverable because the key is a function of the seed and
|
|
35
|
-
* the path. The cost: the platform can always regenerate it, so
|
|
36
|
-
* the tenant's key is only as private as the ceremony guarding the
|
|
37
|
-
* seed.
|
|
38
|
-
*
|
|
39
|
-
* supplied the tenant generates it — on their own hardware, in their own
|
|
40
|
-
* ceremony, however they like — and the vault encrypts and stores
|
|
41
|
-
* it. The platform could never have produced this key, which is
|
|
42
|
-
* exactly the point for a tenant who needs that to be true. The
|
|
43
|
-
* cost: there is now a copy at rest, and losing it loses the key,
|
|
44
|
-
* because nothing can regenerate what the platform never derived.
|
|
45
|
-
*
|
|
46
|
-
* Both sign the same way and neither ever returns the private half. The choice
|
|
47
|
-
* is about who is CAPABLE of generating the key, not about how it is used.
|
|
48
|
-
*/
|
|
49
|
-
export declare const CUSTODY: readonly ["derived", "supplied"];
|
|
50
|
-
export type Custody = (typeof CUSTODY)[number];
|
|
51
|
-
/**
|
|
52
|
-
* How one platform-managed field is produced.
|
|
53
|
-
*
|
|
54
|
-
* Carried in the JSON Schema under `x-fz-derive`, so the schema stays the one
|
|
55
|
-
* description of an entry — a separate list of "which fields are magic" is a
|
|
56
|
-
* second source of truth that drifts the first time somebody edits one.
|
|
57
|
-
*/
|
|
58
|
-
export interface DeriveSpec {
|
|
59
|
-
/** Defaults to `derived`: nothing at rest is the safer default to forget. */
|
|
60
|
-
custody?: Custody;
|
|
61
|
-
scheme: Scheme;
|
|
62
|
-
/** BIP-44 coin type. 60 is every EVM chain, 0 is Bitcoin. Derived only. */
|
|
63
|
-
coinType?: number;
|
|
64
|
-
account?: number;
|
|
65
|
-
change?: 0 | 1;
|
|
66
|
-
/** Address index. Taken from a sibling field when `indexFrom` is set. */
|
|
67
|
-
index?: number;
|
|
68
|
-
/**
|
|
69
|
-
* Read the index from another field of the same entry.
|
|
70
|
-
*
|
|
71
|
-
* Lets one schema describe a whole series — a tenant adds an entry with
|
|
72
|
-
* `index: 7` rather than authoring a seventh schema.
|
|
73
|
-
*/
|
|
74
|
-
indexFrom?: string;
|
|
75
|
-
/** What comes back to the tenant. Never the private key, under any value. */
|
|
76
|
-
expose?: 'address' | 'publicKey';
|
|
77
|
-
}
|
|
78
|
-
export declare const custodyOf: (spec: DeriveSpec) => Custody;
|
|
79
|
-
export declare function assertDeriveSpec(spec: DeriveSpec): void;
|
|
80
|
-
/**
|
|
81
|
-
* The BIP-44 path this spec resolves to, for one entry's data.
|
|
82
|
-
*
|
|
83
|
-
* `m / 44' / coin' / account' / change / index`. Purpose, coin and account are
|
|
84
|
-
* hardened; change and index are not, so a watch-only public key can enumerate
|
|
85
|
-
* addresses without exposing the account.
|
|
86
|
-
*/
|
|
87
|
-
export declare function pathFor(spec: DeriveSpec, data?: Record<string, unknown>): string;
|
|
88
|
-
/** The `x-fz-derive` annotation on one property, if it carries one. */
|
|
89
|
-
export declare function deriveSpecOf(property: unknown): DeriveSpec | undefined;
|
|
90
|
-
export interface DerivedField {
|
|
91
|
-
/** Dotted path into the entry's data. */
|
|
92
|
-
path: string;
|
|
93
|
-
spec: DeriveSpec;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Every field of a schema that ForgeZero fills in.
|
|
97
|
-
*
|
|
98
|
-
* Read from the schema rather than configured alongside it, so a field cannot
|
|
99
|
-
* be derived in one place and expected from the tenant in another.
|
|
100
|
-
*/
|
|
101
|
-
export declare function derivedFields(schema: unknown, prefix?: string): DerivedField[];
|
|
102
|
-
/**
|
|
103
|
-
* Fields a tenant must NOT send.
|
|
104
|
-
*
|
|
105
|
-
* Only the DERIVED ones. Accepting a value for a derived field would let a
|
|
106
|
-
* caller substitute their own key for the platform's — the entry would look
|
|
107
|
-
* identical and every signature would come from a key the tenant controls.
|
|
108
|
-
*
|
|
109
|
-
* A `supplied` field is the opposite: the tenant is REQUIRED to send it, once,
|
|
110
|
-
* and it is sealed on arrival.
|
|
111
|
-
*/
|
|
112
|
-
export declare const derivedPaths: (schema: unknown) => readonly string[];
|
|
113
|
-
/** Fields the tenant generates and the vault only seals. */
|
|
114
|
-
export declare const suppliedPaths: (schema: unknown) => readonly string[];
|
|
115
|
-
export declare const DERIVE_KEYWORD: "x-fz-derive";
|
package/dist/finance/derive.js
DELETED
|
@@ -1,116 +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/derive.ts
|
|
10
|
-
class DeriveError extends Error {
|
|
11
|
-
code;
|
|
12
|
-
constructor(code, message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.code = code;
|
|
15
|
-
this.name = "DeriveError";
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
var SCHEMES = ["secp256k1", "ed25519"];
|
|
19
|
-
var CUSTODY = ["derived", "supplied"];
|
|
20
|
-
var HARDENED = 2147483648;
|
|
21
|
-
var assertIndex = (value, what) => {
|
|
22
|
-
if (!Number.isInteger(value) || value < 0 || value >= HARDENED) {
|
|
23
|
-
throw new DeriveError("BAD_INDEX", `${what} must be an integer in 0..${HARDENED - 1}.`);
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
};
|
|
27
|
-
var custodyOf = (spec) => spec.custody ?? "derived";
|
|
28
|
-
function assertDeriveSpec(spec) {
|
|
29
|
-
if (!SCHEMES.includes(spec.scheme)) {
|
|
30
|
-
throw new DeriveError("UNSUPPORTED_SCHEME", `${spec.scheme} is not a supported curve.`);
|
|
31
|
-
}
|
|
32
|
-
if (custodyOf(spec) === "supplied") {
|
|
33
|
-
if (spec.coinType !== undefined || spec.index !== undefined || spec.indexFrom) {
|
|
34
|
-
throw new DeriveError("BAD_SPEC", "A supplied key has no derivation path; drop coinType, index and indexFrom.");
|
|
35
|
-
}
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (spec.coinType === undefined) {
|
|
39
|
-
throw new DeriveError("BAD_SPEC", "A derived key needs a coinType.");
|
|
40
|
-
}
|
|
41
|
-
assertIndex(spec.coinType, "coinType");
|
|
42
|
-
if (spec.account !== undefined)
|
|
43
|
-
assertIndex(spec.account, "account");
|
|
44
|
-
if (spec.index !== undefined)
|
|
45
|
-
assertIndex(spec.index, "index");
|
|
46
|
-
if (spec.change !== undefined && spec.change !== 0 && spec.change !== 1) {
|
|
47
|
-
throw new DeriveError("BAD_SPEC", "change is 0 (external) or 1 (internal).");
|
|
48
|
-
}
|
|
49
|
-
if (spec.index === undefined && !spec.indexFrom) {
|
|
50
|
-
throw new DeriveError("BAD_SPEC", "A spec needs an index, or a field to read one from.");
|
|
51
|
-
}
|
|
52
|
-
if (spec.index !== undefined && spec.indexFrom) {
|
|
53
|
-
throw new DeriveError("BAD_SPEC", "Give an index or an indexFrom, never both.");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function pathFor(spec, data = {}) {
|
|
57
|
-
assertDeriveSpec(spec);
|
|
58
|
-
if (custodyOf(spec) === "supplied") {
|
|
59
|
-
throw new DeriveError("BAD_SPEC", "A supplied key has no derivation path.");
|
|
60
|
-
}
|
|
61
|
-
let index = spec.index;
|
|
62
|
-
if (spec.indexFrom) {
|
|
63
|
-
const raw = data[spec.indexFrom];
|
|
64
|
-
const value = typeof raw === "string" ? Number(raw) : raw;
|
|
65
|
-
if (typeof value !== "number") {
|
|
66
|
-
throw new DeriveError("BAD_INDEX", `${spec.indexFrom} is not a number on this entry.`);
|
|
67
|
-
}
|
|
68
|
-
index = assertIndex(value, spec.indexFrom);
|
|
69
|
-
}
|
|
70
|
-
const account = spec.account ?? 0;
|
|
71
|
-
const change = spec.change ?? 0;
|
|
72
|
-
return `m/44'/${spec.coinType}'/${account}'/${change}/${index}`;
|
|
73
|
-
}
|
|
74
|
-
function deriveSpecOf(property) {
|
|
75
|
-
if (typeof property !== "object" || property === null)
|
|
76
|
-
return;
|
|
77
|
-
const spec = property["x-fz-derive"];
|
|
78
|
-
if (typeof spec !== "object" || spec === null)
|
|
79
|
-
return;
|
|
80
|
-
const parsed = spec;
|
|
81
|
-
assertDeriveSpec(parsed);
|
|
82
|
-
return parsed;
|
|
83
|
-
}
|
|
84
|
-
function derivedFields(schema, prefix = "") {
|
|
85
|
-
if (typeof schema !== "object" || schema === null)
|
|
86
|
-
return [];
|
|
87
|
-
const properties = schema.properties;
|
|
88
|
-
if (!properties)
|
|
89
|
-
return [];
|
|
90
|
-
const out = [];
|
|
91
|
-
for (const [name, property] of Object.entries(properties)) {
|
|
92
|
-
const path = prefix ? `${prefix}.${name}` : name;
|
|
93
|
-
const spec = deriveSpecOf(property);
|
|
94
|
-
if (spec)
|
|
95
|
-
out.push({ path, spec });
|
|
96
|
-
else
|
|
97
|
-
out.push(...derivedFields(property, path));
|
|
98
|
-
}
|
|
99
|
-
return out;
|
|
100
|
-
}
|
|
101
|
-
var derivedPaths = (schema) => derivedFields(schema).filter((field) => custodyOf(field.spec) === "derived").map((field) => field.path);
|
|
102
|
-
var suppliedPaths = (schema) => derivedFields(schema).filter((field) => custodyOf(field.spec) === "supplied").map((field) => field.path);
|
|
103
|
-
var DERIVE_KEYWORD = "x-fz-derive";
|
|
104
|
-
export {
|
|
105
|
-
suppliedPaths,
|
|
106
|
-
pathFor,
|
|
107
|
-
derivedPaths,
|
|
108
|
-
derivedFields,
|
|
109
|
-
deriveSpecOf,
|
|
110
|
-
custodyOf,
|
|
111
|
-
assertDeriveSpec,
|
|
112
|
-
SCHEMES,
|
|
113
|
-
DeriveError,
|
|
114
|
-
DERIVE_KEYWORD,
|
|
115
|
-
CUSTODY
|
|
116
|
-
};
|
package/dist/finance/ledger.d.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { type Money } from './money';
|
|
2
|
-
/**
|
|
3
|
-
* Balances, as postings.
|
|
4
|
-
*
|
|
5
|
-
* **This module contains no concurrency control, and that is the design.**
|
|
6
|
-
*
|
|
7
|
-
* `@forgezero/runtime/queue` already claims a KEY rather than a message and
|
|
8
|
-
* carries a `dedupeKey`, which gives the two properties people build a ledger
|
|
9
|
-
* lock to get: two writes to one account run one after the other because they
|
|
10
|
-
* share a key, and a retried operation is recognised rather than repeated. A
|
|
11
|
-
* second mechanism next to that is not extra safety — it is a second thing to
|
|
12
|
-
* get right, and it is the one that ends up subtly wrong.
|
|
13
|
-
*
|
|
14
|
-
* So every write here is expected to be inside `drainKey(accountOwner)`, and
|
|
15
|
-
* this module is a data model: entries that sum to zero, balances derived from
|
|
16
|
-
* them.
|
|
17
|
-
*
|
|
18
|
-
* ## A hold is a posting, not a lock
|
|
19
|
-
*
|
|
20
|
-
* "Available" versus "total" is the reason ledgers grow locking code. Here an
|
|
21
|
-
* owner has sub-accounts — `available` and `held` — and placing a hold is a
|
|
22
|
-
* posting between them. `availableOf()` is then an ordinary balance query, and
|
|
23
|
-
* `requireBalance` in `@forgezero/access/conditions` (which already checks
|
|
24
|
-
* available rather than total) is the guard with no new code at all.
|
|
25
|
-
*
|
|
26
|
-
* ## Why double entry rather than a balance column
|
|
27
|
-
*
|
|
28
|
-
* A single column loses the ability to answer *why*. Every movement here has a
|
|
29
|
-
* counterparty and every transaction sums to zero, so a discrepancy names the
|
|
30
|
-
* posting that caused it instead of appearing as a number nobody can
|
|
31
|
-
* reconstruct. It is also what makes a commission split provable: platform
|
|
32
|
-
* revenue, referrer share and user debit are three legs of one transaction that
|
|
33
|
-
* must balance, so a split that loses a unit cannot be committed at all.
|
|
34
|
-
*/
|
|
35
|
-
export declare class LedgerError extends Error {
|
|
36
|
-
readonly code: 'UNBALANCED' | 'EMPTY_TRANSACTION' | 'TOO_MANY_ENTRIES' | 'MIXED_ASSETS' | 'ZERO_ENTRY' | 'UNKNOWN_ACCOUNT' | 'INSUFFICIENT_AVAILABLE';
|
|
37
|
-
constructor(code: 'UNBALANCED' | 'EMPTY_TRANSACTION' | 'TOO_MANY_ENTRIES' | 'MIXED_ASSETS' | 'ZERO_ENTRY' | 'UNKNOWN_ACCOUNT' | 'INSUFFICIENT_AVAILABLE', message: string);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* The sub-accounts every owner has.
|
|
41
|
-
*
|
|
42
|
-
* available spendable now
|
|
43
|
-
* held committed to something open — an order, a pending withdrawal
|
|
44
|
-
*
|
|
45
|
-
* Splitting them is what removes the need for a lock: the question "is there
|
|
46
|
-
* enough?" becomes a balance read on one sub-account rather than a balance read
|
|
47
|
-
* plus a reservation somewhere else.
|
|
48
|
-
*/
|
|
49
|
-
export declare const BUCKETS: readonly ["available", "held"];
|
|
50
|
-
export type Bucket = (typeof BUCKETS)[number];
|
|
51
|
-
/**
|
|
52
|
-
* Where money can sit.
|
|
53
|
-
*
|
|
54
|
-
* `external` is the counterparty for anything crossing the system boundary — a
|
|
55
|
-
* chain deposit, a card payment. Without it a deposit has one leg and cannot
|
|
56
|
-
* balance, which is how single-column ledgers start.
|
|
57
|
-
*/
|
|
58
|
-
export declare const ACCOUNT_KINDS: readonly ["user", "platform", "external"];
|
|
59
|
-
export type AccountKind = (typeof ACCOUNT_KINDS)[number];
|
|
60
|
-
export interface AccountRef {
|
|
61
|
-
kind: AccountKind;
|
|
62
|
-
/** User key, or a platform account name like `revenue` or `promotions`. */
|
|
63
|
-
owner: string;
|
|
64
|
-
bucket?: Bucket;
|
|
65
|
-
}
|
|
66
|
-
/** Canonical string form, so an account is comparable and indexable. */
|
|
67
|
-
export declare const accountId: (account: AccountRef) => string;
|
|
68
|
-
export declare function parseAccount(id: string): AccountRef;
|
|
69
|
-
/**
|
|
70
|
-
* The queue key for any write touching this account.
|
|
71
|
-
*
|
|
72
|
-
* The OWNER, not the sub-account: moving between an owner's available and held
|
|
73
|
-
* buckets must serialise against every other write for that owner, or a hold
|
|
74
|
-
* and a withdrawal can interleave and both see the same available balance.
|
|
75
|
-
*
|
|
76
|
-
* Exported so callers cannot invent their own convention — two call sites
|
|
77
|
-
* disagreeing about the key is the same as having no key.
|
|
78
|
-
*/
|
|
79
|
-
export declare const queueKeyFor: (account: AccountRef) => string;
|
|
80
|
-
export interface Entry {
|
|
81
|
-
account: AccountRef;
|
|
82
|
-
/** Positive credits the account, negative debits it. */
|
|
83
|
-
amount: Money;
|
|
84
|
-
}
|
|
85
|
-
export interface Transaction {
|
|
86
|
-
/**
|
|
87
|
-
* The idempotency key, and the queue's `dedupeKey`.
|
|
88
|
-
*
|
|
89
|
-
* A retry carrying the same reference is the same transaction. This is the
|
|
90
|
-
* field that makes a redelivered deposit a no-op rather than a double credit.
|
|
91
|
-
*/
|
|
92
|
-
reference: string;
|
|
93
|
-
/** `deposit`, `order.fill`, `commission`, `bonus` — read during an incident. */
|
|
94
|
-
kind: string;
|
|
95
|
-
entries: readonly Entry[];
|
|
96
|
-
atMs: number;
|
|
97
|
-
memo?: string;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* A ledger transaction is one atomic business movement, not a batch transport.
|
|
101
|
-
* Keeping the posting set small bounds both the durable document and the
|
|
102
|
-
* multikey `accountIds[*]` index entry fan-out derived from it by the API.
|
|
103
|
-
*/
|
|
104
|
-
export declare const MAX_LEDGER_ENTRIES = 64;
|
|
105
|
-
/**
|
|
106
|
-
* Every transaction sums to zero, per asset.
|
|
107
|
-
*
|
|
108
|
-
* Per asset, not overall: a transaction moving BTC and USDT in one breath would
|
|
109
|
-
* otherwise "balance" by cancelling unrelated currencies against each other,
|
|
110
|
-
* which is not balance, it is an accident that adds to zero.
|
|
111
|
-
*/
|
|
112
|
-
export declare function assertBalanced(transaction: Pick<Transaction, 'entries'>): void;
|
|
113
|
-
/** Two legs: one account credited, another debited, same asset. */
|
|
114
|
-
export declare function transfer(args: {
|
|
115
|
-
reference: string;
|
|
116
|
-
kind: string;
|
|
117
|
-
from: AccountRef;
|
|
118
|
-
to: AccountRef;
|
|
119
|
-
amount: Money;
|
|
120
|
-
atMs: number;
|
|
121
|
-
memo?: string;
|
|
122
|
-
}): Transaction;
|
|
123
|
-
/**
|
|
124
|
-
* Place a hold: available → held, same owner.
|
|
125
|
-
*
|
|
126
|
-
* Not a lock. After this posting `availableOf()` returns less, so the next
|
|
127
|
-
* balance check sees the reduced figure with no reservation table, no expiry
|
|
128
|
-
* sweeper and no way for a crashed process to strand funds behind a lease.
|
|
129
|
-
*/
|
|
130
|
-
export declare const placeHold: (args: {
|
|
131
|
-
reference: string;
|
|
132
|
-
owner: string;
|
|
133
|
-
kind?: AccountKind;
|
|
134
|
-
amount: Money;
|
|
135
|
-
atMs: number;
|
|
136
|
-
memo?: string;
|
|
137
|
-
}) => Transaction;
|
|
138
|
-
/** Release a hold: held → available. The order was cancelled, nothing moved. */
|
|
139
|
-
export declare const releaseHold: (args: {
|
|
140
|
-
reference: string;
|
|
141
|
-
owner: string;
|
|
142
|
-
kind?: AccountKind;
|
|
143
|
-
amount: Money;
|
|
144
|
-
atMs: number;
|
|
145
|
-
}) => Transaction;
|
|
146
|
-
/**
|
|
147
|
-
* Capture a hold: held → somewhere else. The thing it was held for happened.
|
|
148
|
-
*
|
|
149
|
-
* Deliberately separate from `releaseHold`. Collapsing them into one "resolve"
|
|
150
|
-
* with a flag is how a cancellation ends up paying somebody: the two have
|
|
151
|
-
* opposite effects and should not share a code path.
|
|
152
|
-
*/
|
|
153
|
-
export declare const captureHold: (args: {
|
|
154
|
-
reference: string;
|
|
155
|
-
owner: string;
|
|
156
|
-
kind?: AccountKind;
|
|
157
|
-
to: AccountRef;
|
|
158
|
-
amount: Money;
|
|
159
|
-
atMs: number;
|
|
160
|
-
memo?: string;
|
|
161
|
-
}) => Transaction;
|
|
162
|
-
/**
|
|
163
|
-
* Sum entries into a balance per account and asset.
|
|
164
|
-
*
|
|
165
|
-
* Derived rather than stored, because a stored balance and its postings can
|
|
166
|
-
* disagree — and when they do, nobody can say which is right. Production reads
|
|
167
|
-
* a cached figure the store maintains; this is what proves the cache correct.
|
|
168
|
-
*/
|
|
169
|
-
export declare function balancesFrom(transactions: readonly Transaction[]): Map<string, Map<string, Money>>;
|
|
170
|
-
export declare function balanceOf(balances: Map<string, Map<string, Money>>, account: AccountRef, asset: string): Money;
|
|
171
|
-
/** Spendable now. What `requireBalance` should be given as `available`. */
|
|
172
|
-
export declare const availableOf: (balances: Map<string, Map<string, Money>>, owner: string, asset: string, kind?: AccountKind) => Money;
|
|
173
|
-
export declare const heldOf: (balances: Map<string, Map<string, Money>>, owner: string, asset: string, kind?: AccountKind) => Money;
|
|
174
|
-
/** Available plus held. What the owner has, not what they can spend. */
|
|
175
|
-
export declare const totalOf: (balances: Map<string, Map<string, Money>>, owner: string, asset: string, kind?: AccountKind) => Money;
|
|
176
|
-
export interface TrialBalance {
|
|
177
|
-
ok: boolean;
|
|
178
|
-
perAsset: {
|
|
179
|
-
asset: string;
|
|
180
|
-
total: bigint;
|
|
181
|
-
accounts: number;
|
|
182
|
-
}[];
|
|
183
|
-
/** Assets that do not sum to zero, with the discrepancy. */
|
|
184
|
-
discrepancies: {
|
|
185
|
-
asset: string;
|
|
186
|
-
off: bigint;
|
|
187
|
-
}[];
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Does the whole book balance?
|
|
191
|
-
*
|
|
192
|
-
* The check that finds a bug rather than preventing one. Every transaction is
|
|
193
|
-
* validated on the way in, so a non-zero total here means something wrote
|
|
194
|
-
* around this module or a row was edited — which is exactly the class of
|
|
195
|
-
* problem that is invisible until a customer notices.
|
|
196
|
-
*
|
|
197
|
-
* Run it on a schedule. A book that balanced yesterday and does not today has a
|
|
198
|
-
* bounded set of transactions to inspect; one nobody checks has a year of them.
|
|
199
|
-
*/
|
|
200
|
-
export declare function trialBalance(transactions: readonly Transaction[]): TrialBalance;
|
|
201
|
-
/**
|
|
202
|
-
* A human-readable statement for one account.
|
|
203
|
-
*
|
|
204
|
-
* Ordered by time with a running balance, because the question during a dispute
|
|
205
|
-
* is never "what is the balance" — it is "when did it become that".
|
|
206
|
-
*/
|
|
207
|
-
export declare function statement(transactions: readonly Transaction[], account: AccountRef, asset: string): {
|
|
208
|
-
atMs: number;
|
|
209
|
-
reference: string;
|
|
210
|
-
kind: string;
|
|
211
|
-
amount: string;
|
|
212
|
-
balance: string;
|
|
213
|
-
}[];
|
|
214
|
-
/**
|
|
215
|
-
* Would this leave the account short?
|
|
216
|
-
*
|
|
217
|
-
* A convenience for a caller that already holds the balances. The real guard is
|
|
218
|
-
* `requireBalance` at the route, which returns the right status and the right
|
|
219
|
-
* body; this exists so a job — which has no request to refuse — can make the
|
|
220
|
-
* same check without inventing a second answer.
|
|
221
|
-
*/
|
|
222
|
-
export declare function assertAvailable(balances: Map<string, Map<string, Money>>, args: {
|
|
223
|
-
owner: string;
|
|
224
|
-
amount: Money;
|
|
225
|
-
kind?: AccountKind;
|
|
226
|
-
}): void;
|
|
227
|
-
export declare const VERSION = "0.1.0";
|