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/src/session.js ADDED
@@ -0,0 +1,122 @@
1
+ // ChainSession, JS side: send/call/deploy with declared expectations and
2
+ // the shared JSONL journal (alberta-buck-platform.org, Layer 1).
3
+ //
4
+ // The peer of buck_core.session.Web3Session with identical semantics:
5
+ // * expect: "ok" (default) or "revert". An unexpected revert THROWS
6
+ // (with the Solidity reason, extracted by replaying the call at the
7
+ // receipt's block); an unexpected success logs a warning. Either way
8
+ // the outcome is journaled and contradictions accumulate in
9
+ // session.mismatches.
10
+ // * reads (call) are not journaled.
11
+ //
12
+ // Works over any viem-compatible client -- anvil/testnets by HTTP
13
+ // transport, Tevm's in-process MemoryClient in Node or the browser (see
14
+ // backends.js). Contract handles are plain {abi, address} objects.
15
+
16
+ export const CALL_GAS = 12_000_000n;
17
+ export const DEPLOY_GAS = 55_000_000n;
18
+
19
+ export class Session {
20
+ /**
21
+ * @param client viem client with public + wallet actions
22
+ * @param opts.account default viem Account for sends/deploys
23
+ * @param opts.journal a JournalWriter (or null: journaling off)
24
+ * @param opts.txOverrides merged into every tx, e.g. {gasPrice: 0n} on
25
+ * the zero-fee anvil profile the Python sim uses
26
+ */
27
+ constructor(client, { account, journal = null, txOverrides = {} } = {}) {
28
+ this.client = client;
29
+ this.account = account;
30
+ this.journal = journal;
31
+ this.txOverrides = txOverrides;
32
+ this.mismatches = [];
33
+ this.lastRevertReason = "";
34
+ }
35
+
36
+ /** A contract handle: just {abi, address}. */
37
+ contractAt(abi, address) {
38
+ return { abi, address };
39
+ }
40
+
41
+ async call(contract, functionName, args = []) {
42
+ return this.client.readContract({
43
+ address: contract.address, abi: contract.abi, functionName, args,
44
+ });
45
+ }
46
+
47
+ async send(contract, functionName, args = [],
48
+ { account, gas = CALL_GAS, value = 0n,
49
+ expect = "ok", tag = "" } = {}) {
50
+ const acct = account ?? this.account;
51
+ const hash = await this.client.writeContract({
52
+ address: contract.address, abi: contract.abi, functionName, args,
53
+ account: acct, gas, value, chain: null, ...this.txOverrides,
54
+ });
55
+ const rcpt = await this.client.waitForTransactionReceipt({ hash });
56
+ const ok = rcpt.status === "success";
57
+ const err = ok ? "" : await this.#revertReason(
58
+ contract, functionName, args, acct, gas, value, rcpt.blockNumber);
59
+ this.lastRevertReason = err;
60
+ const entry = this.#journalOp(
61
+ "send", functionName, acct.address, expect, ok, rcpt, err, tag);
62
+ // expect "either": a legitimately-uncertain send (e.g. a funding-gate
63
+ // mint that reverting IS the signal) -- journaled, never a mismatch;
64
+ // the caller reads rcpt.status.
65
+ if (ok && expect === "revert") {
66
+ console.warn(`expected REVERT but ${functionName} succeeded ` +
67
+ `(tag=${tag} tx=${rcpt.transactionHash})`);
68
+ this.mismatches.push(entry);
69
+ }
70
+ if (!ok && expect === "ok") {
71
+ this.mismatches.push(entry);
72
+ throw new Error(`tx reverted: ${functionName} :: ${err}`);
73
+ }
74
+ return rcpt;
75
+ }
76
+
77
+ /** Deploy from an {abi, bytecode} artifact; returns a contract handle. */
78
+ async deploy(artifact, args = [],
79
+ { account, gas = DEPLOY_GAS, name = "", tag = "" } = {}) {
80
+ const acct = account ?? this.account;
81
+ const hash = await this.client.deployContract({
82
+ abi: artifact.abi, bytecode: artifact.bytecode, args,
83
+ account: acct, gas, chain: null, ...this.txOverrides,
84
+ });
85
+ const rcpt = await this.client.waitForTransactionReceipt({ hash });
86
+ const ok = rcpt.status === "success";
87
+ this.#journalOp("deploy", "constructor", acct.address, "ok", ok, rcpt,
88
+ ok ? "" : "deploy reverted", tag || `deploy:${name}`);
89
+ if (!ok) throw new Error(`deploy ${name} reverted`);
90
+ return this.contractAt(artifact.abi, rcpt.contractAddress);
91
+ }
92
+
93
+ async #revertReason(contract, functionName, args, acct, gas, value, block) {
94
+ try {
95
+ await this.client.simulateContract({
96
+ address: contract.address, abi: contract.abi, functionName, args,
97
+ account: acct, gas, value, blockNumber: block,
98
+ });
99
+ } catch (e) {
100
+ // Prefer the decoded Solidity reason: a require string ("SPL") or a
101
+ // custom error name (ERC20InsufficientBalance) over viem's generic
102
+ // "The contract function ... reverted." shortMessage.
103
+ const revert = e.walk?.((c) => c?.name === "ContractFunctionRevertedError");
104
+ const reason = revert?.reason ?? revert?.data?.errorName;
105
+ return String(reason ?? e.shortMessage ?? e.message ?? e).slice(0, 400);
106
+ }
107
+ return "";
108
+ }
109
+
110
+ #journalOp(op, fn, sender, expect, ok, rcpt, err, tag) {
111
+ const entry = {
112
+ tag, op, fn, sender,
113
+ expect, outcome: ok ? "ok" : "revert",
114
+ matched: (ok ? "ok" : "revert") === expect,
115
+ gas: Number(rcpt.gasUsed ?? 0n),
116
+ tx: rcpt.transactionHash ?? "",
117
+ block: Number(rcpt.blockNumber ?? 0n),
118
+ err,
119
+ };
120
+ return this.journal ? this.journal.record(entry) : entry;
121
+ }
122
+ }
package/src/v3.js ADDED
@@ -0,0 +1,48 @@
1
+ // Uniswap V3 price/tick math, BigInt throughout -- mirrors
2
+ // alberta_buck/sim/router.py (sqrt_price_x96, full_range_ticks) exactly.
3
+
4
+ export const MIN_SQRT_RATIO = 4295128739n;
5
+ export const MAX_SQRT_RATIO =
6
+ 1461446703485210103287273052203988822378723970342n;
7
+
8
+ /** int256 "swap everything until the price limit" amount (whale snaps). */
9
+ export const HUGE = (1n << 127n) - 1n;
10
+
11
+ export const Q96 = 1n << 96n;
12
+
13
+ /** Integer square root (floor), Newton's method on BigInt. */
14
+ export function isqrt(n) {
15
+ if (n < 0n) throw new Error("isqrt of negative");
16
+ if (n < 2n) return n;
17
+ let x = 1n << (BigInt(n.toString(2).length + 1) >> 1n);
18
+ for (;;) {
19
+ const y = (x + n / x) >> 1n;
20
+ if (y >= x) return x;
21
+ x = y;
22
+ }
23
+ }
24
+
25
+ /** sqrtPriceX96 so 1 `baseAmt` of base costs `quoteAmt` of quote
26
+ * (token0/token1 by address order, exactly as router.py). */
27
+ export function sqrtPriceX96(base, baseAmt, quote, quoteAmt) {
28
+ const baseIs0 = base.toLowerCase() < quote.toLowerCase();
29
+ const a0 = baseIs0 ? baseAmt : quoteAmt;
30
+ const a1 = baseIs0 ? quoteAmt : baseAmt;
31
+ return isqrt((a1 << 192n) / a0);
32
+ }
33
+
34
+ /** Full-range tick bounds for a spacing; truncates toward zero to stay
35
+ * inside [-887272, 887272], matching Solidity (MIN_TICK/spacing)*spacing. */
36
+ export function fullRangeTicks(spacing) {
37
+ const hi = Math.trunc(887272 / spacing) * spacing;
38
+ return [-hi, hi];
39
+ }
40
+
41
+ /** Spot price of one whole token (10^dec base units) in quote base units,
42
+ * from a pool's sqrtPriceX96. The inverse of sqrtPriceX96(). */
43
+ export function spotFromSqrtPriceX96(sqrtX96, token, tokenDec, quote) {
44
+ const tokenIs0 = token.toLowerCase() < quote.toLowerCase();
45
+ const unit = 10n ** BigInt(tokenDec);
46
+ const num = sqrtX96 * sqrtX96;
47
+ return tokenIs0 ? (num * unit) >> 192n : (unit << 192n) / num;
48
+ }
@@ -0,0 +1,75 @@
1
+ // Structured wrapper over the buck-wallet + buck-registry wasm kernel --
2
+ // the environment-free CORE. `wrapWallet(wasm)` takes the raw wasm
3
+ // module (the same buck_identity package: one blob carries all three
4
+ // kernels) and returns the wallet/registry API:
5
+ //
6
+ // receipt cores canonical JSON text (the serialization IS the object)
7
+ // structured args plain objects in the vector-fixture shapes --
8
+ // "0x..." hex words, {x, y} points, {R, C} ciphertexts
9
+ // certificates Uint8Array wire bytes (the Python-pinned formats)
10
+ // accumulators the MerkleTree / Aggregator wasm classes
11
+ //
12
+ // Deterministic: every nonce rides inside the args (`nonces: {...}`),
13
+ // exactly the order the Python reference draws them.
14
+
15
+ export function wrapWallet(wasm) {
16
+ const toText = (v) => (typeof v === "string" ? v : JSON.stringify(v));
17
+ const enc = new TextEncoder();
18
+ const toBytes = (v) => (typeof v === "string" ? enc.encode(v) : v);
19
+
20
+ return {
21
+ // ---- canonical dialect + envelope ---------------------------------
22
+ canonicalJson: (v) => wasm.wallet_canonical_json(toText(v)),
23
+ canonicalIdentityData: (v) => wasm.wallet_canonical_identity_data(toText(v)),
24
+ receiptId: (canonical, prefixLen = 12) =>
25
+ wasm.wallet_receipt_id(toBytes(canonical), prefixLen),
26
+ envelopeText: (canonical, width = 64) =>
27
+ wasm.wallet_envelope_text(toBytes(canonical), width),
28
+ parseEnvelope: (text) => wasm.wallet_parse_envelope(text),
29
+
30
+ // ---- receipts ------------------------------------------------------
31
+ /** Build any receipt kind from named args; returns canonical text. */
32
+ buildReceipt: (args) => wasm.wallet_build_receipt(toText(args)),
33
+ /** Tier-1 offline verify -> {ok, reason, identity_M, value}. */
34
+ verifyReceipt: (core) => JSON.parse(wasm.wallet_verify_receipt(toText(core))),
35
+
36
+ // ---- unilateral identity-targeted Note flows -----------------------
37
+ mintUnilateralA2: (a) => JSON.parse(wasm.wallet_mint_unilateral_a2(toText(a))),
38
+ makeReceiptA2: (a) => JSON.parse(wasm.wallet_make_receipt_a2(toText(a))),
39
+ verifyReceiptA2: (a) => JSON.parse(wasm.wallet_verify_receipt_a2(toText(a))),
40
+ mintUnilateralA1: (a) => JSON.parse(wasm.wallet_mint_unilateral_a1(toText(a))),
41
+ makeReceiptA1: (a) => JSON.parse(wasm.wallet_make_receipt_a1(toText(a))),
42
+ verifyReceiptA1: (a) => JSON.parse(wasm.wallet_verify_receipt_a1(toText(a))),
43
+
44
+ // ---- issuer ceremony -----------------------------------------------
45
+ issueCredential: (a) => JSON.parse(wasm.wallet_issue_credential(toText(a))),
46
+
47
+ // ---- registry ------------------------------------------------------
48
+ registry: {
49
+ /** -> [e, s, Rx, Ry] hex words. */
50
+ schnorrSign: (sk, msgHash, registryId, chainid, k) =>
51
+ wasm.registry_schnorr_sign(sk, msgHash, registryId, chainid, k),
52
+ schnorrVerify: (pk, e, s, r, msgHash, registryId, chainid) =>
53
+ wasm.registry_schnorr_verify(
54
+ pk.x, pk.y, e, s, r.x, r.y, msgHash, registryId, chainid),
55
+ /** -> SignedCertificate wire bytes (Python-pinned format). */
56
+ signCertificate: (sk, registryId, canonical, serial, issuedAt, expiresAt, chainid, k) =>
57
+ wasm.registry_sign_certificate(
58
+ sk, registryId, canonical,
59
+ BigInt(serial), BigInt(issuedAt), BigInt(expiresAt), chainid, k),
60
+ verifyCertificate: (wire, chainid) =>
61
+ wasm.registry_verify_certificate(wire, chainid),
62
+ sealCertificate: (wire, clientPk, r) =>
63
+ wasm.registry_seal_certificate(wire, clientPk.x, clientPk.y, r),
64
+ unsealCertificate: (envelope, clientSk) =>
65
+ wasm.registry_unseal_certificate(envelope, clientSk),
66
+ /** Sub-tree + aggregator path pair -> bool. */
67
+ verifyFullProof: (subProof, aggProof) =>
68
+ wasm.registry_verify_full_proof(toText(subProof), toText(aggProof)),
69
+ /** The identity Merkle accumulator (stateful wasm class). */
70
+ MerkleTree: wasm.MerkleTree,
71
+ /** The central sub-root aggregator (stateful wasm class). */
72
+ Aggregator: wasm.Aggregator,
73
+ },
74
+ };
75
+ }
package/src/wallet.js ADDED
@@ -0,0 +1,23 @@
1
+ // Node entry for the buck-wallet + buck-registry kernels: loads the
2
+ // nodejs-target wasm package (the same buck_identity blob; built by
3
+ // `make nix-core-build-wasm`) and wraps it in the structured API. The
4
+ // wrapper itself lives in wallet-core.js (environment-free); a browser
5
+ // loads the web-target package and calls wrapWallet itself.
6
+
7
+ import { createRequire } from "node:module";
8
+
9
+ import { wrapWallet } from "./wallet-core.js";
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const api = wrapWallet(require("alberta-buck-kernel/identity"));
13
+
14
+ export default api;
15
+ export const {
16
+ canonicalJson, canonicalIdentityData,
17
+ receiptId, envelopeText, parseEnvelope,
18
+ buildReceipt, verifyReceipt,
19
+ mintUnilateralA2, makeReceiptA2, verifyReceiptA2,
20
+ mintUnilateralA1, makeReceiptA1, verifyReceiptA1,
21
+ issueCredential,
22
+ registry,
23
+ } = api;
package/src/world.js ADDED
@@ -0,0 +1,21 @@
1
+ // The agent loop, deliberately tiny (understandability is a decision of
2
+ // record). A "world" is a plain object the scenario builder returns --
3
+ // at minimum {session}, plus whatever contract handles the agents need.
4
+ // An agent is any object with optional `setup(world)` and required
5
+ // `act(world, day, tick)`, both async. Mirrors the Python sim's
6
+ // setup()/act(day, tick) shape.
7
+
8
+ export async function runDays(world, agents,
9
+ { days, ticksPerDay = 1, onDay = null } = {}) {
10
+ for (const a of agents) {
11
+ if (a.setup) await a.setup(world);
12
+ }
13
+ for (let day = 0; day < days; day++) {
14
+ for (let tick = 0; tick < ticksPerDay; tick++) {
15
+ for (const a of agents) {
16
+ await a.act(world, day, tick);
17
+ }
18
+ }
19
+ if (onDay) await onDay(day);
20
+ }
21
+ }