A 15,000,000-max-supply ERC-20 that wraps a weighted basket of XMR 35% ZEC 35% DASH 30% held in custodied reserves, with NAV-anchored minting, a FIFO redemption queue, bounded rebalancing, 2-of-3 attestation, a sanctions gate, and a 48h timelock. Price is derived from three Chainlink USD feeds, not from a DEX pool.
mint(). 18 decimals, 1.5e25 wei.
| Pillar | Mechanism | Failure mode if broken |
|---|---|---|
| Custody | Off-chain vaults holding XMR/ZEC/DASH | Reserves ≠ reported → NAV overstates → mint is theft |
| Attestation | 2-of-3 EIP-712 signatures (custodian + auditor) | Collusion of 2 keys → fake reserves accepted |
| Governance | 48h timelock, 4-of-5 multisig | Compromised multisig → arbitrary upgrade |
| Compliance | Sanctions oracle + optional allowlist | Stale oracle → mint halts; sanctioned transfer → frozen redemption |
Drag to orbit. Scroll to zoom. Watch how coins enter the custodian vaults, get attested, update the on-chain NAV, and propagate to $PWRP price.
Adjust reserves, prices, and NAV inputs. The simulator mirrors the on-chain math:
NAV = Σ(reserve_i × price_i) / totalSupply, and mint/burn follow NAV.
$PWRP has no DEX pool as its source of truth. Its price is the on-chain NAV, and NAV is computed from three Chainlink USD feeds multiplied by reported reserves.
Chainlink XMR/USD ─┐
Chainlink ZEC/USD ─┼──▶ reserve_i × price_i ──▶ totalBasketValue() ──▶ NAV = TVL / supply
Chainlink DASH/USD ─┘
▲
│
reportReserveAttested(symbol, reserve)
▲
│ (2-of-3 signatures)
custodian + auditor
Each feed returns latestRoundData() with 8-decimal price. The
contract scales it to 18 decimals for math:
function _constituentValue(bytes32 symbol)
internal view returns (uint256)
{
Constituent storage c = constituents[symbol];
(, int256 price, , , ) = c.feed.latestRoundData();
require(price > 0, "bad price");
uint256 price18 = uint256(price) * 1e10; // 8 → 18 decimals
return (c.reserve * price18) / 1e18;
}
| Event | Effect on NAV |
|---|---|
| XMR price ↑ | NAV ↑ (weighted 35%) |
| ZEC price ↑ | NAV ↑ (weighted 35%) |
| DASH price ↑ | NAV ↑ (weighted 30%) |
| New deposit minted at NAV | NAV ≈ unchanged |
| Burn at NAV | NAV ≈ unchanged |
| Rebalance within tolerance | NAV ≈ unchanged (±slippage) |
| Fake reserve report | NAV artificially ↑ (attack) |
Initial state: XMR reserve = 100 XMR price = $160 → $16,000 ZEC reserve = 100 ZEC price = $30 → $3,000 DASH reserve = 90 DASH price = $25 → $2,250 Total basket value = $21,250 Supply = 141.666... PWRP NAV = 21,250 / 141.666... = $150.00 XMR rallies to $200 (+25%): XMR value = 100 × 200 = $20,000 Total basket value = $25,250 NAV = 25,250 / 141.666... = $178.24 (+18.8%) Actual weights now 79.2% / 11.9% / 8.9% → drift from 35/35/30 = +44.2 / -23.1 / -21.1 bps → OUT OF TOLERANCE → rebalance required within cooldown window Custodian sells ~28 XMR for ZEC + DASH (off-chain): XMR = 72, ZEC = 200, DASH = 180 XMR value = $14,400, ZEC = $6,000, DASH = $4,500 → total $24,900 Weights = 57.8% / 24.1% / 18.1% → still off target (Deeper rebalance needed; illustrative only.)
| Contract | Role | Trust |
|---|---|---|
BasketVault (UUPS) |
ERC-20 + NAV math + cap | Upgradeable by 4-of-5 timelock |
RedemptionQueue |
FIFO exit at NAV snapshot | Custodian settles, holder can timeout-refund |
RebalanceModule |
Tolerance + slippage + cooldown | Owner tunes params; custodian proposes |
ReserveAttestation |
2-of-3 EIP-712 signed reports | Custodian + auditor keys |
ComplianceGate |
Sanctions oracle + allowlist | Oracle trust + owner policy |
TimelockController |
48h delay on parameter/upgrade | 4-of-5 multisig proposes |
| Pauser | Instant pause of mint/burn/settle | 3-of-5 multisig |
function navPerToken(uint256 totalValue, uint256 totalSupply)
internal pure returns (uint256)
{
if (totalSupply == 0) return 0;
return (totalValue * 1e18) / totalSupply;
}
function tokensForValue(uint256 depositValue, uint256 nav)
internal pure returns (uint256)
{
require(nav > 0, "NavMath: zero nav");
return (depositValue * 1e18) / nav;
}
function mint(
address to,
uint256 depositValueUSD,
string calldata ref
) external onlyCustodian whenNotPaused returns (uint256 tokens) {
require(seeded, "not seeded");
uint256 nav = navPerToken();
require(nav > 0, "nav=0");
tokens = NavMath.tokensForValue(depositValueUSD, nav);
require(tokens > 0, "zero mint");
require(totalSupply() + tokens <= MAX_SUPPLY, "exceeds cap");
_mint(to, tokens);
emit Minted(to, tokens, depositValueUSD, ref);
}
function verifyAndConsume(
Report calldata report,
bytes[] calldata signatures
) external {
require(block.timestamp <= report.deadline, "expired");
require(report.nonce > lastNonce[report.symbol], "stale nonce");
require(signatures.length >= threshold, "not enough sigs");
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
REPORT_TYPEHASH, report.symbol, report.reserve,
report.nonce, report.deadline
)));
address[] memory seen = new address[](signatures.length);
uint256 valid = 0;
for (uint256 i = 0; i < signatures.length; i++) {
address s = digest.recover(signatures[i]);
require(isSigner[s], "not a signer");
bool dup = false;
for (uint256 j = 0; j < valid; j++) {
if (seen[j] == s) { dup = true; break; }
}
if (dup) continue;
seen[valid++] = s;
}
require(valid >= threshold, "below threshold");
lastNonce[report.symbol] = report.nonce;
emit ReportConsumed(report.symbol, report.reserve, report.nonce);
}
if (!vault.compliance().isClean(r.holder)) {
emit SanctionedSkip(head, r.holder);
head++;
processed++;
continue;
}
1. Custodian deposits seed basket off-chain:
100 XMR + 100 ZEC + 90 DASH
2. Custodian + auditor co-sign reserve reports (2-of-3)
3. Custodian calls reportReservesAttested([...])
4. With XMR=$160, ZEC=$30, DASH=$25:
basketValue = $21,250
5. seedMint(alice, "seed-1") with INITIAL_NAV = $150:
tokens = 21,250 / 150 = 141.666... PWRP
6. seeded = true. NAV locked at $150.00.
1. Depositor sends 50 XMR off-chain ($8,000) 2. Custodian reports updated XMR reserve 3. Custodian calls mint(depositor, 8_000e18, "dep-42") 4. Contract reads live NAV and mints 8,000 / NAV tokens 5. Holder now has PWRP worth $8,000 at NAV
1. Holder approves queue and calls requestRedemption(10 PWRP) 2. Queue snapshots valueUSD = 10 × NAV, locks PWRP 3. Custodian settles in settleBatch (batched, gas-capped) 4. Queue burns PWRP; custodian releases coins off-chain 5. If custodian stalls 7 days → emergencyRefund by holder
1. Custodian trades off-chain to restore weights 2. Custodian calls proposeRebalance with post-trade reserves 3. Contract checks: cooldown elapsed, weights within ±200 bps, total value within [minBasketValue, maxBasketValue] 4. Reserves updated; NAV recomputed on next read
1. Monitor detects anomaly 2. 3-of-5 pauser calls pause() within minutes 3. 4-of-5 multisig proposes upgrade via 48h timelock 4. Holders exit during window if they object 5. Upgrade executes; unpause via 3-of-5