Copra Protocol is an autonomous cryptocurrency pegged to the real-world international benchmark value of copra—the dried coconut meat that serves as the global price anchor for the multi-billion dollar coconut economy.
Unlike speculative meme tokens or gold-backed stablecoins, Copra Protocol bridges decentralized finance with a renewable, perennial biological asset:
- Perennial Biological Engine: Coconut palms produce continuous yields every 45 to 60 days for over 60 years.
- On-Chain Collateral, Full at Mint: Every $COPRA is minted against its full value in USDC at the current price, locked in the protocol vault. The collateral pool is fixed while the price floats, so coverage moves with the price. See Backing & Price Moves.
- Autonomous Peg Stability: Anyone can mint $COPRA by depositing collateral at the current commodity benchmark price, or burn $COPRA to reclaim its value at the current price directly from the vault, while the vault holds enough collateral.
- Immutable, Non-Custodial Contracts: No proxies, no upgrade path, no pause function, and no function that can withdraw user collateral. Three privileged roles do exist, and the oracle updater is the one you trust most. See Trust Assumptions.
The protocol codebase is located in src/:
- CopraToken.sol: Standard ERC-20 token ($COPRA) with minting and burning permissions restricted exclusively to the autonomous vault.
- CopraVault.sol: The collateral reserve and Peg Stability Module (PSM). Handles permissionless minting, burning, direct redemptions, and autonomous fee capture.
- CopraOracle.sol: Price oracle for the international copra benchmark (USD, 8 decimals). A single authorized updater posts prices, limited to a 20% change per update and one update per day (
MIN_UPDATE_INTERVAL). - CopraRegistry.sol: On-chain pointer to the live contract set, read by copra.dev, the oracle feeder and the release manifest.
- TestnetUSDC.sol: Public Base Sepolia testnet collateral token with a permissionless
faucet()for frictionless testing.
Further technical documentation:
- Whitepaper
- Architecture & Security Model
- Draft specs: GCPI-001, the Global Copra Price Index, and COPRA-001, the reserve and redemption protocol that would replace the v1 vault. Comment on them in Discussions
- GCPI Phase 1 source research report and machine-readable source registry; Phase 2 methodology backtest with its dataset; source authentication research (#89); the v1 decisions and the methodology governance process are in GCPI-001 §13 and §21 (#90); Phase 3 reference engine, in Python and JavaScript, with published test vectors (#91); Phase 5 contracts, not yet deployed, which reproduce every vector on-chain, and reference collector and notary software (#92)
- Contributing Guidelines
- Security Policy
Copra is not ownerless. Three privileged roles exist. On Base Sepolia, all three are held by the deployer, 0x91d3019b38185b517Bb98B7BB4953c137F5EEb8E. On Base mainnet they are separate: a multisig owns the registry and the vault, and the oracle updater and fee recipient are other addresses (docs/MAINNET_DEPLOY.md):
| Role | Can | Limits |
|---|---|---|
Oracle updaterCopraOracle.feedUpdater (immutable) |
Post the copra price that every mint and redeem uses | At most 20% change per update and one update per day (MIN_UPDATE_INTERVAL = 1 days), so at most +20% in a day and about ×3.6 in a week. That slows the updater down but does not bound it: given time, a run of updates can still reach any price. Oracles deployed before the interval existed (no MIN_UPDATE_INTERVAL() getter) have no such limit, so check the oracle the registry points at. |
Vault ownerCopraVault, Ownable2Step |
Set the protocol fee and the fee recipient; transfer or renounce ownership | Fee capped at 1.00% (MAX_FEE_BPS = 100). setCopraToken is one-shot and already used |
Registry ownerCopraRegistry, Ownable2Step |
Choose which contract set copra.dev, the oracle feeder and the release manifest use | The set must be wired to itself (vault ↔ token ↔ oracle ↔ collateral). It cannot change contracts already deployed |
The oracle updater is the main trust assumption. No function lets any role withdraw user collateral, and claimFees() can only send accumulatedFees to feeRecipient. But redemptions pay out at the oracle price. An updater that is compromised or malicious can raise the price, redeem its own $COPRA and drain the vault, leaving other holders unable to redeem. The update interval means this takes days of public price updates rather than one block, but it does not prevent it. If the updater stops posting, the price goes stale after 7 days, and mint and redeem revert until it resumes. On testnet the updater is the oracle feeder, which posts no default price: it posts a price from a declared source (none is declared yet, see #87) or a manual override entered through the workflow's target_price input, records where each price came from in the run log, and otherwise fails without posting. Redemptions are always paid from the USDC the vault actually holds: if the price rises after deposits, the vault can be worth less than all $COPRA in circulation, and redeem reverts once the vault runs short.
Circle is a fourth party. On mainnet the vault holds Circle's USDC, which Circle can pause and whose blacklist Circle controls. Copra has no pause and no blacklist of its own, but anything that stops USDC moving also stops the vault from paying out:
| Circle action | What reverts | What it means for holders |
|---|---|---|
| USDC paused | Every mint, redeem and claimFees |
Nobody can enter or exit until Circle unpauses. $COPRA still transfers. Nothing is lost: the reserves and recorded fees stay exactly where they were, and everything works again once USDC is unpaused. |
| Vault blacklisted | Every mint, redeem and claimFees |
All collateral is frozen for every holder for as long as the vault stays blacklisted. No role can move it elsewhere, because no function withdraws collateral. Unlisting the vault restores everything. |
| A holder blacklisted | That holder's own mint and redeem |
Other holders are unaffected. $COPRA is not USDC, so the blacklist does not reach it: the holder can still transfer their $COPRA to any address that is not blacklisted, and that address can redeem it. |
| Fee recipient blacklisted | claimFees |
The fees stay recorded in accumulatedFees and backed in the vault. Holders are unaffected. The vault owner can call setFeeRecipient, and the next claim pays the new recipient. |
These behaviors are pinned by tests (test_TrustAssumption_*, test_Vault_MintAndRedeemRevertOnStalePrice and test_Vault_RedeemCannotLeaveFeesUnbacked in test/CopraProtocol.t.sol). The USDC ones are pinned against Circle's real contract on a Base-mainnet fork (e2e/scenarios/tests/usdc.test.cjs, make e2e-fork). A change to any of them also has to change this section.
A user deposits collateral (e.g., USDC) to mint $COPRA:
Collateral Required = (COPRA_Amount × Copra_Price_USD) / 1e8 + Protocol_Fee (0.25%)
A holder can burn $COPRA to claim collateral at the current benchmark price, as long as the vault holds enough:
Collateral Returned = (COPRA_Amount × Copra_Price_USD) / 1e8 - Protocol_Fee (0.25%)
Collateral is a fixed pool of USDC. After setting aside accumulatedFees, it has to cover totalSupply × price, which moves with the oracle:
- At mint: each mint deposits the full value of the new $COPRA at that moment's price (rounded up), plus the fee.
- Price falls: the vault holds more USDC than all $COPRA is worth. The surplus stays in the vault.
- Price rises: the vault holds less USDC than all $COPRA is worth.
redeempays first-come-first-served at the new price and reverts withInsufficientVaultCollateralonce the backing runs out, so the last holders to redeem cannot exit until the price falls or new mints add collateral.
There is no over-collateralization buffer and no redemption haircut today. Redemption is a price floor only while the vault is fully covered at the current price. test_Vault_RedeemCannotLeaveFeesUnbacked shows a redeem reverting after a 0.14% price rise.
- If Market Price < Benchmark Price: Arbitrageurs buy cheap $COPRA on decentralized exchanges and burn it in the vault for its benchmark value, pushing the market price back up to peg.
- If Market Price > Benchmark Price: Arbitrageurs deposit collateral to mint new $COPRA at the benchmark price and sell it on DEXs for profit, driving the price back down to peg.
The creator does not need administrative control over user reserves to earn revenue. The smart contracts autonomously capture a 0.25% protocol fee (configurable up to an immutable 1.0% hard cap) on all mints and redemptions:
- Fees accumulate directly in reserve collateral (USDC) inside
CopraVault.sol. - The designated
feeRecipientwallet receives accumulated earnings whenclaimFees()is triggered. feeRecipientis updateable by the contract owner via OpenZeppelin'sOwnable2Step, enabling progressive decentralization (e.g., migrating from a creator wallet to a community staking pool or DAO treasury) with zero risk of typos or lockout.- As global trading and minting volume expands, the fee recipient earns perpetual, passive, dollar-denominated cash flow.
Built with Foundry. All tests pass with zero warnings.
forge test -vRan 10 tests for test/CopraProtocol.t.sol:CopraProtocolTest
[PASS] test_ClaimProtocolFees()
[PASS] test_InitialState()
[PASS] test_MintCopra()
[PASS] test_OraclePriceUpdate()
[PASS] test_OracleRevertOnUnauthorized()
[PASS] test_Ownable2StepTransfer()
[PASS] test_RedeemCopra()
[PASS] test_SetFeeRecipient()
[PASS] test_SetProtocolFee()
[PASS] test_TestnetUSDC_Faucet()
Suite result: ok. 10 passed; 0 failed; 0 skipped
The live contract set is published on-chain by CopraRegistry at a permanent address. The web app, the oracle feeder and release notes all resolve addresses from it, so redeploys never require editing them. A snapshot is kept in deployments/base-sepolia.json (Chain ID 84532), and each GitHub release ships a manifest read from the registry.
| Contract | Address | Explorer |
|---|---|---|
| CopraRegistry (permanent) | 0x379cB5FED578D6F485ab1834C81E4F1CF4EABb25 |
Basescan |
CopraToken ($COPRA) |
0x53f5Ef67439E8565559f9C450cFF5CB49b4b996C |
Basescan |
| CopraVault (PSM) | 0xbB1CDe2B3c2f8F1D6AAFc1718a92192cb3335f54 |
Basescan |
| CopraOracle | 0x4F409293636dCf579f12e7627137e62A8a790b9A |
Basescan |
| TestnetUSDC | 0xd6bC2f148b750e47936f8720aF84DDFc061404DF |
Basescan |
- Obtain free Base Sepolia testnet ETH from faucets.chain.link or base.org/faucets.
- Set your environment variables:
export PRIVATE_KEY="your_wallet_private_key" export BASE_SEPOLIA_RPC="https://base-sepolia-rpc.publicnode.com"
- Run the deployment script:
forge script script/DeployCopra.s.sol:DeployCopra --rpc-url $BASE_SEPOLIA_RPC --broadcast
DeployCopra is testnet only and refuses Base mainnet. Mainnet deploys use DeployCopraMainnet, which takes real USDC and four separate roles: see docs/MAINNET_DEPLOY.md.
A clean, responsive dApp is included in web/index.html and deployed via Cloudflare Workers static assets (wrangler.jsonc).
Network per environment. The chain, RPC, explorer (and the name its links use), registry and collateral come from networks/ (sepolia, local, mainnet). collateral is TestnetUSDC or USDC (the only choice on Base mainnet): it names the collateral on the page, and only TestnetUSDC gets the faucet, whatever the hostname. The page renders its network copy from them at load, never from index.html's Base Sepolia text. web/ as committed is the Base Sepolia build: web/assets/network.js is generated from networks/sepolia.json. To build another environment:
node scripts/web-env.cjs local dist/web # a copy of web/ for that networkmainnet has no registry yet (null until the mainnet deploy in docs/MAINNET_DEPLOY.md registers one), so the generator refuses to build it.
Only assets/network.js and the CSP's connect-src (that network's RPC) differ from web/, and test/web/network.test.cjs enforces it. After editing networks/sepolia.json, regenerate web/assets/network.js with node scripts/web-env.cjs sepolia <tmp> and copy it back; the test fails until you do.
| Context | Canonical Format | Example |
|---|---|---|
| Domain & Website Links | copra.dev (lowercase) |
https://copra.dev |
| Navigation & Wordmark | Copra.dev (Title Case + accent) |
Copra.dev |
| Protocol / Project Name | Copra Protocol or Copra |
"Copra Protocol is an autonomous currency..." |
| Token Ticker Symbol | $COPRA (All-caps) |
"1 $COPRA tracks 1 kg of Copra" |
| For | Address |
|---|---|
| General questions, partnerships, press | hello@copra.dev |
| Help using the dApp or testnet | support@copra.dev |
| Privacy and data requests | privacy@copra.dev |
| Security vulnerabilities | Never in a public issue. Use GitHub private vulnerability reporting (preferred) or security@copra.dev; see SECURITY.md. |
Bugs and feature requests: GitHub issues. Comments on the GCPI methodology, proposed price sources and the COPRA-001 reserve design: GitHub Discussions.
