Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 116 additions & 30 deletions CONTRACT_VERIFICATION.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,142 @@
# Contract verification on block explorers
# Contract Verification on Block Explorers

This repository ships a **Soroban (Stellar)** contract (`contracts/ajo-circle`). If you also deploy an **EVM** build (e.g. Polygon), use **Etherscan** (or the network’s explorer) to verify Solidity sources.
> **Issue #165** — Push ABI definitions to block explorers to enable GUI tracking
> and community transparency on Sepolia (and Ethereum mainnet).

## Stellar / Soroban (this repo)
---

1. Build the WASM artifact:
## Quick Reference

```bash
cd contracts/ajo-circle
cargo build --target wasm32-unknown-unknown --release
```
| Command | What it does |
|---------|-------------|
| `npm run deploy:sepolia` | Deploy AjoCircle + AjoFactory to Sepolia |
| `npm run verify:sepolia` | Verify both contracts on Etherscan |
| `npm run deploy:verify:sepolia` | Deploy and verify in one step |

2. Install the [Stellar CLI](https://developers.stellar.org/docs/tools/developer-tools) and deploy or reuse your deployed contract address.
---

3. Publish **verified source** on [Stellar Expert](https://stellar.expert) (or your network’s explorer): upload the matching WASM and, when prompted, the **exact** constructor / install parameters used at deploy time.
## Prerequisites

4. Optionally run the helper script (prints build info and verification checklist):
1. **Etherscan API key** — create one at <https://etherscan.io/myapikey>
Free tier is sufficient; 5 calls/second, unlimited verifications.

```bash
./scripts/verify-stellar-contract.sh
2. **Environment variables** — copy `.env.example` → `.env` and fill in:

```env
ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY
PRIVATE_KEY=0xYOUR_DEPLOYER_PRIVATE_KEY
```

## Ethereum / Polygon — Etherscan-style verification
3. **Deploy first** — the verification script reads
`contracts/ethereum/deployed-<network>.json` to obtain the exact constructor
arguments used on-chain. Run deployment before verification.

---

## Step-by-step

For Solidity contracts (not the Soroban crate in this tree):
### 1 · Deploy

1. Create an API key at [Etherscan](https://etherscan.io/apis) (or the target chain’s explorer, e.g. Polygonscan).
```bash
# From the repo root
npm run deploy:sepolia
```

This runs `contracts/ethereum/scripts/deploy.js`, which:
- Deploys **AjoCircle** (implementation, constructor: none)
- Deploys **AjoFactory** (constructor: `address _implementation`)
- Saves addresses + constructor args to `contracts/ethereum/deployed-sepolia.json`
- Attempts inline Etherscan verification (non-blocking)

2. Use **Hardhat** with `@nomicfoundation/hardhat-verify` (successor to `hardhat-etherscan`) or **Foundry** `forge verify-contract`.
### 2 · Verify (if inline verification failed or was skipped)

3. Pass **constructor arguments** exactly as used on-chain (often ABI-encoded; Hardhat’s `verify` task can take them via `--constructor-args`).
```bash
npm run verify:sepolia
```

4. Open the contract page on the explorer and confirm the **green checkmark** / verified badge.
This runs `scripts/verify.ts`, which:
- Reads `contracts/ethereum/deployed-sepolia.json`
- Waits 30 s for Etherscan to index the contracts
- Calls `verify:verify` for **AjoCircle** (0 constructor args)
- Calls `verify:verify` for **AjoFactory** (`_implementation = <AjoCircle address>`)
- Prints direct Etherscan `#code` links on success
- Gracefully skips contracts that are already verified

### Hardhat example (EVM projects)
### 3 · Combined (deploy + verify in one shot)

```bash
npx hardhat verify --network <network> <DEPLOYED_ADDRESS> "<constructor_arg_1>" "<constructor_arg_2>"
npm run deploy:verify:sepolia
```

Or use the helper script provided in this repo (reuses deployment metadata and the exact constructor args for AjoCircle):
### 4 · Manual fallback

If the scripts fail for any reason you can verify manually:

```bash
cd contracts
npm run verify:sepolia
# or
npm run verify:mainnet
# AjoCircle — no constructor args
npx hardhat verify \
--network sepolia \
--contract "contracts/ethereum/contracts/AjoCircle.sol:AjoCircle" \
<AJOCIRCLE_ADDRESS>

# AjoFactory — takes the AjoCircle address as its single constructor arg
npx hardhat verify \
--network sepolia \
--contract "contracts/ethereum/contracts/AjoFactory.sol:AjoFactory" \
<AJOFACTORY_ADDRESS> \
<AJOCIRCLE_ADDRESS>
```

Set `ETHERSCAN_API_KEY` (or `POLYGONSCAN_API_KEY`, etc.) in your environment or `hardhat.config`.
---

## How verification works

`@nomicfoundation/hardhat-verify` (already installed) submits the **Solidity
source + compiler settings** from your local build to the Etherscan API. Etherscan
re-compiles the source and checks that the resulting bytecode matches what is
stored on-chain. On success it:

- Displays a **green ✓ checkmark** on the contract page
- Publishes the **ABI** so users can interact via the Etherscan GUI ("Read/Write
Contract" tabs)
- Enables **event log decoding** — community members see human-readable event
names instead of raw hex topics

A **Sourcify** fallback is also configured in `hardhat.config.ts` (no API key
required) for decentralised, IPFS-backed verification.

---

## Stellar / Soroban (main contract)

This repo's primary savings contract (`contracts/ajo-circle`) runs on Soroban.
Verification there is WASM-based:

1. Build the WASM artifact:

```bash
cd contracts/ajo-circle
cargo build --target wasm32-unknown-unknown --release
```

2. Upload the WASM + matching constructor parameters to
[Stellar Expert](https://stellar.expert) (testnet or mainnet).

3. Optionally run the helper:

```bash
./scripts/verify-stellar-contract.sh
```

---

## Why both sections?
## Troubleshooting

- **Stellar Ajo** uses Soroban; verification is WASM + explorer metadata.
- Issue trackers sometimes say “Etherscan” generically; the equivalent workflow for this codebase is **Stellar Expert** unless you maintain a separate EVM deployment.
| Symptom | Fix |
|---------|-----|
| `ETHERSCAN_API_KEY is not set` | Add the key to `.env` |
| `Contract does not have bytecode` | Wait ~1 min for the TX to be indexed, then retry |
| `Already Verified` | Nothing to do — the contract is already public |
| Wrong constructor args | Check `deployed-sepolia.json`; re-run deploy if stale |
| `hardhat verify` flag errors | Ensure `@nomicfoundation/hardhat-verify` is installed: `npm install` |
13 changes: 12 additions & 1 deletion contracts/ethereum/hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,19 @@ module.exports = {
chainId: 1,
},
},
// @nomicfoundation/hardhat-verify resolves the key by network name.
// Add more networks here (e.g. mainnet, polygon) as needed.
etherscan: {
apiKey: ETHERSCAN_API_KEY,
apiKey: {
sepolia: ETHERSCAN_API_KEY || "",
mainnet: ETHERSCAN_API_KEY || "",
},
customChains: [],
},

// Sourcify: decentralised fallback (no API key needed).
sourcify: {
enabled: true,
},
gasReporter: {
enabled: process.env.REPORT_GAS === "true",
Expand Down
4 changes: 2 additions & 2 deletions contracts/ethereum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"deploy:local": "hardhat run scripts/deploy.js --network localhost",
"deploy:sepolia": "hardhat run scripts/deploy.js --network sepolia",
"deploy:mainnet": "hardhat run scripts/deploy.js --network mainnet",
"verify:sepolia": "hardhat verify --network sepolia",
"verify:sepolia": "echo 'Run: npm run verify:sepolia from the repo root (uses scripts/verify.ts)' && exit 1",
"node": "hardhat node"
},
"devDependencies": {
Expand All @@ -18,4 +18,4 @@
"dotenv": "^16.3.1",
"hardhat": "^2.22.0"
}
}
}
25 changes: 20 additions & 5 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/// <reference types="node" />
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-verify";
import "hardhat-gas-reporter";
import "solidity-coverage";
import dotenv from "dotenv";

dotenv.config();
Expand All @@ -17,6 +15,12 @@ const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "";
const SEPOLIA_PRIVATE_KEY = process.env.SEPOLIA_PRIVATE_KEY || "";
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || "";

if (!ETHERSCAN_API_KEY && process.env.NODE_ENV !== "test") {
console.warn(
"⚠️ ETHERSCAN_API_KEY is not set — contract verification will fail on Sepolia."
);
}

if (!SEPOLIA_RPC_URL && process.env.NODE_ENV === "production") {
console.warn("⚠️ SEPOLIA_RPC_URL is not set in .env");
}
Expand Down Expand Up @@ -58,11 +62,22 @@ const config: HardhatUserConfig = {
},
},

// Etherscan verification
// ─── Block-explorer verification ─────────────────────────────────────────
// @nomicfoundation/hardhat-verify resolves apiKey by network name.
// Add entries here when targeting additional chains (e.g. mainnet, polygon).
etherscan: {
apiKey: {
sepolia: ETHERSCAN_API_KEY || "",
sepolia: ETHERSCAN_API_KEY,
mainnet: ETHERSCAN_API_KEY,
},
// Extend with custom chains that are not natively supported by hardhat-verify
customChains: [],
},

// Sourcify: decentralised, IPFS-backed verification (no API key required).
// Falls back to this if Etherscan verification is unavailable.
sourcify: {
enabled: true,
},

// Gas reporter
Expand Down
18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
"start": "next start",
"start:server": "node server/dist/index.js",
"lint": "eslint .",
"test": "npm run test:contracts && npm run test:unit",
"test": "npm run test:contracts",
"test:unit": "jest --runInBand",
"test:api": "jest --runInBand --selectProjects api",
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate dev",
"test": "npm run test:contracts",
"test:contracts": "cd contracts/ajo-circle && cargo test --lib",
"deploy:sepolia": "hardhat run scripts/deploy.js --network sepolia"
"deploy:sepolia": "hardhat run contracts/ethereum/scripts/deploy.js --network sepolia",
"verify:sepolia": "hardhat run scripts/verify.ts --network sepolia",
"deploy:verify:sepolia": "npm run deploy:sepolia && npm run verify:sepolia"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
Expand Down Expand Up @@ -69,6 +70,7 @@
"cors": "^2.8.5",
"date-fns": "4.1.0",
"embla-carousel-react": "8.6.0",
"ethers": "^5.7.2",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"helmet": "^8.0.0",
Expand All @@ -89,6 +91,7 @@
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.0",
"resend": "^4.8.0",
"siwe": "^2.2.0",
"sonner": "^1.7.1",
"swr": "^2.2.5",
"tailwind-merge": "^3.3.1",
Expand Down Expand Up @@ -120,6 +123,8 @@
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"concurrently": "^9.1.2",
"dotenv": "^17.3.1",
"hardhat": "^3.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^30.3.0",
"jest-environment-jsdom": "^30.3.0",
Expand All @@ -128,13 +133,10 @@
"tailwindcss": "^4.2.0",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"hardhat": "^3.2.0",
"postcss": "^8.5",
"prisma": "^5.10.0",
"tailwindcss": "^4.2.0",
"tsx": "^4.19.2",
"tw-animate-css": "1.3.3",
"typescript": "5.7.3"
},
"packageManager": "pnpm@10.30.2+sha512.36cdc707e7b7940a988c9c1ecf88d084f8514b5c3f085f53a2e244c2921d3b2545bc20dd4ebe1fc245feec463bb298aecea7a63ed1f7680b877dc6379d8d0cb4"
"packageManager": "pnpm@10.30.2+sha512.36cdc707e7b7940a988c9c1ecf88d084f8514b5c3f085f53a2e244c2921d3b2545bc20dd4ebe1fc245feec463bb298aecea7a63ed1f7680b877dc6379d8d0cb4",
"type": "module"
}
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading