alberta-buck-core 0.1.1
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 +66 -0
- package/package.json +49 -0
- package/src/agents/arb.js +61 -0
- package/src/agents/saver.js +72 -0
- package/src/agents/trader.js +70 -0
- package/src/agents/whale.js +45 -0
- package/src/backends.js +54 -0
- package/src/buckworld.js +198 -0
- package/src/chart.js +70 -0
- package/src/identity-core.js +248 -0
- package/src/identity-web.js +19 -0
- package/src/identity.js +32 -0
- package/src/index.js +13 -0
- package/src/journal.js +86 -0
- package/src/nodefs.js +52 -0
- package/src/prices.js +38 -0
- package/src/router.js +76 -0
- package/src/scenarios/eqworld.js +517 -0
- package/src/scenarios/mini.js +35 -0
- package/src/scenarios/onepool.js +59 -0
- package/src/session.js +122 -0
- package/src/v3.js +48 -0
- package/src/wallet-core.js +75 -0
- package/src/wallet.js +23 -0
- package/src/world.js +21 -0
package/src/chart.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Tiny dependency-free SVG line charts, BigInt-free numbers in, string
|
|
2
|
+
// out -- renders identically in node (write to a .svg file) and the
|
|
3
|
+
// browser (innerHTML), so the demo page and bin/eqsim.mjs share it.
|
|
4
|
+
|
|
5
|
+
const COLORS = ["#2563eb", "#dc2626", "#059669", "#d97706", "#7c3aed"];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param opts.title chart title
|
|
9
|
+
* @param opts.series [{label, points: [[x, y], ...]}] -- y as Number
|
|
10
|
+
* @param opts.refY optional dashed reference line (e.g. 1.0 parity)
|
|
11
|
+
* @param opts.yFmt tick formatter
|
|
12
|
+
* @returns a standalone <svg> fragment string (nestable via svgDoc)
|
|
13
|
+
*/
|
|
14
|
+
export function lineChart({ title, series, width = 860, height = 240,
|
|
15
|
+
refY = null, yFmt = (v) => v.toPrecision(4) }) {
|
|
16
|
+
const m = { l: 64, r: 14, t: 26, b: 22 };
|
|
17
|
+
const pts = series.flatMap((s) => s.points);
|
|
18
|
+
const xs = pts.map((p) => p[0]), ys = pts.map((p) => p[1]);
|
|
19
|
+
const xmin = Math.min(...xs), xmax = Math.max(...xs);
|
|
20
|
+
let ymin = Math.min(...ys, refY ?? Infinity);
|
|
21
|
+
let ymax = Math.max(...ys, refY ?? -Infinity);
|
|
22
|
+
const pad = (ymax - ymin || Math.abs(ymax) || 1) * 0.08;
|
|
23
|
+
ymin -= pad; ymax += pad;
|
|
24
|
+
const sx = (x) => m.l + ((x - xmin) / (xmax - xmin || 1)) * (width - m.l - m.r);
|
|
25
|
+
const sy = (y) => height - m.b - ((y - ymin) / (ymax - ymin)) * (height - m.t - m.b);
|
|
26
|
+
|
|
27
|
+
const yTicks = [0, 1, 2, 3, 4].map((i) => ymin + (i / 4) * (ymax - ymin));
|
|
28
|
+
const xTicks = [0, 1, 2, 3, 4, 5].map((i) => xmin + (i / 5) * (xmax - xmin));
|
|
29
|
+
const el = [];
|
|
30
|
+
el.push(`<rect width="${width}" height="${height}" fill="#ffffff"/>`);
|
|
31
|
+
el.push(`<text x="${m.l}" y="16" font-size="13" font-weight="bold" ` +
|
|
32
|
+
`font-family="monospace">${title}</text>`);
|
|
33
|
+
for (const t of yTicks) {
|
|
34
|
+
el.push(`<line x1="${m.l}" y1="${sy(t)}" x2="${width - m.r}" y2="${sy(t)}" ` +
|
|
35
|
+
`stroke="#e5e7eb"/>`);
|
|
36
|
+
el.push(`<text x="${m.l - 6}" y="${sy(t) + 4}" font-size="10" ` +
|
|
37
|
+
`font-family="monospace" text-anchor="end">${yFmt(t)}</text>`);
|
|
38
|
+
}
|
|
39
|
+
for (const t of xTicks) {
|
|
40
|
+
el.push(`<text x="${sx(t)}" y="${height - 6}" font-size="10" ` +
|
|
41
|
+
`font-family="monospace" text-anchor="middle">${Math.round(t)}</text>`);
|
|
42
|
+
}
|
|
43
|
+
if (refY !== null) {
|
|
44
|
+
el.push(`<line x1="${m.l}" y1="${sy(refY)}" x2="${width - m.r}" ` +
|
|
45
|
+
`y2="${sy(refY)}" stroke="#9ca3af" stroke-dasharray="5,4"/>`);
|
|
46
|
+
}
|
|
47
|
+
series.forEach((s, i) => {
|
|
48
|
+
const path = s.points.map((p) => `${sx(p[0]).toFixed(1)},${sy(p[1]).toFixed(1)}`)
|
|
49
|
+
.join(" ");
|
|
50
|
+
const c = s.color ?? COLORS[i % COLORS.length];
|
|
51
|
+
el.push(`<polyline points="${path}" fill="none" stroke="${c}" stroke-width="1.6"/>`);
|
|
52
|
+
el.push(`<text x="${width - m.r}" y="${m.t + 12 * i}" font-size="11" ` +
|
|
53
|
+
`font-family="monospace" text-anchor="end" fill="${c}">${s.label}</text>`);
|
|
54
|
+
});
|
|
55
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" ` +
|
|
56
|
+
`height="${height}" viewBox="0 0 ${width} ${height}">${el.join("")}</svg>`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Stack chart fragments vertically into one SVG document. */
|
|
60
|
+
export function svgDoc(charts, { width = 860 } = {}) {
|
|
61
|
+
let y = 0;
|
|
62
|
+
const parts = [];
|
|
63
|
+
for (const c of charts) {
|
|
64
|
+
const h = Number(c.match(/height="(\d+)"/)[1]);
|
|
65
|
+
parts.push(`<svg y="${y}">${c.replace(/<\/?svg[^>]*>/g, "")}</svg>`);
|
|
66
|
+
y += h + 8;
|
|
67
|
+
}
|
|
68
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${y}">` +
|
|
69
|
+
parts.join("") + `</svg>`;
|
|
70
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// BigInt-native wrapper over the buck-identity wasm kernel -- the
|
|
2
|
+
// environment-free CORE. `wrapIdentity(wasm)` takes the raw wasm module
|
|
3
|
+
// (0x-hex string ABI) however the host loaded it -- node require of the
|
|
4
|
+
// nodejs-target package (identity.js) or awaited init of the web-target
|
|
5
|
+
// package (identity-web.js) -- and returns the structured API:
|
|
6
|
+
//
|
|
7
|
+
// point {x, y} (BigInt; {0n, 0n} = infinity)
|
|
8
|
+
// G2 point {x: [c0, c1], y: [c0, c1]}
|
|
9
|
+
// ciphertext {R: point, C: point}
|
|
10
|
+
// PS sig {sigma_1: point, sigma_2: point}
|
|
11
|
+
// proofs objects with the Python dataclass field names
|
|
12
|
+
//
|
|
13
|
+
// Deterministic: every nonce is an argument. `randScalar()` draws from
|
|
14
|
+
// WebCrypto for live use; tests replay recorded nonces from
|
|
15
|
+
// core/vectors/identity-kernel-vectors.json.
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Pure helpers (no wasm)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export const hex = (v) => "0x" + BigInt(v).toString(16).padStart(64, "0");
|
|
22
|
+
export const big = (s) => BigInt(s);
|
|
23
|
+
|
|
24
|
+
/** THE canonical JSON dialect: sorted keys, compact separators, raw
|
|
25
|
+
* UTF-8 -- byte-identical to Python's canonical_json(), shared by the
|
|
26
|
+
* identity preimage (canonical_identity_data) and the AB-RCPT/1 receipt
|
|
27
|
+
* core. Values must be strings and integers (floats are not canonical).
|
|
28
|
+
* JSON.stringify emits this natively once keys are sorted. */
|
|
29
|
+
export function canonicalIdentity(fields) {
|
|
30
|
+
const sort = (v) => {
|
|
31
|
+
if (Array.isArray(v)) return v.map(sort);
|
|
32
|
+
if (v && typeof v === "object") {
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.keys(v).sort().map((k) => [k, sort(v[k])]),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return v;
|
|
38
|
+
};
|
|
39
|
+
return JSON.stringify(sort(fields));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// The wrapped API
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export function wrapIdentity(wasm) {
|
|
47
|
+
const P = (arr, i = 0) => ({ x: big(arr[i]), y: big(arr[i + 1]) });
|
|
48
|
+
const CT = (arr, i = 0) => ({ R: P(arr, i), C: P(arr, i + 2) });
|
|
49
|
+
const flatP = (p) => [hex(p.x), hex(p.y)];
|
|
50
|
+
const flatCT = (ct) => [...flatP(ct.R), ...flatP(ct.C)];
|
|
51
|
+
const flatG2 = (g) => [hex(g.x[0]), hex(g.x[1]), hex(g.y[0]), hex(g.y[1])];
|
|
52
|
+
|
|
53
|
+
const ORDER = big(wasm.order());
|
|
54
|
+
|
|
55
|
+
/** Random non-zero scalar mod ORDER via WebCrypto. */
|
|
56
|
+
function randScalar() {
|
|
57
|
+
for (;;) {
|
|
58
|
+
const bytes = new Uint8Array(32);
|
|
59
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
60
|
+
let v = 0n;
|
|
61
|
+
for (const b of bytes) v = (v << 8n) | BigInt(b);
|
|
62
|
+
v %= ORDER;
|
|
63
|
+
if (v !== 0n) return v;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
hex, big, canonicalIdentity, randScalar,
|
|
69
|
+
|
|
70
|
+
// ---- constants --------------------------------------------------------
|
|
71
|
+
ORDER,
|
|
72
|
+
F_R: ORDER,
|
|
73
|
+
FIELD_MODULUS: big(wasm.field_modulus()),
|
|
74
|
+
G1: P(wasm.g1_generator()),
|
|
75
|
+
G2: (() => {
|
|
76
|
+
const g = wasm.g2_generator();
|
|
77
|
+
return { x: [big(g[0]), big(g[1])], y: [big(g[2]), big(g[3])] };
|
|
78
|
+
})(),
|
|
79
|
+
H_POINT: P(wasm.h_point()),
|
|
80
|
+
FLAVOR_A1: 1,
|
|
81
|
+
FLAVOR_A2: 2,
|
|
82
|
+
FLAVOR_B1: 3,
|
|
83
|
+
|
|
84
|
+
// ---- curve ops / hashes ------------------------------------------------
|
|
85
|
+
g1Add: (a, b) => P(wasm.g1_add(...flatP(a), ...flatP(b))),
|
|
86
|
+
g1Mul: (p, k) => P(wasm.g1_mul(...flatP(p), hex(k))),
|
|
87
|
+
g1Neg: (p) => P(wasm.g1_neg(...flatP(p))),
|
|
88
|
+
g2Mul: (g, k) => {
|
|
89
|
+
const r = wasm.g2_mul(...flatG2(g), hex(k));
|
|
90
|
+
return { x: [big(r[0]), big(r[1])], y: [big(r[2]), big(r[3])] };
|
|
91
|
+
},
|
|
92
|
+
/** EVM ecPairing semantics: pairs = [[g1Point, g2Point], ...]. */
|
|
93
|
+
pairingCheck: (pairs) =>
|
|
94
|
+
wasm.pairing_check(pairs.flatMap(([p, q]) => [...flatP(p), ...flatG2(q)])),
|
|
95
|
+
keccakScalar: (words) => big(wasm.keccak_scalar(words.map(hex))),
|
|
96
|
+
identityScalar: (canonical) => big(wasm.identity_scalar(canonical)),
|
|
97
|
+
reduceModOrder: (v) => big(wasm.reduce_mod_order(hex(v))),
|
|
98
|
+
poseidon: (inputs) => big(wasm.poseidon(inputs.map(hex))),
|
|
99
|
+
|
|
100
|
+
// ---- ElGamal / PS ------------------------------------------------------
|
|
101
|
+
elgamalEncrypt: (M, pk, r) =>
|
|
102
|
+
CT(wasm.elgamal_encrypt(...flatP(M), ...flatP(pk), hex(r))),
|
|
103
|
+
elgamalDecrypt: (E, sk) => P(wasm.elgamal_decrypt(...flatCT(E), hex(sk))),
|
|
104
|
+
psSign(skX, skY, m, t) {
|
|
105
|
+
const r = wasm.ps_sign(hex(skX), hex(skY), hex(m), hex(t));
|
|
106
|
+
return { sigma_1: P(r, 0), sigma_2: P(r, 2) };
|
|
107
|
+
},
|
|
108
|
+
psVerify: (pkX, pkY, sig, m) =>
|
|
109
|
+
wasm.ps_verify(flatG2(pkX), flatG2(pkY),
|
|
110
|
+
...flatP(sig.sigma_1), ...flatP(sig.sigma_2), hex(m)),
|
|
111
|
+
psRerandomize(sig, t) {
|
|
112
|
+
const r = wasm.ps_rerandomize(...flatP(sig.sigma_1), ...flatP(sig.sigma_2), hex(t));
|
|
113
|
+
return { sigma_1: P(r, 0), sigma_2: P(r, 2) };
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// ---- Schnorr batch binding ---------------------------------------------
|
|
117
|
+
/** Raw UNREDUCED keccak word (what the Schnorr transcript signs). */
|
|
118
|
+
batchCommitment: (cms) => big(wasm.batch_commitment(cms.map(hex))),
|
|
119
|
+
issuerSchnorrSign(skIss, hBatch, issuer, chainid, k) {
|
|
120
|
+
const r = wasm.issuer_schnorr_sign(
|
|
121
|
+
hex(skIss), hex(hBatch), hex(issuer), hex(chainid), hex(k));
|
|
122
|
+
return { e: big(r[0]), s: big(r[1]), R: P(r, 2) };
|
|
123
|
+
},
|
|
124
|
+
issuerSchnorrVerify: (pkIss, proof, hBatch, issuer, chainid) =>
|
|
125
|
+
wasm.issuer_schnorr_verify(
|
|
126
|
+
...flatP(pkIss),
|
|
127
|
+
[hex(proof.e), hex(proof.s), ...flatP(proof.R)],
|
|
128
|
+
hex(hBatch), hex(issuer), hex(chainid)),
|
|
129
|
+
|
|
130
|
+
// ---- Registration NIZK -------------------------------------------------
|
|
131
|
+
registrationProve(sig, m, r, pk, E, registrant, mTilde, rTilde) {
|
|
132
|
+
const o = wasm.registration_prove(
|
|
133
|
+
[...flatP(sig.sigma_1), ...flatP(sig.sigma_2)],
|
|
134
|
+
hex(m), hex(r), ...flatP(pk), flatCT(E),
|
|
135
|
+
hex(registrant), hex(mTilde), hex(rTilde));
|
|
136
|
+
return { e: big(o[0]), s_m: big(o[1]), s_r: big(o[2]),
|
|
137
|
+
A_ps: P(o, 3), T_C: P(o, 5), T_R: P(o, 7) };
|
|
138
|
+
},
|
|
139
|
+
registrationVerify: (sig, E, pk, issuerX, issuerY, proof, registrant) =>
|
|
140
|
+
wasm.registration_verify(
|
|
141
|
+
[...flatP(sig.sigma_1), ...flatP(sig.sigma_2)],
|
|
142
|
+
flatCT(E), ...flatP(pk), flatG2(issuerX), flatG2(issuerY),
|
|
143
|
+
[hex(proof.e), hex(proof.s_m), hex(proof.s_r),
|
|
144
|
+
...flatP(proof.A_ps), ...flatP(proof.T_C), ...flatP(proof.T_R)],
|
|
145
|
+
hex(registrant)),
|
|
146
|
+
|
|
147
|
+
// ---- Chaum-Pedersen approve --------------------------------------------
|
|
148
|
+
chaumPedersenProve(eAlice, eBob, pkA, pkB, skA, rPrime,
|
|
149
|
+
sender, spender, chainid, k1, k2) {
|
|
150
|
+
const o = wasm.chaum_pedersen_prove(
|
|
151
|
+
flatCT(eAlice), flatCT(eBob), ...flatP(pkA), ...flatP(pkB),
|
|
152
|
+
hex(skA), hex(rPrime), hex(sender), hex(spender), hex(chainid),
|
|
153
|
+
hex(k1), hex(k2));
|
|
154
|
+
return { e: big(o[0]), s1: big(o[1]), s2: big(o[2]),
|
|
155
|
+
T1: P(o, 3), T2: P(o, 5), T3: P(o, 7) };
|
|
156
|
+
},
|
|
157
|
+
chaumPedersenVerify: (eAlice, eBob, pkA, pkB, proof, sender, spender, chainid) =>
|
|
158
|
+
wasm.chaum_pedersen_verify(
|
|
159
|
+
flatCT(eAlice), flatCT(eBob), ...flatP(pkA), ...flatP(pkB),
|
|
160
|
+
[hex(proof.e), hex(proof.s1), hex(proof.s2),
|
|
161
|
+
...flatP(proof.T1), ...flatP(proof.T2), ...flatP(proof.T3)],
|
|
162
|
+
hex(sender), hex(spender), hex(chainid)),
|
|
163
|
+
|
|
164
|
+
// ---- Verifiable decryption ---------------------------------------------
|
|
165
|
+
verifiableDecryptProve(E, sk, M, account, chainid, t) {
|
|
166
|
+
const o = wasm.verifiable_decrypt_prove(
|
|
167
|
+
flatCT(E), hex(sk), ...flatP(M), hex(account), hex(chainid), hex(t));
|
|
168
|
+
return { e: big(o[0]), s: big(o[1]), T1: P(o, 2), T2: P(o, 4) };
|
|
169
|
+
},
|
|
170
|
+
verifiableDecryptVerify: (E, pk, M, proof, account, chainid) =>
|
|
171
|
+
wasm.verifiable_decrypt_verify(
|
|
172
|
+
flatCT(E), ...flatP(pk), ...flatP(M),
|
|
173
|
+
[hex(proof.e), hex(proof.s), ...flatP(proof.T1), ...flatP(proof.T2)],
|
|
174
|
+
hex(account), hex(chainid)),
|
|
175
|
+
|
|
176
|
+
// ---- A2 issuer re-encryption binding -----------------------------------
|
|
177
|
+
issuerReencProve(skIss, rPrime, pkRec, eReg, eIss, issuer, chainid,
|
|
178
|
+
beta, gamma, kR, kB, kS, kG) {
|
|
179
|
+
const o = wasm.issuer_reenc_prove(
|
|
180
|
+
hex(skIss), hex(rPrime), ...flatP(pkRec), flatCT(eReg), flatCT(eIss),
|
|
181
|
+
hex(issuer), hex(chainid),
|
|
182
|
+
hex(beta), hex(gamma), hex(kR), hex(kB), hex(kS), hex(kG));
|
|
183
|
+
return {
|
|
184
|
+
e: big(o[0]), s_r: big(o[1]), s_b: big(o[2]), s_s: big(o[3]), s_g: big(o[4]),
|
|
185
|
+
A1: P(o, 5), A2: P(o, 7), A3: P(o, 9), A4: P(o, 11), A5: P(o, 13),
|
|
186
|
+
Q: P(o, 15), U: P(o, 17), T: P(o, 19),
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
issuerReencVerify: (pkIss, eReg, eIss, proof, issuer, chainid) =>
|
|
190
|
+
wasm.issuer_reenc_verify(
|
|
191
|
+
...flatP(pkIss), flatCT(eReg), flatCT(eIss),
|
|
192
|
+
[hex(proof.e), hex(proof.s_r), hex(proof.s_b), hex(proof.s_s), hex(proof.s_g),
|
|
193
|
+
...flatP(proof.A1), ...flatP(proof.A2), ...flatP(proof.A3),
|
|
194
|
+
...flatP(proof.A4), ...flatP(proof.A5),
|
|
195
|
+
...flatP(proof.Q), ...flatP(proof.U), ...flatP(proof.T)],
|
|
196
|
+
hex(issuer), hex(chainid)),
|
|
197
|
+
|
|
198
|
+
// ---- Deposit coupling / B1 depositor binding ----------------------------
|
|
199
|
+
depositCoupleProve(mRec, skDep, eDep, eIss, account, chainid, b, kM, kS, kB) {
|
|
200
|
+
const o = wasm.deposit_couple_prove(
|
|
201
|
+
hex(mRec), hex(skDep), flatCT(eDep), flatCT(eIss),
|
|
202
|
+
hex(account), hex(chainid), hex(b), hex(kM), hex(kS), hex(kB));
|
|
203
|
+
return {
|
|
204
|
+
e: big(o[0]), s_m: big(o[1]), s_s: big(o[2]), s_b: big(o[3]),
|
|
205
|
+
A2: P(o, 4), A3: P(o, 6), A4: P(o, 8), P_I: P(o, 10),
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
depositCoupleVerify: (pkDep, eDep, eIss, proof, account, chainid) =>
|
|
209
|
+
wasm.deposit_couple_verify(
|
|
210
|
+
...flatP(pkDep), flatCT(eDep), flatCT(eIss),
|
|
211
|
+
[hex(proof.e), hex(proof.s_m), hex(proof.s_s), hex(proof.s_b),
|
|
212
|
+
...flatP(proof.A2), ...flatP(proof.A3), ...flatP(proof.A4), ...flatP(proof.P_I)],
|
|
213
|
+
hex(account), hex(chainid)),
|
|
214
|
+
b1BindProve(mDep, skDep, eDep, pkIss, account, chainid, r, b, kM, kS, kR, kB) {
|
|
215
|
+
const o = wasm.b1_bind_prove(
|
|
216
|
+
hex(mDep), hex(skDep), flatCT(eDep), ...flatP(pkIss),
|
|
217
|
+
hex(account), hex(chainid),
|
|
218
|
+
hex(r), hex(b), hex(kM), hex(kS), hex(kR), hex(kB));
|
|
219
|
+
return {
|
|
220
|
+
proof: {
|
|
221
|
+
e: big(o[0]), s_m: big(o[1]), s_s: big(o[2]), s_r: big(o[3]), s_b: big(o[4]),
|
|
222
|
+
A2: P(o, 5), A4: P(o, 7), B1: P(o, 9), B2: P(o, 11),
|
|
223
|
+
A_p: P(o, 13), P_dep: P(o, 15),
|
|
224
|
+
},
|
|
225
|
+
eDepForIss: CT(o, 17),
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
b1BindVerify: (pkDep, eDep, pkIss, eDepForIss, proof, account, chainid) =>
|
|
229
|
+
wasm.b1_bind_verify(
|
|
230
|
+
...flatP(pkDep), flatCT(eDep), ...flatP(pkIss), flatCT(eDepForIss),
|
|
231
|
+
[hex(proof.e), hex(proof.s_m), hex(proof.s_s), hex(proof.s_r), hex(proof.s_b),
|
|
232
|
+
...flatP(proof.A2), ...flatP(proof.A4), ...flatP(proof.B1),
|
|
233
|
+
...flatP(proof.B2), ...flatP(proof.A_p), ...flatP(proof.P_dep)],
|
|
234
|
+
hex(account), hex(chainid)),
|
|
235
|
+
|
|
236
|
+
// ---- Notes family --------------------------------------------------------
|
|
237
|
+
noteCommitment: (flavor, v, rho, idHash, predicate) =>
|
|
238
|
+
big(wasm.note_commitment(Number(flavor), hex(v), hex(rho), hex(idHash), hex(predicate))),
|
|
239
|
+
nullifierB: (rho, idHash) => big(wasm.nullifier_b(hex(rho), hex(idHash))),
|
|
240
|
+
nullifierA: (rho, idHash) => big(wasm.nullifier_a(hex(rho), hex(idHash))),
|
|
241
|
+
idHashB1: (mIssuer, sigmaR, sigmaS) =>
|
|
242
|
+
big(wasm.id_hash_b1(hex(mIssuer), ...flatP(sigmaR), hex(sigmaS))),
|
|
243
|
+
idHashA1: (eNote, mIssuer, sigmaR, sigmaS) =>
|
|
244
|
+
big(wasm.id_hash_a1(flatCT(eNote), hex(mIssuer), ...flatP(sigmaR), hex(sigmaS))),
|
|
245
|
+
idHashA2: (eNote, eIss) => big(wasm.id_hash_a2(flatCT(eNote), flatCT(eIss))),
|
|
246
|
+
identityLeaf: (M) => big(wasm.identity_leaf(...flatP(M))),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Browser entry for the buck-identity kernel: initializes the WEB-target
|
|
2
|
+
// wasm package (built by `make nix-core-build-wasm-web` into
|
|
3
|
+
// the alberta-buck-kernel package) and wraps it in the same BigInt-native API node code
|
|
4
|
+
// gets from identity.js.
|
|
5
|
+
//
|
|
6
|
+
// Always pass an explicit wasm source -- a URL string the page serves
|
|
7
|
+
// (e.g. "./wasm-web/buck_identity_bg.wasm"), a Response/Promise thereof,
|
|
8
|
+
// or a BufferSource -- because bundlers break wasm-bindgen's default
|
|
9
|
+
// import.meta.url-relative fetch.
|
|
10
|
+
|
|
11
|
+
import { wrapIdentity } from "./identity-core.js";
|
|
12
|
+
|
|
13
|
+
export async function loadIdentity(wasmSource) {
|
|
14
|
+
const mod = await import("alberta-buck-kernel/web/identity");
|
|
15
|
+
await mod.default(
|
|
16
|
+
wasmSource !== undefined ? { module_or_path: wasmSource } : undefined,
|
|
17
|
+
);
|
|
18
|
+
return wrapIdentity(mod);
|
|
19
|
+
}
|
package/src/identity.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Node entry for the buck-identity kernel: loads the nodejs-target wasm
|
|
2
|
+
// package (built by `make nix-core-build-wasm`) and wraps it in the
|
|
3
|
+
// BigInt-native API. The wrapper itself lives in identity-core.js
|
|
4
|
+
// (environment-free); the browser loads the web-target package through
|
|
5
|
+
// identity-web.js instead.
|
|
6
|
+
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
|
|
9
|
+
import { wrapIdentity } from "./identity-core.js";
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const api = wrapIdentity(require("alberta-buck-kernel/identity"));
|
|
13
|
+
|
|
14
|
+
export default api;
|
|
15
|
+
export const {
|
|
16
|
+
hex, big, canonicalIdentity, randScalar,
|
|
17
|
+
ORDER, F_R, FIELD_MODULUS, G1, G2, H_POINT,
|
|
18
|
+
FLAVOR_A1, FLAVOR_A2, FLAVOR_B1,
|
|
19
|
+
g1Add, g1Mul, g1Neg, g2Mul, pairingCheck,
|
|
20
|
+
keccakScalar, identityScalar, reduceModOrder, poseidon,
|
|
21
|
+
elgamalEncrypt, elgamalDecrypt,
|
|
22
|
+
psSign, psVerify, psRerandomize,
|
|
23
|
+
batchCommitment, issuerSchnorrSign, issuerSchnorrVerify,
|
|
24
|
+
registrationProve, registrationVerify,
|
|
25
|
+
chaumPedersenProve, chaumPedersenVerify,
|
|
26
|
+
verifiableDecryptProve, verifiableDecryptVerify,
|
|
27
|
+
issuerReencProve, issuerReencVerify,
|
|
28
|
+
depositCoupleProve, depositCoupleVerify,
|
|
29
|
+
b1BindProve, b1BindVerify,
|
|
30
|
+
noteCommitment, nullifierB, nullifierA,
|
|
31
|
+
idHashB1, idHashA1, idHashA2, identityLeaf,
|
|
32
|
+
} = api;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// alberta-buck-core -- the JS side of the Alberta Buck platform.
|
|
2
|
+
// See alberta-buck-platform.org and core/README.md.
|
|
3
|
+
|
|
4
|
+
export { FIELDS, JournalWriter, parseJournal, mismatches, totalGas,
|
|
5
|
+
summarize } from "./journal.js";
|
|
6
|
+
export { CALL_GAS, DEPLOY_GAS, Session } from "./session.js";
|
|
7
|
+
export { DEV_KEYS, devAccount, anvilSession, tevmSession } from "./backends.js";
|
|
8
|
+
export { MIN_SQRT_RATIO, MAX_SQRT_RATIO, HUGE, Q96, isqrt, sqrtPriceX96,
|
|
9
|
+
fullRangeTicks, spotFromSqrtPriceX96 } from "./v3.js";
|
|
10
|
+
export { runDays } from "./world.js";
|
|
11
|
+
export { PinWhale } from "./agents/whale.js";
|
|
12
|
+
export { RoundTripTrader } from "./agents/trader.js";
|
|
13
|
+
export { buildOnePool } from "./scenarios/onepool.js";
|
package/src/journal.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Journal: the shared JSONL operation record of a ChainSession run.
|
|
2
|
+
//
|
|
3
|
+
// Schema (alberta-buck-platform.org, Layer 1; pinned by
|
|
4
|
+
// core/vectors/journal-sample.jsonl, asserted identically by the Python
|
|
5
|
+
// suite): one JSON object per line with fields
|
|
6
|
+
// i, tag, op, fn, sender, expect, outcome, matched, gas, tx, block, err
|
|
7
|
+
// Unknown fields are ignored; extra whitespace/blank lines are tolerated.
|
|
8
|
+
//
|
|
9
|
+
// JournalWriter mirrors buck_core.session.Journal: entries in FIELDS
|
|
10
|
+
// order, compact separators, missing fields as "" -- so identical runs
|
|
11
|
+
// produce byte-identical lines across languages. It writes through a
|
|
12
|
+
// caller-supplied sink so this module stays environment-agnostic; use
|
|
13
|
+
// nodefs.js fileJournalWriter() for a file sink in Node.
|
|
14
|
+
|
|
15
|
+
const REQUIRED = ["i", "op", "fn", "expect", "outcome", "matched"];
|
|
16
|
+
|
|
17
|
+
export const FIELDS = ["i", "tag", "op", "fn", "sender", "expect", "outcome",
|
|
18
|
+
"matched", "gas", "tx", "block", "err"];
|
|
19
|
+
|
|
20
|
+
export class JournalWriter {
|
|
21
|
+
/** @param {(line: string) => void} sink called with one JSONL line per entry */
|
|
22
|
+
constructor(sink) {
|
|
23
|
+
this.sink = sink;
|
|
24
|
+
this.seq = 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Record one entry; fills schema order, returns the entry object. */
|
|
28
|
+
record(fields) {
|
|
29
|
+
this.seq += 1;
|
|
30
|
+
const entry = { i: this.seq };
|
|
31
|
+
for (const f of FIELDS) {
|
|
32
|
+
if (f !== "i") entry[f] = fields[f] ?? "";
|
|
33
|
+
}
|
|
34
|
+
this.sink(JSON.stringify(entry) + "\n");
|
|
35
|
+
return entry;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Parse JSONL journal text into an array of entry objects. */
|
|
40
|
+
export function parseJournal(text) {
|
|
41
|
+
const entries = [];
|
|
42
|
+
for (const [n, raw] of text.split("\n").entries()) {
|
|
43
|
+
const line = raw.trim();
|
|
44
|
+
if (!line) continue;
|
|
45
|
+
let entry;
|
|
46
|
+
try {
|
|
47
|
+
entry = JSON.parse(line);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
throw new Error(`journal line ${n + 1}: invalid JSON: ${e.message}`);
|
|
50
|
+
}
|
|
51
|
+
for (const f of REQUIRED) {
|
|
52
|
+
if (!(f in entry)) {
|
|
53
|
+
throw new Error(`journal line ${n + 1}: missing field '${f}'`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
entries.push(entry);
|
|
57
|
+
}
|
|
58
|
+
return entries;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Entries whose outcome contradicted their declared expectation. */
|
|
62
|
+
export function mismatches(entries) {
|
|
63
|
+
return entries.filter((e) => e.matched === false);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Total gasUsed across entries. */
|
|
67
|
+
export function totalGas(entries) {
|
|
68
|
+
return entries.reduce((sum, e) => sum + (e.gas ?? 0), 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** One-object rollup of a journal: op counts, reverts, mismatches, gas. */
|
|
72
|
+
export function summarize(entries) {
|
|
73
|
+
const ops = {};
|
|
74
|
+
let reverts = 0;
|
|
75
|
+
for (const e of entries) {
|
|
76
|
+
ops[e.op] = (ops[e.op] ?? 0) + 1;
|
|
77
|
+
if (e.outcome === "revert") reverts += 1;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
entries: entries.length,
|
|
81
|
+
ops,
|
|
82
|
+
reverts,
|
|
83
|
+
mismatches: mismatches(entries).length,
|
|
84
|
+
gas: totalGas(entries),
|
|
85
|
+
};
|
|
86
|
+
}
|
package/src/nodefs.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Node-only IO glue: repo/artifact discovery and file journal sinks.
|
|
2
|
+
// Keep environment-specific imports here so session.js/journal.js/v3.js
|
|
3
|
+
// stay loadable in the browser (Phase 4).
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync, mkdirSync, appendFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
import { JournalWriter } from "./journal.js";
|
|
9
|
+
|
|
10
|
+
/** The repository checkout root: ALBERTA_BUCK_REPO, or walk up from cwd
|
|
11
|
+
* (then from this file) looking for foundry.toml -- the same resolution
|
|
12
|
+
* the Python side uses (buck_core.artifacts.repo_root). */
|
|
13
|
+
export function repoRoot() {
|
|
14
|
+
const starts = [];
|
|
15
|
+
if (process.env.ALBERTA_BUCK_REPO) starts.push(process.env.ALBERTA_BUCK_REPO);
|
|
16
|
+
starts.push(process.cwd(), dirname(new URL(import.meta.url).pathname));
|
|
17
|
+
for (const start of starts) {
|
|
18
|
+
let p = resolve(start);
|
|
19
|
+
for (;;) {
|
|
20
|
+
if (existsSync(join(p, "foundry.toml"))) return p;
|
|
21
|
+
const up = dirname(p);
|
|
22
|
+
if (up === p) break;
|
|
23
|
+
p = up;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw new Error(
|
|
27
|
+
"alberta-buck repo root not found (looked for foundry.toml); " +
|
|
28
|
+
"set ALBERTA_BUCK_REPO or run from inside the repo checkout");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Return {abi, bytecode} for out/<solFile or name>.sol/<name>.json. */
|
|
32
|
+
export function loadArtifact(name, solFile = null) {
|
|
33
|
+
const f = join(repoRoot(), "out", `${solFile ?? name}.sol`, `${name}.json`);
|
|
34
|
+
const art = JSON.parse(readFileSync(f, "utf8"));
|
|
35
|
+
return { abi: art.abi, bytecode: art.bytecode.object };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** True when the Foundry artifacts are built (tests skip when not). */
|
|
39
|
+
export function artifactsAvailable() {
|
|
40
|
+
try {
|
|
41
|
+
loadArtifact("MockERC20");
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A JournalWriter appending JSONL lines to `path`. */
|
|
49
|
+
export function fileJournalWriter(path) {
|
|
50
|
+
mkdirSync(dirname(resolve(path)), { recursive: true });
|
|
51
|
+
return new JournalWriter((line) => appendFileSync(path, line));
|
|
52
|
+
}
|
package/src/prices.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Seeded synthetic price walk -- deterministic BigInt-only math so any
|
|
2
|
+
// platform (or a re-run) reproduces the same path bit-for-bit. The demo's
|
|
3
|
+
// stand-in for the Python sim's CSV Brownian-bridge quotes; the Stage-4
|
|
4
|
+
// parity mini-scenario pins its targets in core/vectors/mini-scenario.json
|
|
5
|
+
// instead (no cross-language RNG at all).
|
|
6
|
+
|
|
7
|
+
/** A 64-bit LCG (MMIX constants) over BigInt. */
|
|
8
|
+
export function lcg(seed) {
|
|
9
|
+
let s = BigInt(seed) & ((1n << 64n) - 1n);
|
|
10
|
+
return () => {
|
|
11
|
+
s = (s * 6364136223846793005n + 1442695040888963407n) & ((1n << 64n) - 1n);
|
|
12
|
+
return s;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A multiplicative random walk: each step moves the price by a draw in
|
|
18
|
+
* [-stepBp, +stepBp] basis points.
|
|
19
|
+
*
|
|
20
|
+
* @param opts.seed walk seed
|
|
21
|
+
* @param opts.start starting price (quote base units per whole token)
|
|
22
|
+
* @param opts.stepBp max per-step move in basis points (default 150)
|
|
23
|
+
* @param opts.steps path length
|
|
24
|
+
* @returns BigInt[] of length `steps` (walk[0] === start)
|
|
25
|
+
*/
|
|
26
|
+
export function seededWalk({ seed, start, stepBp = 150, steps }) {
|
|
27
|
+
const next = lcg(seed);
|
|
28
|
+
const out = [BigInt(start)];
|
|
29
|
+
const span = BigInt(2 * stepBp + 1);
|
|
30
|
+
while (out.length < steps) {
|
|
31
|
+
const delta = (next() % span) - BigInt(stepBp); // [-stepBp, +stepBp]
|
|
32
|
+
const prev = out[out.length - 1];
|
|
33
|
+
let p = (prev * (10_000n + delta)) / 10_000n;
|
|
34
|
+
if (p < 1n) p = 1n;
|
|
35
|
+
out.push(p);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
package/src/router.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// REAL Uniswap periphery for JS worlds: the Universal Router deploy
|
|
2
|
+
// recipe the Python sim proved on anvil (deploy.py), and the
|
|
3
|
+
// V3_SWAP_EXACT_IN encoding (mirroring alberta_buck/sim/router.py).
|
|
4
|
+
//
|
|
5
|
+
// The pools were always real (UniswapV3Factory/Pool); SimLP only stood in
|
|
6
|
+
// for the periphery. Doctrine: SimLP remains for simulation god-modes
|
|
7
|
+
// (whale pinning, liquidity seeding); agent and USER swaps go through the
|
|
8
|
+
// real router -- the code path an integrator copies, and the surface real
|
|
9
|
+
// JS AMM tooling (@uniswap/v3-sdk et al.) can be pointed at.
|
|
10
|
+
//
|
|
11
|
+
// Wiring facts (proven by the Python sim, mirrored here):
|
|
12
|
+
// * permit2 is a DUMMY address: swaps use the pre-fund route
|
|
13
|
+
// (transfer the input to the router, execute with payerIsUser=false),
|
|
14
|
+
// so Permit2 is never consulted. Real Permit2 is optional follow-up
|
|
15
|
+
// for signature-based approvals.
|
|
16
|
+
// * poolInitCodeHash = keccak of OUR compiled UniswapV3Pool creation
|
|
17
|
+
// bytecode -- the router computes pool addresses via CREATE2, and the
|
|
18
|
+
// locally-built pool hash differs from mainnet's canonical one.
|
|
19
|
+
// * in BUCK worlds the router must be identity-bound public+carrying
|
|
20
|
+
// (it transiently custodies BUCK mid-route); deploy.py shows the bind.
|
|
21
|
+
|
|
22
|
+
import { encodeAbiParameters, encodePacked, keccak256 } from "viem";
|
|
23
|
+
|
|
24
|
+
export const ZERO_ADDR = "0x" + "00".repeat(20);
|
|
25
|
+
export const DUMMY_PERMIT2 = "0x" + "be".repeat(20); // pre-fund route: unused
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Deploy the Universal Router from the vendored artifact
|
|
29
|
+
* (alberta_buck/sim/artifacts/UniversalRouter.json), wired exactly as the
|
|
30
|
+
* Python sim wires it. RouterParameters field NAMES come from the
|
|
31
|
+
* artifact's ABI; the VALUES follow deploy.py's order.
|
|
32
|
+
*
|
|
33
|
+
* @param session a Session (deployer pays)
|
|
34
|
+
* @param urArtifact the parsed UniversalRouter.json artifact
|
|
35
|
+
* @param opts.weth WETH9 address (unused by V3 swaps; real for fidelity)
|
|
36
|
+
* @param opts.v3Factory our UniswapV3Factory address
|
|
37
|
+
* @param opts.poolInitCode our UniswapV3Pool CREATION bytecode (0x-hex)
|
|
38
|
+
*/
|
|
39
|
+
export async function deployUniversalRouter(session, urArtifact,
|
|
40
|
+
{ weth, v3Factory, poolInitCode, gas = 15_000_000n }) {
|
|
41
|
+
const ctor = urArtifact.abi.find((e) => e.type === "constructor");
|
|
42
|
+
const comps = ctor.inputs[0].components;
|
|
43
|
+
const vals = [DUMMY_PERMIT2, weth, ZERO_ADDR, v3Factory,
|
|
44
|
+
"0x" + "00".repeat(32), keccak256(poolInitCode),
|
|
45
|
+
ZERO_ADDR, ZERO_ADDR, ZERO_ADDR, ZERO_ADDR];
|
|
46
|
+
if (comps.length !== vals.length) {
|
|
47
|
+
throw new Error(`UniversalRouter constructor has ${comps.length} ` +
|
|
48
|
+
`params; the deploy.py recipe wires ${vals.length}`);
|
|
49
|
+
}
|
|
50
|
+
const params = Object.fromEntries(comps.map((c, i) => [c.name, vals[i]]));
|
|
51
|
+
// Accept the raw forge-artifact shape ({bytecode: {object}}) and the
|
|
52
|
+
// flattened bundle shape ({bytecode: "0x.."}) alike.
|
|
53
|
+
const bytecode = urArtifact.bytecode.object ?? urArtifact.bytecode;
|
|
54
|
+
return session.deploy(
|
|
55
|
+
{ abi: urArtifact.abi, bytecode },
|
|
56
|
+
[params], { name: "UniversalRouter", gas });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** [tokenA, fee0, tokenB, fee1, tokenC, ...] -> packed V3 path bytes. */
|
|
60
|
+
export function encodePath(tokensFees) {
|
|
61
|
+
const types = tokensFees.map((_, i) => (i % 2 === 0 ? "address" : "uint24"));
|
|
62
|
+
return encodePacked(types, tokensFees);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* (commands, inputs) for UniversalRouter.execute V3_SWAP_EXACT_IN on the
|
|
67
|
+
* pre-fund route (payerIsUser=false: the router pays from its own
|
|
68
|
+
* balance, so transfer amountIn to the router first).
|
|
69
|
+
*/
|
|
70
|
+
export function urExecArgs(recipient, amountIn, path) {
|
|
71
|
+
const input = encodeAbiParameters(
|
|
72
|
+
[{ type: "address" }, { type: "uint256" }, { type: "uint256" },
|
|
73
|
+
{ type: "bytes" }, { type: "bool" }, { type: "uint256[]" }],
|
|
74
|
+
[recipient, amountIn, 0n, path, false, []]);
|
|
75
|
+
return ["0x00", [input]]; // 0x00 = V3_SWAP_EXACT_IN
|
|
76
|
+
}
|