@finchip.ai/foga 0.0.0-stage → 0.6.0-alpha.4

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 (131) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +216 -2
  4. package/artifacts/bundle.json +74250 -0
  5. package/artifacts/bundle.sha256 +1 -0
  6. package/bin/foga.js +14 -0
  7. package/build/known-interface-ids.json +18 -0
  8. package/build/profiles.json +120 -0
  9. package/dist/build/bundle.js +417 -0
  10. package/dist/build/vendor.js +74 -0
  11. package/dist/bundle/load.js +24 -0
  12. package/dist/bundle/types.js +1 -0
  13. package/dist/cli-cell.js +395 -0
  14. package/dist/cli.js +505 -0
  15. package/dist/core/acdf.js +162 -0
  16. package/dist/core/adrf.js +273 -0
  17. package/dist/core/aid.js +83 -0
  18. package/dist/core/ard.js +146 -0
  19. package/dist/core/asf.js +157 -0
  20. package/dist/core/canonical.js +79 -0
  21. package/dist/core/erc8004.js +52 -0
  22. package/dist/core/kya.js +225 -0
  23. package/dist/core/measure.js +96 -0
  24. package/dist/core/pack/cbor.js +130 -0
  25. package/dist/core/pack/skillpack.js +142 -0
  26. package/dist/core/skills.js +40 -0
  27. package/dist/core/swarm.js +206 -0
  28. package/dist/core/tenders.js +121 -0
  29. package/dist/core/units.js +122 -0
  30. package/dist/core/world.js +104 -0
  31. package/dist/demo/demander.js +76 -0
  32. package/dist/demo/evidence.js +1 -0
  33. package/dist/demo/loop-d.js +449 -0
  34. package/dist/doctor/checks.js +51 -0
  35. package/dist/doctor/doctor.js +385 -0
  36. package/dist/doctor/loop-d.js +103 -0
  37. package/dist/doctor/probe.js +97 -0
  38. package/dist/doctor/semantic.js +83 -0
  39. package/dist/genesis/extract.js +64 -0
  40. package/dist/genesis/funding.js +126 -0
  41. package/dist/genesis/operator-scope.js +90 -0
  42. package/dist/genesis/preflight.js +54 -0
  43. package/dist/genesis/probe.js +164 -0
  44. package/dist/genesis/roles.js +96 -0
  45. package/dist/genesis/run.js +71 -0
  46. package/dist/genesis/scenario.js +765 -0
  47. package/dist/genesis/skill-runner.js +103 -0
  48. package/dist/genesis/skill-worker.js +41 -0
  49. package/dist/genesis/t1-zk.js +62 -0
  50. package/dist/genesis/t3-zk.js +105 -0
  51. package/dist/genesis/t4.js +259 -0
  52. package/dist/genesis/t5.js +168 -0
  53. package/dist/genesis/t6.js +222 -0
  54. package/dist/genesis/t7.js +198 -0
  55. package/dist/genesis/t8.js +189 -0
  56. package/dist/genesis/t9.js +188 -0
  57. package/dist/genesis/zk.js +128 -0
  58. package/dist/http/server.js +276 -0
  59. package/dist/kernel/cell.js +104 -0
  60. package/dist/kernel/errors.js +78 -0
  61. package/dist/kernel/kernel.js +251 -0
  62. package/dist/kernel/provider.js +125 -0
  63. package/dist/kernel/store.js +470 -0
  64. package/dist/kernel/tx.js +385 -0
  65. package/dist/mcp/server.js +170 -0
  66. package/dist/node/config.js +195 -0
  67. package/dist/node/init.js +200 -0
  68. package/dist/node/keys.js +95 -0
  69. package/dist/node/migrate.js +173 -0
  70. package/dist/node/model/anthropic.js +97 -0
  71. package/dist/node/model/stub.js +129 -0
  72. package/dist/node/model/types.js +9 -0
  73. package/dist/node/node.js +198 -0
  74. package/dist/node/policy.js +95 -0
  75. package/dist/node/runner.js +256 -0
  76. package/dist/node/watchers.js +88 -0
  77. package/dist/tools/admin.js +184 -0
  78. package/dist/tools/catalog.js +261 -0
  79. package/dist/tools/common.js +143 -0
  80. package/dist/tools/identity.js +107 -0
  81. package/dist/tools/index.js +14 -0
  82. package/dist/tools/read.js +471 -0
  83. package/dist/tools/tenders.js +400 -0
  84. package/dist/tools/types.js +4 -0
  85. package/dist/util/bytecode.js +123 -0
  86. package/dist/util/fs.js +58 -0
  87. package/dist/util/hash.js +4 -0
  88. package/dist/util/log.js +11 -0
  89. package/dist/util/version.js +12 -0
  90. package/dist/world/deploy.js +277 -0
  91. package/dist/world/plan.js +93 -0
  92. package/dist/world/types.js +1 -0
  93. package/docs/build.md +45 -0
  94. package/docs/cell-account.md +58 -0
  95. package/docs/compatibility-matrix.md +38 -0
  96. package/docs/design-memo.md +113 -0
  97. package/docs/doctor.md +53 -0
  98. package/docs/examples/README.md +11 -0
  99. package/docs/examples/claude-code-mcp.json +13 -0
  100. package/docs/examples/claude_desktop_config.json +12 -0
  101. package/docs/examples/cursor-mcp.json +11 -0
  102. package/docs/genesis.md +312 -0
  103. package/docs/kernel.md +101 -0
  104. package/docs/loop-d.md +90 -0
  105. package/docs/mcp.md +105 -0
  106. package/docs/node.md +66 -0
  107. package/docs/version-lock.md +67 -0
  108. package/docs/world.md +50 -0
  109. package/docs/zk.md +48 -0
  110. package/package.json +82 -4
  111. package/prover/README.md +26 -0
  112. package/prover/build-predicate.mjs +77 -0
  113. package/prover/package-lock.json +1837 -0
  114. package/prover/package.json +15 -0
  115. package/prover/run.cjs +199 -0
  116. package/scenarios/fixtures/sample-filing.txt +16 -0
  117. package/scenarios/skills/finch-extract-verify/LICENSE +2 -0
  118. package/scenarios/skills/finch-extract-verify/SKILL.md +34 -0
  119. package/scenarios/skills/finch-extract-verify/fields.json +56 -0
  120. package/scenarios/skills/finch-extract-verify/manifest.json +16 -0
  121. package/scenarios/skills/foga-probe/LICENSE +2 -0
  122. package/scenarios/skills/foga-probe/SKILL.md +35 -0
  123. package/scenarios/skills/foga-probe/manifest.json +16 -0
  124. package/scenarios/skills/foga-probe/probe.schema.json +40 -0
  125. package/server.json +79 -0
  126. package/vendor/LOCK.json +107 -0
  127. package/vendor/README.md +13 -0
  128. package/worlds/local.json +34 -0
  129. package/worlds/sepolia.json +314 -0
  130. package/zk/build/verification_key.json +174 -0
  131. package/zk/circuits/kya_predicate_count_v1.circom +231 -0
@@ -0,0 +1 @@
1
+ b09770ec43673cca94f9f6feb5ad6050a24aee510792078fd5c87bed389cdcd2 bundle.json
package/bin/foga.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ // Launcher for the published package: runs the compiled CLI.
3
+ // During development use `npm run foga -- <args>` (tsx) instead.
4
+ import { existsSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import { dirname, join } from "node:path";
7
+
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ const dist = join(here, "..", "dist", "cli.js");
10
+ if (!existsSync(dist)) {
11
+ console.error("foga: compiled CLI not found. Run `npm run build` first, or use `npm run foga -- <args>`.");
12
+ process.exit(1);
13
+ }
14
+ await import(dist);
@@ -0,0 +1,18 @@
1
+ {
2
+ "skill-token-standard": {
3
+ "source": "README.md (compiler-verified ids published upstream; the repository has no vectors file)",
4
+ "ids": {
5
+ "ISkillToken": "0x734553a6",
6
+ "IOnchainSkillDocument": "0x7050dd2c"
7
+ }
8
+ },
9
+ "task-token-standard": {
10
+ "source": "README.md and examples/DEPLOYMENTS.md (published upstream; the repository has no vectors file)",
11
+ "ids": {
12
+ "ITaskToken": "0xcdaeb26d",
13
+ "ITaskTender": "0xc319d532",
14
+ "ITaskVerifier": "0x9977db15",
15
+ "IOnchainTaskDocument": "0xeb078d05"
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,120 @@
1
+ {
2
+ "schema": "foga.build-profiles/1",
3
+ "note": "One entry per upstream repository. `native` runs `forge build` inside the vendored repository with its own foundry.toml (tests and scripts skipped). `staged` is used for repositories whose upstream toolchain is solc-js: the contract tree is copied into a throw-away Foundry project whose settings mirror the upstream compile script exactly (same solc version, optimizer, runs, EVM version, metadata). Compile conditions are never changed by FOGA; only the orchestration is.",
4
+ "foundry": {
5
+ "skip": ["test", "script"],
6
+ "solcDirEnv": "FOGA_SOLC_DIR",
7
+ "solcBinaryPattern": "solc-{version}"
8
+ },
9
+ "repos": [
10
+ {
11
+ "repo": "skill-token-standard",
12
+ "erc": 8338,
13
+ "mode": "native",
14
+ "solc": "0.8.24",
15
+ "contractsRoot": "contracts",
16
+ "vectors": [],
17
+ "extras": {}
18
+ },
19
+ {
20
+ "repo": "task-token-standard",
21
+ "erc": 8414,
22
+ "mode": "native",
23
+ "solc": "0.8.24",
24
+ "contractsRoot": "contracts",
25
+ "vectors": [],
26
+ "extras": {}
27
+ },
28
+ {
29
+ "repo": "kya-standard",
30
+ "erc": 8419,
31
+ "mode": "staged",
32
+ "solc": "0.8.28",
33
+ "contractsRoot": "assets/erc-kya/contracts",
34
+ "stage": {
35
+ "copy": [
36
+ { "from": "assets/erc-kya/contracts", "to": "." },
37
+ { "from": "companions/zk-kya-groth16/build/Groth16Verifier.sol", "to": "companions/Groth16Verifier.sol" }
38
+ ],
39
+ "settings": { "optimizer": true, "runs": 200, "viaIr": false, "evmVersion": "cancun", "bytecodeHash": "ipfs" },
40
+ "mirrors": "tools/compile.js"
41
+ },
42
+ "vectors": ["assets/erc-kya/vectors/vectors.json"],
43
+ "extras": {}
44
+ },
45
+ {
46
+ "repo": "aid-standard",
47
+ "erc": 8434,
48
+ "mode": "staged",
49
+ "solc": "0.8.28",
50
+ "contractsRoot": "assets/erc-aid/contracts",
51
+ "stage": {
52
+ "copy": [{ "from": "assets/erc-aid/contracts", "to": "." }],
53
+ "settings": { "optimizer": true, "runs": 200, "viaIr": false, "evmVersion": "cancun", "bytecodeHash": "ipfs" },
54
+ "mirrors": "scripts/compile.js"
55
+ },
56
+ "vectors": ["assets/erc-aid/vectors/aid-vectors.json"],
57
+ "extras": {}
58
+ },
59
+ {
60
+ "repo": "acd-framework",
61
+ "erc": 8436,
62
+ "mode": "native",
63
+ "solc": "0.8.24",
64
+ "contractsRoot": "assets/erc-acdf/contracts",
65
+ "vectors": ["assets/erc-acdf/vectors/interface-ids.json"],
66
+ "extras": {}
67
+ },
68
+ {
69
+ "repo": "ard-onchain-framework",
70
+ "erc": 8444,
71
+ "mode": "native",
72
+ "solc": "0.8.24",
73
+ "contractsRoot": "assets/erc-8444/contracts",
74
+ "vectors": ["assets/erc-8444/vectors/interface-ids.json"],
75
+ "extras": { "kindIds": "assets/erc-8444/vectors/kind-ids.json" }
76
+ },
77
+ {
78
+ "repo": "swarm-ai-framework",
79
+ "erc": 8447,
80
+ "mode": "native",
81
+ "solc": "0.8.24",
82
+ "contractsRoot": "assets/erc-8447/contracts",
83
+ "vectors": ["assets/erc-8447/vectors/interface-ids.json"],
84
+ "extras": { "termIds": "assets/erc-8447/vectors/term-ids.json" }
85
+ },
86
+ {
87
+ "repo": "aifi-unit-framework",
88
+ "erc": 8454,
89
+ "mode": "native",
90
+ "solc": "0.8.30",
91
+ "contractsRoot": "assets/erc-auf/contracts",
92
+ "vectors": ["assets/erc-auf/vectors/interface-ids.json"],
93
+ "extras": { "identifiers": "assets/erc-auf/vectors/identifiers.json" }
94
+ },
95
+ {
96
+ "repo": "agent-safety-framework",
97
+ "erc": 8468,
98
+ "mode": "native",
99
+ "solc": "0.8.30",
100
+ "contractsRoot": "assets/erc-asf/contracts",
101
+ "vectors": ["assets/erc-asf/vectors/interface-ids.json"],
102
+ "extras": {}
103
+ },
104
+ {
105
+ "repo": "ai-dispute-resolution",
106
+ "erc": 8471,
107
+ "mode": "native",
108
+ "solc": "0.8.30",
109
+ "contractsRoot": "assets/erc-adrf/contracts",
110
+ "vectors": ["assets/erc-adrf/vectors/interface-ids.json"],
111
+ "extras": {}
112
+ }
113
+ ],
114
+ "local": {
115
+ "note": "FOGA's own local-only contracts (contracts/), compiled with contracts/foundry.toml. They never stand in for a public-chain deployment.",
116
+ "solc": "0.8.30",
117
+ "project": "contracts",
118
+ "contractsRoot": "src"
119
+ }
120
+ }
@@ -0,0 +1,417 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { writeFileSync } from "node:fs";
3
+ import { basename, join, relative } from "node:path";
4
+ import { canonicalJson, copyTree, ensureDir, exists, readJson, repoRoot, rmrf, walk, writeJson } from "../util/fs.js";
5
+ import { sha256Hex } from "../util/hash.js";
6
+ import { codeSize, runtimeIdentity } from "../util/bytecode.js";
7
+ import { log } from "../util/log.js";
8
+ import { loadLock, verifyVendor } from "./vendor.js";
9
+ function sh(cmd, args, cwd, env = {}) {
10
+ return execFileSync(cmd, args, {
11
+ cwd,
12
+ encoding: "utf8",
13
+ stdio: ["ignore", "pipe", "pipe"],
14
+ maxBuffer: 64 * 1024 * 1024,
15
+ env: { ...process.env, NO_COLOR: "1", FOUNDRY_PROFILE: "default", SEPOLIA_RPC_URL: process.env.SEPOLIA_RPC_URL ?? "http://127.0.0.1:1", ...env },
16
+ });
17
+ }
18
+ function forgeVersion() {
19
+ try {
20
+ const out = sh("forge", ["--version"], repoRoot());
21
+ const m = out.match(/Version:\s*([^\s]+)/) ?? out.match(/forge\s+([^\s]+)/);
22
+ return m ? m[1] : out.split("\n")[0].trim();
23
+ }
24
+ catch {
25
+ throw new Error("forge not found on PATH. Install Foundry (https://getfoundry.sh) or use the Docker image.");
26
+ }
27
+ }
28
+ function solcBinary(profiles, version, solcDir) {
29
+ const dir = solcDir ?? process.env[profiles.foundry.solcDirEnv];
30
+ if (!dir)
31
+ return undefined;
32
+ const bin = join(dir, profiles.foundry.solcBinaryPattern.replace("{version}", version));
33
+ return exists(bin) ? bin : undefined;
34
+ }
35
+ function forgeBuild(projectDir, outDir, skip, solcPath, extraArgs = []) {
36
+ rmrf(outDir);
37
+ ensureDir(outDir);
38
+ const args = ["build", "--ast", "--no-cache", "--out", outDir, ...extraArgs];
39
+ for (const s of skip)
40
+ args.push("--skip", s);
41
+ if (solcPath)
42
+ args.push("--use", solcPath);
43
+ try {
44
+ sh("forge", args, projectDir);
45
+ }
46
+ catch (e) {
47
+ const msg = (e.stderr?.toString?.() ?? "") + (e.stdout?.toString?.() ?? "");
48
+ throw new Error(`forge build failed in ${projectDir}:\n${msg.slice(-4000)}`);
49
+ }
50
+ }
51
+ function writeStagedProject(profile, repoDir, stageDir) {
52
+ if (!profile.stage)
53
+ throw new Error(`${profile.repo}: staged profile without stage spec`);
54
+ rmrf(stageDir);
55
+ ensureDir(stageDir);
56
+ for (const c of profile.stage.copy)
57
+ copyTree(join(repoDir, c.from), join(stageDir, c.to));
58
+ const s = profile.stage.settings;
59
+ const toml = [
60
+ "[profile.default]",
61
+ 'src = "."',
62
+ 'out = "out"',
63
+ "libs = []",
64
+ 'test = "__no_tests__"',
65
+ 'script = "__no_scripts__"',
66
+ "auto_detect_remappings = false",
67
+ "remappings = []",
68
+ `solc_version = "${profile.solc}"`,
69
+ `optimizer = ${s.optimizer}`,
70
+ `optimizer_runs = ${s.runs}`,
71
+ `via_ir = ${s.viaIr}`,
72
+ `evm_version = "${s.evmVersion}"`,
73
+ `bytecode_hash = "${s.bytecodeHash}"`,
74
+ "cbor_metadata = true",
75
+ "",
76
+ ].join("\n");
77
+ writeFileSync(join(stageDir, "foundry.toml"), toml);
78
+ }
79
+ function normalizeMetadata(raw) {
80
+ if (!raw)
81
+ return null;
82
+ if (typeof raw === "string") {
83
+ try {
84
+ return JSON.parse(raw);
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ return raw;
91
+ }
92
+ function classifyRole(kind, abstract, path, name) {
93
+ if (kind === "interface")
94
+ return "interface";
95
+ if (kind === "library")
96
+ return "library";
97
+ if (abstract)
98
+ return "abstract";
99
+ const p = "/" + path.replace(/\\/g, "/");
100
+ if (/\/mocks?\//i.test(p) || /^Mock/.test(name) || /Mock$/.test(name))
101
+ return "mock";
102
+ if (p.includes("/adapters/"))
103
+ return "adapter";
104
+ if (p.includes("/modules/"))
105
+ return "module";
106
+ if (p.includes("/bridges/"))
107
+ return "bridge";
108
+ if (p.includes("/verifiers/"))
109
+ return "verifier";
110
+ if (p.includes("/examples/"))
111
+ return "example";
112
+ if (p.includes("/companions/"))
113
+ return "companion";
114
+ if (p.includes("/judgment/"))
115
+ return "authority";
116
+ if (p.includes("/account/"))
117
+ return "account";
118
+ if (/Registry$/.test(name))
119
+ return "registry";
120
+ if (/Token$/.test(name) || /Vault$/.test(name))
121
+ return "kernel";
122
+ return "contract";
123
+ }
124
+ function xorSelectors(node) {
125
+ let acc = 0n;
126
+ let n = 0;
127
+ for (const child of node.nodes ?? []) {
128
+ if (child.nodeType === "FunctionDefinition" && typeof child.functionSelector === "string") {
129
+ acc ^= BigInt("0x" + child.functionSelector);
130
+ n++;
131
+ }
132
+ }
133
+ if (n === 0)
134
+ return null;
135
+ return "0x" + acc.toString(16).padStart(8, "0");
136
+ }
137
+ function extractArtifacts(opts) {
138
+ const contracts = [];
139
+ const solcVersions = new Set();
140
+ for (const file of walk(opts.outDir)) {
141
+ if (!file.endsWith(".json"))
142
+ continue;
143
+ if (file.includes(`${opts.outDir}/build-info/`))
144
+ continue;
145
+ const art = readJson(file);
146
+ const metadata = normalizeMetadata(art.metadata);
147
+ if (!metadata?.settings?.compilationTarget)
148
+ continue;
149
+ const targetEntries = Object.entries(metadata.settings.compilationTarget);
150
+ if (targetEntries.length !== 1)
151
+ continue;
152
+ const [sourcePath, name] = targetEntries[0];
153
+ if (name !== basename(file, ".json"))
154
+ continue;
155
+ const norm = sourcePath.replace(/\\/g, "/");
156
+ // Scope: only the contract tree each profile declares; never tests, scripts or vendored fixtures.
157
+ if (/^(test|script|lib)\//.test(norm) || /\/(test|script)\//.test(norm))
158
+ continue;
159
+ let upstreamPath = norm;
160
+ if (opts.mode === "native" || opts.mode === "local") {
161
+ if (!norm.startsWith(opts.contractsRoot.replace(/\/$/, "") + "/"))
162
+ continue;
163
+ }
164
+ else {
165
+ const copy = (opts.stageCopies ?? []).find((c) => c.to !== "." && norm.startsWith(c.to.replace(/\/$/, "")));
166
+ upstreamPath = copy ? join(copy.from, norm.slice(copy.to.length)).replace(/\\/g, "/") : `${opts.contractsRoot}/${norm}`;
167
+ }
168
+ let kind = "contract";
169
+ let abstract = false;
170
+ let baseContracts = [];
171
+ let interfaceId = null;
172
+ const ast = art.ast;
173
+ if (ast?.nodes) {
174
+ const node = ast.nodes.find((n) => n.nodeType === "ContractDefinition" && n.name === name);
175
+ if (node) {
176
+ kind = node.contractKind;
177
+ abstract = Boolean(node.abstract);
178
+ baseContracts = (node.baseContracts ?? []).map((b) => b.baseName?.name).filter(Boolean);
179
+ interfaceId = kind === "interface" ? xorSelectors(node) : null;
180
+ }
181
+ }
182
+ else {
183
+ const bc = art.bytecode?.object ?? "0x";
184
+ if (bc === "0x" && (art.abi?.length ?? 0) > 0)
185
+ kind = "interface";
186
+ }
187
+ const bytecode = art.bytecode?.object ?? "0x";
188
+ const deployedBytecode = art.deployedBytecode?.object ?? "0x";
189
+ const linkReferences = art.bytecode?.linkReferences ?? {};
190
+ const deployedLinkReferences = art.deployedBytecode?.linkReferences ?? {};
191
+ const immutableReferences = art.deployedBytecode?.immutableReferences ?? {};
192
+ const compiler = metadata.compiler?.version ?? "unknown";
193
+ solcVersions.add(compiler);
194
+ const s = metadata.settings ?? {};
195
+ const settings = {
196
+ optimizer: Boolean(s.optimizer?.enabled),
197
+ runs: typeof s.optimizer?.runs === "number" ? s.optimizer.runs : null,
198
+ viaIr: Boolean(s.viaIR),
199
+ evmVersion: s.evmVersion ?? null,
200
+ bytecodeHash: s.metadata?.bytecodeHash ?? null,
201
+ remappings: s.remappings ?? [],
202
+ };
203
+ const sourceKeccak = metadata.sources?.[sourcePath]?.keccak256 ?? null;
204
+ const role = opts.mode === "local" ? "local-stub" : classifyRole(kind, abstract, upstreamPath, name);
205
+ contracts.push({
206
+ key: `${opts.repo}:${name}`,
207
+ repo: opts.repo,
208
+ erc: opts.erc,
209
+ name,
210
+ upstreamPath,
211
+ kind,
212
+ abstract,
213
+ role,
214
+ deployable: bytecode !== "0x" && !abstract && kind !== "interface",
215
+ baseContracts,
216
+ interfaceId,
217
+ abi: art.abi ?? [],
218
+ methodIdentifiers: art.methodIdentifiers ?? {},
219
+ bytecode,
220
+ deployedBytecode,
221
+ linkReferences,
222
+ deployedLinkReferences,
223
+ immutableReferences,
224
+ runtimeIdentity: deployedBytecode === "0x" ? "0x" : runtimeIdentity(deployedBytecode, deployedLinkReferences, immutableReferences),
225
+ creationCodeSize: codeSize(bytecode),
226
+ runtimeCodeSize: codeSize(deployedBytecode),
227
+ compiler,
228
+ settings,
229
+ sourceKeccak,
230
+ });
231
+ }
232
+ contracts.sort((a, b) => a.key.localeCompare(b.key));
233
+ return { contracts, solcVersions };
234
+ }
235
+ /** Search a vectors JSON tree for `<InterfaceName>: 0x????????` pairs. */
236
+ function scanInterfaceIds(tree, names, out) {
237
+ if (!tree || typeof tree !== "object")
238
+ return;
239
+ for (const [k, v] of Object.entries(tree)) {
240
+ if (typeof v === "string" && names.has(k) && /^0x[0-9a-fA-F]{8}$/.test(v))
241
+ out.set(k, v.toLowerCase());
242
+ else if (v && typeof v === "object")
243
+ scanInterfaceIds(v, names, out);
244
+ }
245
+ }
246
+ export function bundleHash(contracts, sources) {
247
+ const subset = {};
248
+ for (const [k, c] of Object.entries(contracts)) {
249
+ subset[k] = {
250
+ abi: c.abi,
251
+ bytecode: c.bytecode,
252
+ deployedBytecode: c.deployedBytecode,
253
+ linkReferences: c.linkReferences,
254
+ deployedLinkReferences: c.deployedLinkReferences,
255
+ immutableReferences: c.immutableReferences,
256
+ compiler: c.compiler,
257
+ settings: c.settings,
258
+ sourceKeccak: c.sourceKeccak,
259
+ upstreamPath: c.upstreamPath,
260
+ };
261
+ }
262
+ const src = sources.map((s) => ({ repo: s.repo, commit: s.commit, mode: s.mode, solc: s.solc }));
263
+ return sha256Hex(canonicalJson({ contracts: subset, sources: src }));
264
+ }
265
+ export async function buildBundle(opts = {}) {
266
+ const root = repoRoot();
267
+ const profiles = readJson(opts.profilesPath ?? join(root, "build", "profiles.json"));
268
+ const lock = loadLock(opts.lockPath);
269
+ const vendorDir = opts.vendorDir ?? join(root, "vendor");
270
+ const workDir = opts.workDir ?? join(root, ".foga-build");
271
+ const outPath = opts.outPath ?? join(root, "artifacts", "bundle.json");
272
+ const problems = verifyVendor({ lockPath: opts.lockPath, vendorDir });
273
+ if (problems.length)
274
+ throw new Error("vendor tree does not match vendor/LOCK.json:\n " + problems.join("\n "));
275
+ const forge = forgeVersion();
276
+ log.info(`forge ${forge}`);
277
+ const contracts = {};
278
+ const sources = [];
279
+ const interfaceIds = {};
280
+ const extras = {};
281
+ const solcUsed = {};
282
+ const known = exists(join(root, "build", "known-interface-ids.json"))
283
+ ? readJson(join(root, "build", "known-interface-ids.json"))
284
+ : {};
285
+ for (const profile of profiles.repos) {
286
+ if (opts.only && !opts.only.includes(profile.repo))
287
+ continue;
288
+ const lockEntry = lock.sources.find((s) => s.repo === profile.repo);
289
+ if (!lockEntry)
290
+ throw new Error(`profile ${profile.repo} has no lock entry`);
291
+ const repoDir = join(vendorDir, profile.repo);
292
+ const outDir = join(workDir, "out", profile.repo);
293
+ const solcPath = solcBinary(profiles, profile.solc, opts.solcDir);
294
+ log.info(`building ${profile.repo} (ERC-${profile.erc}, ${profile.mode}, solc ${profile.solc}${solcPath ? ", local binary" : ""})`);
295
+ let projectDir = repoDir;
296
+ if (profile.mode === "staged") {
297
+ projectDir = join(workDir, "stage", profile.repo);
298
+ writeStagedProject(profile, repoDir, projectDir);
299
+ forgeBuild(projectDir, outDir, [], solcPath);
300
+ }
301
+ else {
302
+ forgeBuild(projectDir, outDir, profiles.foundry.skip, solcPath);
303
+ }
304
+ const ex = extractArtifacts({
305
+ outDir,
306
+ repo: profile.repo,
307
+ erc: profile.erc,
308
+ mode: profile.mode,
309
+ contractsRoot: profile.contractsRoot,
310
+ stageCopies: profile.stage?.copy,
311
+ });
312
+ for (const v of ex.solcVersions)
313
+ solcUsed[v.split("+")[0]] = v;
314
+ for (const c of ex.contracts)
315
+ contracts[c.key] = c;
316
+ log.step(`${ex.contracts.length} compilation targets kept from ${relative(root, outDir)}`);
317
+ sources.push({
318
+ erc: profile.erc,
319
+ repo: profile.repo,
320
+ url: lockEntry.url,
321
+ commit: lockEntry.commit,
322
+ contractsRoot: profile.contractsRoot,
323
+ mode: profile.mode,
324
+ upstreamToolchain: lockEntry.upstreamToolchain,
325
+ solc: profile.solc,
326
+ mirrors: profile.stage?.mirrors,
327
+ });
328
+ // Interface ids: computed from the AST, cross-checked against the upstream vectors (or README-published values).
329
+ const names = new Set(ex.contracts.filter((c) => c.kind === "interface").map((c) => c.name));
330
+ const upstream = new Map();
331
+ let source = null;
332
+ for (const v of profile.vectors) {
333
+ const p = join(repoDir, v);
334
+ if (!exists(p))
335
+ continue;
336
+ scanInterfaceIds(readJson(p), names, upstream);
337
+ source = v;
338
+ }
339
+ if (known[profile.repo]) {
340
+ for (const [n, id] of Object.entries(known[profile.repo].ids))
341
+ if (!upstream.has(n))
342
+ upstream.set(n, id.toLowerCase());
343
+ source = source ?? known[profile.repo].source;
344
+ }
345
+ const rec = {};
346
+ for (const c of ex.contracts) {
347
+ if (c.kind !== "interface" || !c.interfaceId)
348
+ continue;
349
+ const up = upstream.get(c.name) ?? null;
350
+ rec[c.name] = { computed: c.interfaceId, upstream: up, match: up ? up === c.interfaceId : null, source: up ? source : null };
351
+ if (up && up !== c.interfaceId)
352
+ log.warn(`${profile.repo}: ${c.name} id ${c.interfaceId} differs from upstream ${up}`);
353
+ }
354
+ interfaceIds[profile.repo] = rec;
355
+ for (const [k, rel] of Object.entries(profile.extras)) {
356
+ const p = join(repoDir, rel);
357
+ if (exists(p)) {
358
+ extras[profile.repo] = extras[profile.repo] ?? {};
359
+ extras[profile.repo][k] = readJson(p);
360
+ }
361
+ }
362
+ }
363
+ // FOGA's own local-only contracts.
364
+ if (!opts.only || opts.only.includes("foga")) {
365
+ const projectDir = join(root, profiles.local.project);
366
+ const outDir = join(workDir, "out", "foga");
367
+ const solcPath = solcBinary(profiles, profiles.local.solc, opts.solcDir);
368
+ log.info(`building local contracts (${profiles.local.project}, solc ${profiles.local.solc})`);
369
+ forgeBuild(projectDir, outDir, ["test", "script"], solcPath);
370
+ const ex = extractArtifacts({ outDir, repo: "foga", erc: "foga", mode: "local", contractsRoot: profiles.local.contractsRoot });
371
+ for (const v of ex.solcVersions)
372
+ solcUsed[v.split("+")[0]] = v;
373
+ for (const c of ex.contracts)
374
+ contracts[c.key] = c;
375
+ sources.push({
376
+ erc: "foga",
377
+ repo: "foga",
378
+ url: null,
379
+ commit: null,
380
+ contractsRoot: `${profiles.local.project}/${profiles.local.contractsRoot}`,
381
+ mode: "local",
382
+ upstreamToolchain: "foundry (this repository)",
383
+ solc: profiles.local.solc,
384
+ });
385
+ log.step(`${ex.contracts.length} local contracts`);
386
+ }
387
+ const ordered = {};
388
+ for (const k of Object.keys(contracts).sort())
389
+ ordered[k] = contracts[k];
390
+ const bundle = {
391
+ schema: "foga.bundle/1",
392
+ builtAt: new Date().toISOString(),
393
+ builtWith: { forge, solc: solcUsed },
394
+ sources,
395
+ contracts: ordered,
396
+ interfaceIds,
397
+ extras,
398
+ hash: bundleHash(ordered, sources),
399
+ };
400
+ if (opts.verify) {
401
+ if (!exists(outPath))
402
+ throw new Error(`nothing to verify: ${outPath} does not exist`);
403
+ const committed = readJson(outPath);
404
+ const verifyPath = join(workDir, "bundle.verify.json");
405
+ writeJson(verifyPath, bundle);
406
+ const ok = committed.hash === bundle.hash;
407
+ if (ok)
408
+ log.info(`bundle hash verified: ${bundle.hash}`);
409
+ else
410
+ log.error(`bundle hash mismatch: committed ${committed.hash}, rebuilt ${bundle.hash} (fresh build written to ${relative(root, verifyPath)})`);
411
+ return { bundle, path: verifyPath, verified: ok };
412
+ }
413
+ writeJson(outPath, bundle);
414
+ writeFileSync(outPath.replace(/\.json$/, ".sha256"), `${bundle.hash.slice(2)} ${basename(outPath)}\n`);
415
+ log.info(`wrote ${relative(root, outPath)} (${Object.keys(ordered).length} contracts, hash ${bundle.hash})`);
416
+ return { bundle, path: outPath };
417
+ }
@@ -0,0 +1,74 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { ensureDir, exists, readJson, repoRoot, rmrf } from "../util/fs.js";
4
+ import { log } from "../util/log.js";
5
+ export function loadLock(path) {
6
+ const p = path ?? join(repoRoot(), "vendor", "LOCK.json");
7
+ const lock = readJson(p);
8
+ if (lock.schema !== "foga.vendor-lock/1")
9
+ throw new Error(`unsupported lock schema in ${p}`);
10
+ return lock;
11
+ }
12
+ function git(cwd, args) {
13
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
14
+ }
15
+ export function headCommit(dir) {
16
+ try {
17
+ return git(dir, ["rev-parse", "HEAD"]);
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ /**
24
+ * Materialize every locked source under vendor/<repo>/ at exactly the locked commit.
25
+ * Existing directories already at the right commit are left untouched; anything else is replaced.
26
+ */
27
+ export function syncVendor(opts = {}) {
28
+ const lock = loadLock(opts.lockPath);
29
+ const vendorDir = opts.vendorDir ?? join(repoRoot(), "vendor");
30
+ ensureDir(vendorDir);
31
+ for (const src of lock.sources) {
32
+ if (opts.only && !opts.only.includes(src.repo))
33
+ continue;
34
+ const dir = join(vendorDir, src.repo);
35
+ const have = exists(dir) ? headCommit(dir) : null;
36
+ if (have === src.commit) {
37
+ log.step(`${src.repo} already at ${src.commit.slice(0, 7)}`);
38
+ continue;
39
+ }
40
+ if (exists(dir)) {
41
+ log.step(`${src.repo}: replacing ${have ? have.slice(0, 7) : "non-git directory"} with ${src.commit.slice(0, 7)}`);
42
+ rmrf(dir);
43
+ }
44
+ else {
45
+ log.step(`${src.repo}: fetching ${src.commit.slice(0, 7)}`);
46
+ }
47
+ ensureDir(dir);
48
+ git(dir, ["init", "-q"]);
49
+ git(dir, ["remote", "add", "origin", src.url]);
50
+ // GitHub serves fetches by commit id, so this is a one-commit shallow fetch of exactly the lock.
51
+ git(dir, ["-c", "advice.detachedHead=false", "fetch", "-q", "--depth", "1", "origin", src.commit]);
52
+ git(dir, ["-c", "advice.detachedHead=false", "checkout", "-q", "FETCH_HEAD"]);
53
+ const now = headCommit(dir);
54
+ if (now !== src.commit)
55
+ throw new Error(`${src.repo}: expected ${src.commit}, got ${now}`);
56
+ }
57
+ }
58
+ /** Verify that every vendored tree is present and at the locked commit; returns the problems found. */
59
+ export function verifyVendor(opts = {}) {
60
+ const lock = loadLock(opts.lockPath);
61
+ const vendorDir = opts.vendorDir ?? join(repoRoot(), "vendor");
62
+ const problems = [];
63
+ for (const src of lock.sources) {
64
+ const dir = join(vendorDir, src.repo);
65
+ if (!exists(dir)) {
66
+ problems.push(`${src.repo}: missing (run \`foga vendor sync\`)`);
67
+ continue;
68
+ }
69
+ const have = headCommit(dir);
70
+ if (have !== src.commit)
71
+ problems.push(`${src.repo}: at ${have ?? "unknown"}, lock says ${src.commit}`);
72
+ }
73
+ return problems;
74
+ }
@@ -0,0 +1,24 @@
1
+ import { join } from "node:path";
2
+ import { readJson, repoRoot, exists } from "../util/fs.js";
3
+ export function defaultBundlePath() {
4
+ return join(repoRoot(), "artifacts", "bundle.json");
5
+ }
6
+ export function loadBundle(path) {
7
+ const p = path ?? defaultBundlePath();
8
+ if (!exists(p))
9
+ throw new Error(`bundle not found at ${p}; run \`foga build\` (needs \`foga vendor sync\` first)`);
10
+ const bundle = readJson(p);
11
+ if (bundle.schema !== "foga.bundle/1")
12
+ throw new Error(`unsupported bundle schema ${String(bundle.schema)}`);
13
+ return bundle;
14
+ }
15
+ export function getContract(bundle, key) {
16
+ const c = bundle.contracts[key];
17
+ if (!c)
18
+ throw new Error(`bundle has no contract ${key}`);
19
+ return c;
20
+ }
21
+ /** Find a bundle contract by repo and name (libraries are resolved this way when linking). */
22
+ export function findByName(bundle, repo, name) {
23
+ return bundle.contracts[`${repo}:${name}`];
24
+ }
@@ -0,0 +1 @@
1
+ export {};