Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4becee0
docs(fundraising): add group fundraising escrow design spec
Douglasacost Aug 24, 2026
31e54dd
docs(fundraising): correct prior-art claims and remove drafting narra…
Douglasacost Aug 24, 2026
dce5a96
docs(fundraising): state the no-deployment-change constraint
Douglasacost Aug 24, 2026
622e166
docs(fundraising): factory-per-fundraise, creation schema, permission…
Douglasacost Aug 27, 2026
dad1a43
docs(fundraising): correct the deployment mechanism, add implementati…
Douglasacost Aug 27, 2026
19717ac
docs(fundraising): cite zkSync's own sources for the EIP-1167 limitation
Douglasacost Aug 27, 2026
bb53bd3
docs(fundraising): deploy a full contract per objective, not a proxy
Douglasacost Aug 27, 2026
ea84ee7
feat(fundraising): add fundraise interfaces and shared types
Douglasacost Aug 27, 2026
5e0a990
feat(fundraising): add the Fundraiser escrow
Douglasacost Aug 27, 2026
5959e1c
feat(fundraising): add FundraiserFactory
Douglasacost Aug 27, 2026
31b0308
test(fundraising): full suite — 82 tests across every user journey
Douglasacost Aug 27, 2026
f9891b8
feat(fundraising): add deploy script and README section
Douglasacost Aug 27, 2026
dca38b1
docs(fundraising): record how to verify on the zkSync explorer
Douglasacost Aug 27, 2026
6bbab35
refactor(fundraising): scope the contracts to a fundraise, nothing be…
Douglasacost Aug 28, 2026
eed18ef
docs(fundraising): record testnet deployments and supersede the earli…
Douglasacost Aug 28, 2026
7a62599
ops(fundraising): add the deployment script
Douglasacost Aug 28, 2026
a57265b
ops(fundraising): verify the mainnet factory, register it for verific…
Douglasacost Aug 28, 2026
6dc1428
docs(fundraising): sketch how a service should consume the contracts
Douglasacost Aug 28, 2026
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
14 changes: 14 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@
"src/swarms/doc/iso3166-2"
],
"ignoreWords": [
"fundraise",
"Finalizable",
"tstore",
"fundraises",
"Fundraiser",
"unpledge",
"unpledges",
"blocklist",
"blocklisted",
"blocklisting",
"stablecoin",
"stablecoins",
"Juicebox",
"Allo",
"AMPL",
"NODL",
"Nodle",
Expand Down
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,68 @@ npx hardhat deploy-zksync --script deploy_staking.dp.ts --network zkSyncSepoliaT

The admin account (GOV_ADDR) holds the default-admin, rewards-manager, and emergency-manager roles. It can pause the contract (which blocks `claim`/`unstake`), toggle `unstakeAllowed` (which defaults to false, so before the period ends users can only exit once the admin enables it), and — while paused — call `emergencyWithdraw` to sweep the entire contract balance, including staked principal. Deployments intended for untrusted users should split these roles and/or place them behind a timelock or multisig.

### Deploying the fundraising contracts

Deploys `FundraiserFactory`, which creates one `Fundraiser` contract per fundraise. There is no implementation contract and no proxy to deploy — the factory creates each fundraise with `new`, and zksolc registers that bytecode as a factory dependency at compile time.

Please define the following environment variables:

- `N_FUNDRAISING_ADMIN`: multisig that will hold `DEFAULT_ADMIN_ROLE`.
- `N_FUNDRAISING_TOKENS`: comma-separated ERC-20 addresses allowed at launch, e.g. USDC and NODL for the network.
- `N_FUNDRAISING_FEE_BPS`: optional, defaults to `0`. Capped by `MAX_FEE_BPS` (500).
- `N_FUNDRAISING_FEE_RECIPIENT`: optional, required only when the rate is non-zero.

The allow-list is seeded in the constructor because the admin is expected to be a multisig the deploy script cannot act for. A fee rate set with no recipient is rejected rather than silently collecting nothing.

```shell
export DEPLOYER_PRIVATE_KEY=0x...
export N_FUNDRAISING_ADMIN=0x...
export N_FUNDRAISING_TOKENS=0xUSDC...,0xNODL...

forge script script/DeployFundraiserFactory.s.sol \
--rpc-url https://sepolia.era.zksync.dev --broadcast --zksync
```

Only the factory needs verifying; each fundraise is a full contract created from bytecode already published by the factory.

Verification uses the ZKsync explorer's own verifier rather than the manual Etherscan flow described further down:

```shell
export ARGS=$(cast abi-encode "constructor(address,uint16,address,address[])" \
$N_FUNDRAISING_ADMIN 0 0x0000000000000000000000000000000000000000 "[$NODL]")

forge verify-contract <FACTORY_ADDRESS> src/fundraising/FundraiserFactory.sol:FundraiserFactory \
--zksync --verifier zksync --verifier-url $L2_VERIFIER_URL \
--constructor-args $ARGS --watch
```

Individual fundraises can be verified the same way against `src/fundraising/Fundraiser.sol:Fundraiser`, passing the constructor tuple. Their parameters are all readable from the deployed contract, so they can be reconstructed after the fact:

```shell
cast abi-encode "constructor((string,address,uint128,uint40,uint8,address,uint128,uint128),address,uint16,address)" \
"(\"<name>\",<token>,<goal>,<deadline>,<onMissed>,<beneficiary>,<min>,<max>)" \
<organizer> <feeBps> <factory>
```

> [!NOTE]
> Run deployments through [`ops/deploy_fundraising_zksync.sh`](ops/deploy_fundraising_zksync.sh) rather than calling `forge script` directly. `forge build --zksync` compiles the whole tree, and zksolc rejects an L1-only contract elsewhere in `src/` that uses `EXTCODECOPY`; the ops script temporarily moves those files aside and restores them on exit, the same pattern the swarms and collections deploy scripts use. It also gates on `factoryDependencies` being populated — empty means `createFundraiser` would revert on EraVM while passing every EVM-profile test — and verifies source through `ops/verify_zksync_contracts.py`, which rewrites imports to project-rooted paths that the ZKsync verifier will accept.


Fees ship switched off. The capability exists — the rate is snapshotted into each fundraise at creation, so raising it later cannot reach anything already in flight — but turning it on is a product decision:

```shell
export ETH_RPC_URL=https://sepolia.era.zksync.dev
export FACTORY=0x... # from the deploy output

# 250 = 2.5%
cast send -i $FACTORY "setFeeParams(uint16,address)" 250 0xFeeRecipient...

# allowing another token for future fundraises
cast send -i $FACTORY "setTokenAllowed(address,bool)" 0xToken... true
```

De-listing a token only stops new fundraises choosing it. Deposits, withdrawals and refunds on live fundraises are never affected, so de-listing cannot become a freeze switch.

## Scripts

### Checking on bridging proposals
Expand Down
Loading
Loading