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
10 changes: 5 additions & 5 deletions deploy/MAINNET-PARAMETERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ never calls the setters that would write the secure values.
| `stakeAssets[EIGEN].depositCap` | 1M | **250k** | Right-size before any future enablement (entry stays `0x0`/disabled) |
| `stakeAssets[USDe].depositCap` | 15M | **5M** | Risk right-size (disabled) |
| `stakeAssets[stETH/wstETH].depositCap` | 30k each | **15k each** | Same Lido underlying — treat as one family budget (15k each = 30k aggregate) |
| **+ `governance` block** | (absent) | votingDelay 1d / votingPeriod 7d / proposalThreshold 200k TNT / quorum 6% / timelockDelay 4d | Pin launch governance; conservative-start |
| **+ `governance` block** | (absent) | votingDelay 1d / votingPeriod 7d / proposalThreshold 100k TNT / quorum 4% / timelockDelay 4d | Pin launch governance; thin-float-aware conservative start |
| **+ `slashing` block** | (absent) | maxSlashBps 50% / disputeWindow 7d / disputeBond 0.02 ETH / resolutionDeadline 21d / maxPending 8 | Drives `setSlashConfig`; removes 100%-slash + free-dispute defaults |
| **+ `payments` block** | (absent) | 2000/1950/4000/2000/**50** (dev/protocol/operator/staker/keeper) | Drives `setPaymentSplit`; keeper market for permissionless billing |

Expand Down Expand Up @@ -124,10 +124,10 @@ signalling high dilution.

**Governance launch values (thin-float-aware).** `votingDelay 1d / votingPeriod 7d / proposalThreshold 100k
TNT / quorum 4% / timelockDelay 4d`. The non-obvious driver: quorum is `% of getPastTotalSupply` = the full
109.26M minted at genesis, but most supply is vesting-locked and undelegated at launch (Substrate+EVM unlock
only 10% at TGE; Treasury 0%, Foundation 30%), so realistic delegatable float is single-digit millions. A high
quorum would *brick* governance. 4% (~4.37M votes) is reachable with engagement; 100k proposalThreshold is a
spam floor that isn't paralyzing on thin float. *Alternatives:* quorum 3% if early participation is weak, 6%
100M minted at genesis, but most supply is vesting-locked and undelegated at launch (Substrate unlocks
10% at claim, Treasury 0%, Foundation 30%), so realistic delegatable float is single-digit millions. A high
quorum would *brick* governance. 4% (= 4,000,000 votes at the 100M cap) is reachable with engagement; 100k
proposalThreshold is a spam floor that isn't paralyzing on thin float. *Alternatives:* quorum 3% if early participation is weak, 6%
once float deepens (ratchet up via governance as vesting unlocks over 3yr). `proposalThreshold` 200k if you
expect strong delegation. The real defense against a low-quorum capture proposal is the **CANCELLER guardian
Safe below**, not a high quorum — wire it.
Expand Down
2 changes: 1 addition & 1 deletion deploy/config/base-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
},
"_note_governance_slashing_payments": "These three blocks are consumed by deploy/deploy-all.sh (the prod orchestrator) AFTER core deploy. governance -> script/DeployGovernance.s.sol (deploy Timelock+Governor, then put the Timelock address in roles.timelock). slashing -> Tangle.setSlashConfig. payments -> Tangle.setPaymentSplit. Without these steps mainnet ships the insecure hardcoded defaults (maxSlashBps=100%, disputeBond=0, keeperBps=0). See deploy/MAINNET-PARAMETERS.md for rationale.",
"governance": {
"_note": "Quorum is % of getPastTotalSupply = the full 109.26M minted at genesis, but most supply is vesting-locked/undelegated at launch (substrate+EVM unlock only 10% at TGE; treasury 0%, foundation 30%). Realistic delegatable float at launch is single-digit millions, so a high quorum BRICKS governance. quorumPercent=4 (~4.37M votes) is reachable with engagement; proposalThreshold 100k TNT is a spam floor that isn't paralyzing on thin float. Conservative-start: ratchet quorum up via governance as vesting unlocks over 3yr. STRONGLY pair with an independent CANCELLER_ROLE guardian Safe (see openDecisions) it is the real defense against a low-quorum capture proposal, not a high quorum. Alternatives: quorum 3% if early participation is weak, or 6% only once float deepens.",
"_note": "Quorum is % of getPastTotalSupply = the full 100M minted at genesis, but most supply is vesting-locked/undelegated at launch (Substrate unlocks 10% at claim; Treasury 0%; Foundation 30%). Realistic delegatable float at launch is single-digit millions, so a high quorum bricks governance. quorumPercent=4 (= 4,000,000 votes at the 100M cap) is reachable with engagement; proposalThreshold 100k TNT is a spam floor that is not paralyzing on thin float. Conservative start: ratchet quorum up via governance as vesting unlocks over 3yr. Strongly pair with an independent CANCELLER_ROLE guardian Safe (see openDecisions); it is the real defense against a low-quorum capture proposal, not a high quorum. Alternatives: quorum 3% if early participation is weak, or 6% only once float deepens.",
"votingDelay": 86400,
"votingPeriod": 604800,
"proposalThreshold": "100000000000000000000000",
Expand Down
24 changes: 17 additions & 7 deletions packages/migration-claim/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# TNT Substrate → EVM Migration (SP1)

This subpackage (`packages/migration-claim`) contains the SP1/ZK-based migration claim system for TNT, plus scripts and real snapshot outputs for testnet/mainnet dry runs.
This subpackage (`packages/migration-claim`) contains the SP1/ZK-based migration claim system for TNT, plus scripts and snapshot outputs used by deployment rehearsals.

Mainnet genesis is normalized to exactly 100,000,000 TNT in `deploy/distributions/normalized-100m.json`.
That file is the distribution source of truth: active Substrate claims, active EVM claims, the foundation
bucket, and the treasury balancer must sum to `TangleToken.MAX_SUPPLY`.

## What’s Included

Expand All @@ -10,19 +14,20 @@ This subpackage (`packages/migration-claim`) contains the SP1/ZK-based migration
- `src/lockups/TNTVestingFactory.sol` + `src/lockups/TNTLinearVesting.sol`: per-beneficiary linear vesting contracts.
- `src/TNT.sol`: simple mintable ERC20 used for local testing only (production can use the canonical TNT token from `tnt-core`).

**Real snapshot outputs (kept in-repo for reproducible testing)**
**Snapshot outputs (kept in-repo for reproducible testing)**
- `packages/migration-claim/merkle-tree.json`: Merkle root + per-SS58 proofs.
- `packages/migration-claim/evm-claims.json`: EVM recipient list + amounts.
- `packages/migration-claim/treasury-carveout.json`: Sum of non-claimable Substrate module accounts (sent to the EVM treasury at deploy).
- `packages/migration-claim/foundation-carveout.json`: Optional carveout for the foundation allocation (sent fully liquid at deploy).
- `packages/migration-claim/treasury-carveout.json`: Treasury bucket input before normalization.
- `packages/migration-claim/foundation-carveout.json`: Foundation bucket input before deployment vesting policy is applied.

If you need to carve out additional non-claimable pubkeys (beyond `modl*` module accounts), add them to `treasury-carveout.json` and re-run `scripts/carveoutTreasury.ts` (or pass `--treasury-pubkey 0x...`).
If you regenerate the snapshot, update `deploy/distributions/normalized-100m.json` and run
`python3 deploy/distributions/reconcile.py`. Treasury is the balancing bucket; the 100M cap does not move.

## Vesting Schedule (default)

Claims split into:
- `unlockedBps = 200` (2%) transferred immediately to the recipient at TGE.
- `vestedAmount` (98%) transferred to a deterministic `TNTLinearVesting` contract for the recipient.
- `unlockedBps = 1000` (10%) transferred immediately to the recipient at claim time.
- `vestedAmount` (90%) transferred to a deterministic `TNTLinearVesting` contract for the recipient.

**Default vesting parameters:**
- 12-month cliff (365 days) - no tokens vest during this period
Expand All @@ -33,6 +38,11 @@ You can update vesting config via `TangleMigration.setVestingConfig(...)` only w

Vesting contracts are created with `delegatee = recipient` so (if the token supports `IVotes`) the recipient can use voting power while tokens are locked.

`FullDeploy` handles the treasury and foundation buckets separately from user claims:

- Treasury: 0% unlocked, 6-month cliff, 30-month linear vest.
- Foundation: 30% unlocked, 6-month cliff, 30-month linear vest.

## Merkle Format

Each Merkle value is `(bytes32 pubkey, uint256 amount)` where `pubkey` is the 32-byte SR25519 public key (derived from SS58).
Expand Down
13 changes: 9 additions & 4 deletions packages/migration-claim/docs/KITE_TESTNET_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ Save the `PROGRAM_VKEY` from the output.

## Step 3: Deploy Migration Contracts

The migration includes token allocations for the foundation and liquidity operations. For testnet, use your deployer address as the recipient for both.
The wrapper reads the normalized 100M distribution from `deploy/distributions/normalized-100m.json` when
run from the repo. It uses the same four buckets as the production deploy path: Substrate claims, active EVM
claims, foundation, and treasury. For testnet, use your deployer address as the treasury and foundation
recipient.

```bash
cd packages/migration-claim
Expand All @@ -77,13 +80,15 @@ PRIVATE_KEY=0x... \
PROGRAM_VKEY=0x<from-step-2> \
SP1_VERIFIER=0x<gateway-from-step-1> \
FOUNDATION_RECIPIENT=0x<your-deployer-address> \
LIQUIDITY_OPS_RECIPIENT=0x<your-deployer-address> \
TREASURY_RECIPIENT=0x<your-deployer-address> \
./scripts/deploy-tangle-migration.sh --kite
```

**Environment variables:**
- `FOUNDATION_RECIPIENT`: EVM address to receive foundation tokens (~15M TNT)
- `LIQUIDITY_OPS_RECIPIENT`: EVM address to receive liquidity/ops tokens (5M TNT)
- `TREASURY_RECIPIENT`: EVM address to receive treasury tokens (~35.64M TNT)

The current normalized distribution has no liquidity-ops bucket and no active EVM allocation.

Save the deployed addresses:
- `TNT_TOKEN`
Expand Down Expand Up @@ -272,7 +277,7 @@ export PRIVATE_KEY=0x...
export PROGRAM_VKEY=0x...
export SP1_VERIFIER=0x...
export FOUNDATION_RECIPIENT=0x... # Receives ~15M TNT (use deployer address for testnet)
export LIQUIDITY_OPS_RECIPIENT=0x... # Receives 5M TNT (use deployer address for testnet)
export TREASURY_RECIPIENT=0x... # Receives ~35.64M TNT (use deployer address for testnet)

# Relayer
export RELAYER_PRIVATE_KEY=0x...
Expand Down
41 changes: 20 additions & 21 deletions packages/migration-claim/script/DeployTangleMigration.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
*
* Environment Variables (Required):
* MERKLE_ROOT - Merkle root from migration snapshot
* TOTAL_SUBSTRATE - Exact total for Substrate claims (from distribution.json)
* TOTAL_EVM - Exact total for EVM airdrop (from distribution.json)
* TOTAL_SUBSTRATE - Exact total for Substrate claims (from normalized-100m.json)
* TOTAL_EVM - Exact total for active EVM claims (currently 0 in normalized-100m.json)
*
* Environment Variables (Optional):
* PRIVATE_KEY - Deployer private key (default: Anvil account 0)
Expand All @@ -29,18 +29,18 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
* TREASURY_RECIPIENT - Address to receive treasury allocation (vested)
* TREASURY_AMOUNT - Treasury allocation in wei (0% unlocked, 100% vested)
* FOUNDATION_RECIPIENT - Address to receive foundation allocation
* FOUNDATION_AMOUNT - Foundation allocation in wei (2% of total supply unlocked, rest vested)
* LIQUIDITY_OPS_RECIPIENT - Address to receive liquidity ops allocation
* LIQUIDITY_OPS_AMOUNT - Liquidity ops allocation in wei (100% liquid)
* FOUNDATION_AMOUNT - Foundation allocation in wei (30% unlocked, rest vested)
* LIQUIDITY_OPS_RECIPIENT - Legacy test-only liquidity ops recipient
* LIQUIDITY_OPS_AMOUNT - Legacy test-only liquidity ops amount in wei
*
* Usage:
* # Use the deploy-migration.sh wrapper script which reads from distribution.json:
* # Use the deploy-migration.sh wrapper script which reads normalized-100m.json:
* ./scripts/local-env/deploy-migration.sh
*
* # Or manually with exact values:
* MERKLE_ROOT=0x824b... \
* TOTAL_SUBSTRATE=108138164691043996671207028 \
* TOTAL_EVM=1125776519168932493729792 \
* TOTAL_SUBSTRATE=49322182835914246230618074 \
* TOTAL_EVM=0 \
* USE_MOCK_VERIFIER=true \
* forge script script/DeployTangleMigration.s.sol:DeployTangleMigration \
* --rpc-url http://localhost:8545 --broadcast
Expand Down Expand Up @@ -183,13 +183,13 @@ contract DeployTangleMigration is Script {
console.log(" Remaining in deployer:", evmAllocation / 1e18, "TNT");
console.log(" Run ExecuteEVMAirdrop to distribute to", "EVM holders");

// 8. Optional: Treasury allocation (0% unlocked, 100% vested with 12-month cliff + 24-month linear)
// 8. Optional: Treasury allocation (0% unlocked, 100% vested with 6-month cliff + 30-month linear)
if (treasuryAmount > 0) {
require(treasuryRecipient != address(0), "TREASURY_RECIPIENT required when TREASURY_AMOUNT > 0");

// Treasury: 0% unlocked, 100% goes to vesting contract
// 12-month cliff + 24-month linear = 3 years total
TNTVestingFactory treasuryVestingFactory = new TNTVestingFactory(365 days, 730 days);
// 6-month cliff + 30-month linear = 3 years total
TNTVestingFactory treasuryVestingFactory = new TNTVestingFactory(180 days, 912 days);
// forge-lint: disable-next-line(unsafe-typecast)
address treasuryVesting = treasuryVestingFactory.getOrCreateVesting(
configuredTnt,
Expand All @@ -203,23 +203,22 @@ contract DeployTangleMigration is Script {
console.log(" Total:", treasuryAmount / 1e18, "TNT");
console.log(" Vesting contract:", treasuryVesting);
console.log(" Beneficiary:", treasuryRecipient);
console.log(" Cliff: 12 months, Linear: 24 months (3 years total)");
console.log(" Cliff: 6 months, Linear: 30 months (3 years total)");
}

// 9. Optional: Foundation allocation (2% of total supply unlocked, rest vested with 12-month cliff + 24-month linear)
// 9. Optional: Foundation allocation (30% unlocked, rest vested with 6-month cliff + 30-month linear)
if (foundationAmount > 0) {
require(foundationRecipient != address(0), "FOUNDATION_RECIPIENT required when FOUNDATION_AMOUNT > 0");

// Foundation: 2% of TOTAL SUPPLY unlocked immediately, rest vested
uint256 foundationUnlocked = (totalSupply * 200) / 10_000; // 2% of total supply
require(foundationUnlocked <= foundationAmount, "Foundation allocation too small for 2% total supply unlock");
// Foundation: 30% of its allocation unlocked immediately, rest vested.
uint256 foundationUnlocked = (foundationAmount * 3000) / 10_000;
uint256 foundationVested = foundationAmount - foundationUnlocked;

// Transfer unlocked portion directly
require(tntToken.transfer(foundationRecipient, foundationUnlocked), "Foundation unlocked transfer failed");

// Create vesting for the rest (12-month cliff + 24-month linear = 3 years)
TNTVestingFactory foundationVestingFactory = new TNTVestingFactory(365 days, 730 days);
// Create vesting for the rest (6-month cliff + 30-month linear = 3 years)
TNTVestingFactory foundationVestingFactory = new TNTVestingFactory(180 days, 912 days);
// forge-lint: disable-next-line(unsafe-typecast)
address foundationVesting = foundationVestingFactory.getOrCreateVesting(
configuredTnt,
Expand All @@ -229,12 +228,12 @@ contract DeployTangleMigration is Script {
);
require(tntToken.transfer(foundationVesting, foundationVested), "Foundation vesting transfer failed");

console.log("\n6. Foundation Allocation (2% of total supply unlocked):");
console.log("\n6. Foundation Allocation (30% unlocked):");
console.log(" Total:", foundationAmount / 1e18, "TNT");
console.log(" Unlocked (2% of total supply):", foundationUnlocked / 1e18, "TNT to", foundationRecipient);
console.log(" Unlocked:", foundationUnlocked / 1e18, "TNT to", foundationRecipient);
console.log(" Vested:", foundationVested / 1e18, "TNT");
console.log(" Vesting contract:", foundationVesting);
console.log(" Cliff: 12 months, Linear: 24 months (3 years total)");
console.log(" Cliff: 6 months, Linear: 30 months (3 years total)");
}

// 10. Optional: Liquidity Ops allocation (100% liquid for liquidity/operational expenses)
Expand Down
Loading
Loading