Skip to content

Commit 9e8e049

Browse files
committed
test(docs): cover th verify flow and align status docs
1 parent f9c8e88 commit 9e8e049

3 files changed

Lines changed: 134 additions & 5 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ Spec-aligned (new) pipeline:
99
- Input: THS (Token Host Schema) JSON (validated + linted), e.g. `apps/example/job-board.schema.json`.
1010
- Schema/validation: `packages/schema` (JSON Schema + Ajv validation, semantic lints, RFC8785+sha256 hashing, legacy importer).
1111
- Contracts generator: `packages/generator` (single-contract, mapping-based CRUD `App.sol`, Solidity 0.8.24).
12-
- CLI: `packages/cli` (`th init|studio|validate|import-legacy|generate|build|deploy|verify(stub)|doctor`).
12+
- CLI: `packages/cli` (`th init|studio|validate|import-legacy|generate|build|deploy|verify|doctor`).
1313
- Build output: `th build <schema> --out <dir>` writes `contracts/App.sol`, `compiled/App.json`, `schema.json`, `manifest.json`.
14-
- Deploy: `th deploy <buildDir> --chain anvil|sepolia` (anvil deploy works; sepolia verify still TBD).
14+
- Deploy: `th deploy <buildDir> --chain anvil|sepolia` (anvil + sepolia supported).
1515

1616
Legacy (deprecated path; no longer source-of-truth in this repo):
1717
- Input: legacy `contracts.json` shape (`contracts{}` with `fields`, `initRules`, `readRules`, `writeRules`).
@@ -137,7 +137,7 @@ Phase 4 progress (done/partial):
137137
Remaining:
138138
- Package artifacts (`sources.tgz`, `compiled.tgz`, UI bundle) and record/serve URLs.
139139
- Chain config artifact integration + digest recording.
140-
- Real `th verify` (Etherscan/Sourcify) and end-to-end Sepolia verified deploy.
140+
- End-to-end Sepolia verified deploy playbook/documentation.
141141

142142
## Phase 5: CLI (SPEC 12)
143143

@@ -148,11 +148,11 @@ Goal: replace ad-hoc scripts with a coherent CLI that runs locally and in CI.
148148
- Add `th migrate` and stubs for chain migration/indexer hooks as needed.
149149

150150
Phase 5 progress (done/partial):
151-
- Implemented: `th init`, `th studio` (local schema builder), `th validate`, `th import-legacy`, `th generate` (contracts + UI), `th build`, `th deploy`, `th verify` (stub), `th doctor`, `th up|run|dev`.
151+
- Implemented: `th init`, `th studio` (local schema builder), `th validate`, `th import-legacy`, `th generate` (contracts + UI), `th build`, `th deploy`, `th verify` (Sourcify + Etherscan), `th doctor`, `th up|run|dev`.
152152
- `th generate --with-tests` emits generated app test scaffold and generated app CI workflow.
153153

154154
Remaining:
155-
- `th publish` (if/when in scope), real `th verify`.
155+
- `th publish` (if/when in scope).
156156
- `th migrate` implementation + `migrate-chain` implementation (currently stubs).
157157

158158
## Phase 6: Generated-app test rollout (default behavior)

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,15 @@ This repo requires:
3434
- `integration-local`
3535

3636
`integration-local` must continue to include canonical generated-app verification before default-on is enabled for emitted tests.
37+
38+
## Contract verification
39+
40+
Use `th verify <buildDir>` after deployment to verify on Sourcify and/or Etherscan.
41+
42+
- Supports `--verifier sourcify|etherscan|both`.
43+
- Uses chain-scoped API keys:
44+
- `SEPOLIA_ETHERSCAN_API_KEY` (preferred)
45+
- `ETHERSCAN_API_KEY` (fallback)
46+
- Writes verification status back into `manifest.json` under `deployments[*].verified`
47+
and `manifest.extensions.verification[chainId]`.
48+
- Re-publishes manifest into `ui-site/` when present.

test/testCliVerify.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { expect } from 'chai';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { spawnSync } from 'child_process';
6+
7+
function writeJson(filePath, value) {
8+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
9+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
10+
}
11+
12+
function runTh(args, cwd, env = {}) {
13+
return spawnSync('node', [path.resolve('packages/cli/dist/index.js'), ...args], {
14+
cwd,
15+
encoding: 'utf-8',
16+
env: { ...process.env, ...env }
17+
});
18+
}
19+
20+
function writeVerifyFixtureBuild(outDir) {
21+
const manifest = {
22+
manifestVersion: '0.1.0',
23+
app: { slug: 'verify-test', title: 'Verify Test' },
24+
schema: { version: '0.0.1', thsVersion: '2025-12', schemaHash: 'sha256:test' },
25+
build: {
26+
generatedAt: new Date().toISOString(),
27+
toolchain: { node: '20.x', solc: '0.8.24' }
28+
},
29+
deployments: [
30+
{
31+
role: 'primary',
32+
chainId: 11155111,
33+
deploymentEntrypointAddress: '0x1111111111111111111111111111111111111111',
34+
blockNumber: 1,
35+
txHash: '0x' + '1'.repeat(64),
36+
adminAddress: '0x2222222222222222222222222222222222222222',
37+
treasuryAddress: '0x3333333333333333333333333333333333333333',
38+
contracts: [
39+
{
40+
name: 'App',
41+
address: '0x1111111111111111111111111111111111111111',
42+
verified: false
43+
}
44+
],
45+
verified: false
46+
}
47+
],
48+
artifacts: {
49+
soliditySources: { digest: 'sha256:test', url: 'file:///tmp/sources.tgz' },
50+
compiledContracts: { digest: 'sha256:test', url: 'file:///tmp/compiled.tgz' }
51+
},
52+
signatures: [{ alg: 'none', sig: 'UNSIGNED' }]
53+
};
54+
55+
writeJson(path.join(outDir, 'manifest.json'), manifest);
56+
writeJson(path.join(outDir, 'compiled', 'App.json'), {
57+
abi: [],
58+
evm: {
59+
bytecode: { object: '6080604052348015600f57600080fd5b50600080fdfea2646970667358221220' },
60+
deployedBytecode: { object: '6080604052600080fdfea2646970667358221220' }
61+
}
62+
});
63+
fs.mkdirSync(path.join(outDir, 'contracts'), { recursive: true });
64+
fs.writeFileSync(
65+
path.join(outDir, 'contracts', 'App.sol'),
66+
[
67+
'// SPDX-License-Identifier: MIT',
68+
'pragma solidity ^0.8.24;',
69+
'',
70+
'contract App {',
71+
' function ping() external pure returns (uint256) { return 1; }',
72+
'}',
73+
''
74+
].join('\n')
75+
);
76+
}
77+
78+
describe('th verify', function () {
79+
it('fails when Etherscan key is missing', function () {
80+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'th-verify-no-key-'));
81+
writeVerifyFixtureBuild(dir);
82+
83+
const res = runTh(['verify', dir, '--chain', 'sepolia', '--verifier', 'etherscan', '--dry-run'], process.cwd(), {
84+
ETHERSCAN_API_KEY: ''
85+
});
86+
87+
expect(res.status).to.not.equal(0);
88+
expect(res.stderr).to.include('Missing Etherscan API key');
89+
});
90+
91+
it('redacts Etherscan key in dry-run output', function () {
92+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'th-verify-redact-'));
93+
writeVerifyFixtureBuild(dir);
94+
const secret = 'SUPER_SECRET_ETHERSCAN_KEY';
95+
96+
const res = runTh(
97+
['verify', dir, '--chain', 'sepolia', '--verifier', 'etherscan', '--dry-run', '--etherscan-api-key', secret],
98+
process.cwd()
99+
);
100+
101+
expect(res.status, res.stderr || res.stdout).to.equal(0);
102+
expect(res.stdout).to.include('forge verify-contract');
103+
expect(res.stdout).to.include('--etherscan-api-key <redacted>');
104+
expect(res.stdout).to.not.include(secret);
105+
});
106+
107+
it('supports sourcify dry-run without forge installed', function () {
108+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'th-verify-sourcify-'));
109+
writeVerifyFixtureBuild(dir);
110+
111+
const res = runTh(['verify', dir, '--chain', 'sepolia', '--verifier', 'sourcify', '--dry-run'], process.cwd());
112+
113+
expect(res.status, res.stderr || res.stdout).to.equal(0);
114+
expect(res.stdout).to.include('--verifier sourcify');
115+
expect(res.stdout).to.include('Dry run complete');
116+
});
117+
});

0 commit comments

Comments
 (0)