DESIGN DOCUMENT · INTERACTIVE

$PWRP — Privacy Basket Wrapper Token

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.

Max Supply
15,000,000 PWRP
Hard cap enforced in mint(). 18 decimals, 1.5e25 wei.
Target Weights
35 / 35 / 30
XMR · ZEC · DASH, tolerance ±200 bps
Pricing
NAV = TVL / Supply
No DEX pool is the price. NAV is computed on-chain from three Chainlink USD feeds × reported reserves.

What $PWRP is — and is not

It is

  • An ERC-20 with a hard 15M cap.
  • A claim on a custodied basket of native XMR/ZEC/DASH.
  • Priced by on-chain NAV from Chainlink feeds.
  • Mint/burn gated by 2-of-3 signed reserve reports.
  • Upgradeable only through a 48h timelock.
  • Pausable in seconds by a 3-of-5 multisig.

It is not

  • Trustless. No cryptographic bridge exists from Monero/Zcash/Dash to EVM.
  • Private. Holding PWRP is fully transparent on-chain.
  • Free-floating. The price cannot be pumped by a thin DEX pool — it is NAV-anchored, but NAV itself depends on trusted reserves.
  • Regulatorily blessed. Privacy coins face listing and AML scrutiny in many jurisdictions.

Four trust pillars

PillarMechanismFailure mode if broken
CustodyOff-chain vaults holding XMR/ZEC/DASH Reserves ≠ reported → NAV overstates → mint is theft
Attestation2-of-3 EIP-712 signatures (custodian + auditor) Collusion of 2 keys → fake reserves accepted
Governance48h timelock, 4-of-5 multisig Compromised multisig → arbitrary upgrade
ComplianceSanctions oracle + optional allowlist Stale oracle → mint halts; sanctioned transfer → frozen redemption

3D value-flow visualization

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.

Layer 0 · Source coins
Native XMR, ZEC, DASH live on their own chains. They cannot be read by Ethereum.
Layer 1 · Custody + attestation
Off-chain vaults sign a reserve report; 2-of-3 threshold required.
Layer 2 · On-chain pricing
Chainlink USD feeds × reported reserves → NAV. $PWRP mint/burn follows NAV.

NAV simulator

Adjust reserves, prices, and NAV inputs. The simulator mirrors the on-chain math: NAV = Σ(reserve_i × price_i) / totalSupply, and mint/burn follow NAV.

Inputs

Live output

XMR value$0
ZEC value$0
DASH value$0
Total basket value$0
On-chain supply0
NAV per PWRP$0
Actual weights— / — / —
Drift vs 35/35/30—
Drift status—
Deposit $1,000 → mint0 PWRP
Burn 1 PWRP → receive$0

Simulation log

Simulator note: the real contract reads Chainlink feeds; here you move the prices by hand. The math is identical — only the source of the price differs.

How Chainlink feeds price $PWRP

$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.

Price pipeline

  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

Chainlink feed contract (AggregatorV3Interface)

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;
}

What changes the price?

EventEffect on NAV
XMR price ↑NAV ↑ (weighted 35%)
ZEC price ↑NAV ↑ (weighted 35%)
DASH price ↑NAV ↑ (weighted 30%)
New deposit minted at NAVNAV ≈ unchanged
Burn at NAVNAV ≈ unchanged
Rebalance within toleranceNAV ≈ unchanged (±slippage)
Fake reserve reportNAV artificially ↑ (attack)

What happens on a price move — worked example

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 inventory

ContractRoleTrust
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

Key snippets

NAV math (NavMath.sol)
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;
}
Mint at live NAV (BasketVault.sol)
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);
}
2-of-3 attestation (ReserveAttestation.sol)
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);
}
Redemption queue skip on sanctions
if (!vault.compliance().isClean(r.holder)) {
    emit SanctionedSkip(head, r.holder);
    head++;
    processed++;
    continue;
}

End-to-end lifecycle

Phase A — Bootstrap (one-time)

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.

Phase B — Minting

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

Phase C — Redemption

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

Phase D — Rebalance

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

Phase E — Incident

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

What can go wrong

Custody failure

Reserves are not what the report says. NAV is inflated. Mint becomes theft from existing holders. Mitigation: 2-of-3 attestation, physical audits, insurance.

Attestation collusion

Custodian + 1 auditor collude. Fake reserves accepted. Mitigation: legal separation, independent entities, signer rotation.

Oracle manipulation

Thin Chainlink feeds mispriced during rebalance. Mitigation: staleness guard, TWAP fallback, tolerance checks.

Governance compromise

4-of-5 multisig compromised → arbitrary upgrade. Mitigation: 48h timelock, public monitoring, signer hardware separation.

Regulatory action

Privacy coins delisted or banned in a jurisdiction. Mitigation: compliance gate, jurisdictional allowlist, legal opinion.

Sanctions freeze

Holder becomes sanctioned between request and settlement. Queue skips and freezes their PWRP. Mitigation: disclosed in terms.

The irreducible caveat

No module removes the custody assumption. Monero, Zcash, and Dash are on their own chains; an EVM contract cannot see them. The strongest claim this design can make is: two independent entities must collude, and a 48h public window must be ignored by every holder, for a theft to succeed. That is the ceiling. There is no seventh module.