Skip to content
14 changes: 14 additions & 0 deletions src/components/ContractTable/ContractTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ describe("ContractTable", () => {
expect(container.querySelector("symbol")).toBeNull();
});

it("keeps the address and copy button but renders no explorer link on a chain without explorer", () => {
const { container } = render(
<ContractTable
chain="fantom"
contracts={[{ name: "ParallelAccessManager", address: ADDR_1 }]}
/>,
);

expect(screen.getByText("0x1234…ef12")).toBeInTheDocument();
expect(screen.getByRole("button", { name: `Copy address ${ADDR_1}` })).toBeInTheDocument();
expect(screen.queryByRole("link")).not.toBeInTheDocument();
expect(container.querySelector('use[href="#cooper-icon-external-link"]')).toBeNull();
});

it("keeps the row icons decorative", () => {
const { container } = render(
<ContractTable chain="ethereum" contracts={[{ name: "eUSD", address: ADDR_1 }]} />,
Expand Down
27 changes: 15 additions & 12 deletions src/components/ContractTable/ContractTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface ContractTableProps {
*
* - Desktop (≥ 768 px): three-column table (Contract / Address / Actions) with an `sr-only` caption.
* - Mobile (< 768 px): each row collapses into a stacked card via CSS only.
* - Each row exposes a copy button and an explorer link.
* - Each row exposes a copy button and, when the chain has an explorer, an explorer link.
* - Contracts with a `description` render it inline beneath the contract name.
*/
export function ContractTable({
Expand Down Expand Up @@ -86,7 +86,7 @@ export function ContractTable({
key={`${contract.name}-${contract.address}`}
contract={contract}
chainLabel={chainLabel}
explorerUrl={buildExplorerUrl(contract.address)}
explorerUrl={buildExplorerUrl?.(contract.address)}
/>
))}
</tbody>
Expand All @@ -98,7 +98,8 @@ export function ContractTable({
interface ContractRowProps {
contract: Contract;
chainLabel: string;
explorerUrl: string;
/** Absent when the chain has no explorer to link to. */
explorerUrl?: string;
}

function ContractRow({ contract, chainLabel, explorerUrl }: ContractRowProps) {
Expand All @@ -116,15 +117,17 @@ function ContractRow({ contract, chainLabel, explorerUrl }: ContractRowProps) {
<td className="cooper-ct-actions-cell">
<span className="cooper-ct-actions">
<CopyButton address={address} />
<a
className="cooper-ct-link"
href={explorerUrl}
target="_blank"
rel="noopener noreferrer"
aria-label={`View ${name} on ${chainLabel} explorer`}
>
<Icon name="external-link" size={14} />
</a>
{explorerUrl ? (
<a
className="cooper-ct-link"
href={explorerUrl}
target="_blank"
rel="noopener noreferrer"
aria-label={`View ${name} on ${chainLabel} explorer`}
>
<Icon name="external-link" size={14} />
</a>
) : null}
</span>
</td>
</tr>
Expand Down
8 changes: 4 additions & 4 deletions src/data/prl-addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const PRL_ADDRESSES: Record<string, ContractEntry[]> = {
{ name: "ParallelAccessManager", address: "0x0e4e7Ca9D7b1e6293D0713EFEfB4BCA010DeBF46", description: "Core Protocol" },
{ name: "PeripheralPRL", address: "0xfD28f108e95f4D41daAE9dbfFf707D677985998E", description: "Parallel Governance Token l PRL Bridging Module" },
{ name: "sPRL1", address: "0x01fA35fDE0E813e2D6687660a74A313d8D922E48", description: "PRL Tokenomics" },
{ name: "sPRL2", address: "0xB22f5eDbc62adCC093307025B8fdf75a0aa00e24", description: "PRL Tokenomics" },
{ name: "sPRL2 (v2)", address: "0xB22f5eDbc62adCC093307025B8fdf75a0aa00e24", description: "PRL Tokenomics" },
{ name: "MainFeeDistributor (USDp)", address: "0x5D49C8b8cc691533742602d6aB3127904959B7e3", description: "PRL Tokenomics" },
{ name: "RewardMerkleDistributor (USDp)", address: "0x13E867f55043302925971e88aB8faB704241A96B", description: "PRL Tokenomics" },
],
Expand Down Expand Up @@ -45,13 +45,13 @@ export const PRL_ADDRESSES: Record<string, ContractEntry[]> = {
{ name: "ParallelAccessManager", address: "0x7Df74BBB6F82eC1BCB1562a30ef5Bf5c326e2811", description: "Core Protocol" },
{ name: "PeripheralPRL", address: "0x7790dd69aa10eD3f1271E41CD7222D2a7d2D5948", description: "Parallel Governance Token l PRL Bridging Module l PRL Migration Contract" },
{ name: "PeripheralMigrationContract (deprecated)", address: "0x9C68850E18EACD4ea7ca2998b6BBeD9cf55316cb", description: "Parallel Governance Token l PRL Bridging Module l PRL Migration Contract" },
{ name: "sPRL1 (deprecated)", address: "0xDB7Be3a50bdf5641757EBEa38e8014E1F0AA9475", description: "PRL Tokenomics" },
{ name: "sPRL1 (sunset, unstake only)", address: "0xDB7Be3a50bdf5641757EBEa38e8014E1F0AA9475", description: "PRL Tokenomics" },
{ name: "MainfeeDistributor (PAR, deprecated)", address: "0x90337e484B1Cb02132fc150d3Afa262147348545", description: "PRL Tokenomics" },
{ name: "RewardMerkleDistributor (PAR, deprecated)", address: "0x7b54f3D993d3bcA077946034Ea710F9c07420C72", description: "PRL Tokenomics" },
{ name: "RewardMerkleDistributor (PAR, legacy epochs)", address: "0x7b54f3D993d3bcA077946034Ea710F9c07420C72", description: "PRL Tokenomics" },
],
"sonic": [
{ name: "ParallelAccessManager", address: "0x8eFb3DED78FbaEF2a4eFe01E01BBD911E4094b78", description: "Core Protocol" },
{ name: "PeripheralPRL", address: "0xfD28f108e95f4D41daAE9dbfFf707D677985998E", description: "Parallel Governance Token l PRL Bridging Module" },
{ name: "sPRL1 (deprecated)", address: "0x7Df74BBB6F82eC1BCB1562a30ef5Bf5c326e2811", description: "PRL Tokenomics" },
{ name: "sPRL1 (sunset, unstake only)", address: "0x7Df74BBB6F82eC1BCB1562a30ef5Bf5c326e2811", description: "PRL Tokenomics" },
],
};
25 changes: 14 additions & 11 deletions src/lib/chains.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,24 @@ describe("CHAIN_LABELS", () => {
describe("EXPLORERS", () => {
const ADDR = "0x0000000000000000000000000000000000000000";

it("returns an https URL containing the address for every ChainSlug", () => {
for (const slug of SLUGS) {
const url = EXPLORERS[slug](ADDR);
expect(url.startsWith("https://")).toBe(true);
it("returns an https URL containing the address for every ChainSlug with an explorer", () => {
for (const slug of SLUGS.filter((s) => s !== "fantom")) {
const url = EXPLORERS[slug]?.(ADDR);
expect(url?.startsWith("https://")).toBe(true);
expect(url).toContain(ADDR);
}
});

it("uses the documented host per chain", () => {
expect(EXPLORERS.ethereum(ADDR)).toContain("etherscan.io");
expect(EXPLORERS.sei(ADDR)).toContain("seitrace.com");
expect(EXPLORERS.base(ADDR)).toContain("basescan.org");
expect(EXPLORERS.sonic(ADDR)).toContain("sonicscan.org");
expect(EXPLORERS.arbitrum(ADDR)).toContain("arbiscan.io");
expect(EXPLORERS.polygon(ADDR)).toContain("polygonscan.com");
expect(EXPLORERS.fantom(ADDR)).toContain("ftmscan.com");
expect(EXPLORERS.ethereum?.(ADDR)).toContain("etherscan.io");
expect(EXPLORERS.sei?.(ADDR)).toContain("seitrace.com");
expect(EXPLORERS.base?.(ADDR)).toContain("basescan.org");
expect(EXPLORERS.sonic?.(ADDR)).toContain("sonicscan.org");
expect(EXPLORERS.arbitrum?.(ADDR)).toContain("arbiscan.io");
expect(EXPLORERS.polygon?.(ADDR)).toContain("polygonscan.com");
});

it("links no explorer for Fantom, since ftmscan.com is gone", () => {
expect(EXPLORERS.fantom).toBeNull();
});
});
7 changes: 5 additions & 2 deletions src/lib/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export const CHAIN_LABELS: Record<ChainSlug, string> = {
fantom: "Fantom",
};

export const EXPLORERS: Record<ChainSlug, (addr: string) => string> = {
/** Explorer URL per chain; `null` when no explorer reliably shows the listed contracts. */
export const EXPLORERS: Record<ChainSlug, ((addr: string) => string) | null> = {
ethereum: (a) => `https://etherscan.io/address/${a}`,
base: (a) => `https://basescan.org/address/${a}`,
sonic: (a) => `https://sonicscan.org/address/${a}`,
Expand All @@ -80,5 +81,7 @@ export const EXPLORERS: Record<ChainSlug, (addr: string) => string> = {
fraxtal: (a) => `https://fraxscan.com/address/${a}`,
world: (a) => `https://worldscan.org/address/${a}`,
hemi: (a) => `https://explorer.hemi.xyz/address/${a}`,
fantom: (a) => `https://ftmscan.com/address/${a}`,
// ftmscan.com no longer resolves, and no other Fantom explorer shows the listed
// contracts reliably: the addresses stay, without a link.
fantom: null,
};
8 changes: 4 additions & 4 deletions src/pages/agents/cli/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ Only USDp bridges for now, on every supported chain.
parallel staking overview
parallel staking info <address>
parallel staking cooldown <address>
parallel staking stake --type <sprl1|sprl2> --amount <n> --wallet
parallel staking unstake --type <sprl1|sprl2> --amount <n> --wallet # starts cooldown
parallel staking unstake --type sprl2 --wallet # withdraw matured requests
parallel staking stake --type <sprl1|sprl2> --amount <n> [--chain base] --wallet
parallel staking unstake --type <sprl1|sprl2> --amount <n> [--chain <chain>] --wallet # starts cooldown
parallel staking unstake --type <sprl1|sprl2> [--chain <chain>] --wallet # withdraw matured requests
parallel staking claim --type <sprl1|sprl2|all> --wallet
```

Staking transactions run on Ethereum. The sPRL2 stake takes `--weth-amount <n>` for the PRL + WETH path; `claim` can return transactions on both Ethereum and Polygon.
`stake` and `unstake` act on the chain given by `--chain` (default `ethereum`): stake on Ethereum or Base, unstake on the chain that holds the position, Polygon and Sonic included while staking is being sunset there. The sPRL2 stake takes `--amount` in BPT of the chosen chain's pool, or `--amount` in PRL with `--weth-amount <n>` for the PRL + WETH path. `claim` sends one claim on Base, plus one on Polygon for the epochs published before the move to Base, while any remain. `info` and `cooldown` cover every chain, and list what is left in the paused sPRL2 v1 vault.

## Governance

Expand Down
21 changes: 11 additions & 10 deletions src/pages/agents/mcp/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { HelpFooter } from '@/components/HelpFooter'
- **Slippage is in basis points** — `50` = 0.5%. Default 50, maximum 500. Applies to `mint`, `redeem` and `redeem_proportional`.
- **Chains are lowercase slugs** (`base`, `ethereum`, `hyperevm`, …). Minting, redeeming and savings run on the chains with a Parallelizer deployment — Ethereum, Base, Avalanche and HyperEVM; on Sonic those operations return `CHAIN_SUNSET` (PIP-64 wind-down) while bridging and balance reads stay active. Bridging covers every supported chain.
- **Multi-chain aggregates are honest about failures**: when a chain read fails, the response carries `partial: true` and a `failedChains` list, and the totals exclude those chains — never a silently wrong number. `get_proof_of_solvency` goes further and refuses to conclude on incomplete data (`PARTIAL_READ`).
- **Every tool carries MCP annotations**: reads are marked `readOnlyHint: true`, transaction builders `readOnlyHint: false`, and no tool is marked destructive. Every tool returns its data as JSON, `get_protocol_parameters` included.

## Protocol data

Expand All @@ -27,7 +28,7 @@ import { HelpFooter } from '@/components/HelpFooter'
| `get_supported_chains` | The 24 chains USDp is deployed on, with status and contract addresses (Sonic is flagged sunset — mint, redeem and savings were wound down there under PIP-64, while USDp, bridging and balance reads stay live) |
| `get_supported_collaterals` | Collaterals accepted on a chain, with caps, utilization and mint/redeem status |
| `get_fee_rates` | Adaptive mint/redeem fees per collateral — can be negative (incentives) |
| `get_savings_rate` | The protocol-wide sUSDp APY (7d/30d/90d), exchange rate and total deposits |
| `get_savings_rate` | The protocol-wide sUSDp APY (7d/30d/90d), exchange rate and total deposits, with `feeShare`: the share of the protocol surplus not kept by the DAO Treasury, read on-chain (90% today), or `null` when that read fails |
| `get_proof_of_solvency` | Total backing vs USDp supply — the canonical solvency ratio, per chain |

## Savings (sUSDp)
Expand Down Expand Up @@ -62,30 +63,30 @@ import { HelpFooter } from '@/components/HelpFooter'

| Tool | What it does |
|---|---|
| `get_staking_overview` | Epochs, totals staked in sPRL1/sPRL2, voting power, and APRs (when an epoch has settled — otherwise null, with the reason in `unavailableFields`) |
| `get_staking_info` | One address's full staking position: balances, rewards, cooldowns, ParaBoost |
| `get_cooldown_status` | Pending unstake requests, remaining time and estimated penalties |
| `stake` | Builds staking transactions — PRL into sPRL1, or PRL/BPT into sPRL2 |
| `unstake` | With `amount`: builds the cooldown start. Without: builds the withdrawal of matured requests |
| `claim_rewards` | Builds reward claims for sPRL1/sPRL2 — may return transactions on several chains |
| `get_staking_overview` | Epochs, totals staked in sPRL1/sPRL2 in all and per chain, voting power, and APRs with an `aprNote` (when an epoch has settled — otherwise null, with the reason in `unavailableFields`) |
| `get_staking_info` | One address's full staking position on every chain: balances, rewards (including the legacy ones on Polygon), cooldowns, ParaBoost, and any sPRL2 left in the paused v1 vault |
| `get_cooldown_status` | Pending unstake requests on every chain, remaining time and estimated penalties |
| `stake` | Builds staking transactions on Ethereum or Base (`chain`) — PRL into sPRL1, or BPT or PRL + WETH into sPRL2 |
| `unstake` | On the `chain` that holds the position. With `amount`: builds the cooldown start. Without: builds the withdrawal of matured requests |
| `claim_rewards` | Builds reward claims for sPRL1/sPRL2 on Base, plus one on Polygon for the epochs published before the move to Base while any remain |

sPRL1 is deployed on Ethereum, Polygon, Base and Sonic; sPRL2 on Ethereum only.
New stakes go to Ethereum or Base: sPRL1, and the sPRL2 v2 vault, which replaced the paused v1 vault on Ethereum. sPRL1 on Polygon and Sonic is being sunset: a stake there is refused with `CHAIN_SUNSET`, while unstaking and withdrawing stay open and carry a `STAKING_SUNSET` warning. Every staking APR comes with an `aprNote`: a 0.00% means the last epoch distributed no rewards, not that a vault pays none.

## Governance

| Tool | What it does |
|---|---|
| `get_proposals` | Snapshot proposals (PIP/PGP/PIR), filterable by type and status |
| `get_proposal_details` | One proposal in full, votes and result included |
| `get_protocol_parameters` | Contract addresses, governance roles, fees and bridge limits — one formatted summary |
| `get_protocol_parameters` | Contract addresses, governance roles, fees, collateral caps, bridge limits and `savingsFeeShare` (90% today, read on-chain, `null` when that read fails), in one JSON answer |

## Agent payments (x402)

| Tool | What it does |
|---|---|
| `get_payment_capabilities` | Payment routes available on a chain and whether gas is sponsored |
| `prepare_x402_payment` | Builds a gasless x402 payment — returns typed data for your wallet to sign; the facilitator settles on-chain only after that |
| `prepare_convert_and_pay` | Builds a two-step payment — redeem USDp to a collateral, then pay it, for recipients wanting a specific token |
| `prepare_convert_and_pay` | Builds a two-step payment — redeem USDp to a collateral, then pay it, for recipients wanting a specific token. The collateral must support EIP-3009 (USDC by default); any other target is refused before anything is built |

Gas is sponsored on every Parallelizer chain except Ethereum. See [x402](/agents/x402) for the full payment protocol.

Expand Down
2 changes: 1 addition & 1 deletion src/pages/agents/mcp/transactions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The approval is **bounded**: the calldata grants exactly `approvalAmount` — th
Every write is statically simulated before it reaches you:

- `simulation.success: false` comes with a `revertReason`. When the response bundles an `approvalTx`, a revert on allowance is the expected pre-approval state and is flagged `PENDING_APPROVAL` with severity `info` — submit the approval first, then the transaction. Any other revert is flagged `SIMULATION_FAILED` with severity `error`: the transaction would fail on-chain, don't send it.
- `warnings[]` also surfaces business risks before you sign: an sPRL1 early-unstake penalty (`EARLY_UNSTAKE_PENALTY`), a ParaBoost about to be lost on unstake (`PARABOOST_LOST`) or locked on stake (`PARABOOST_LOCK`).
- `warnings[]` also surfaces business risks before you sign: an sPRL1 early-unstake penalty (`EARLY_UNSTAKE_PENALTY`), a ParaBoost about to be lost on unstake (`PARABOOST_LOST`) or locked on stake (`PARABOOST_LOCK`), or an unstake on Polygon or Sonic, where staking is being sunset (`STAKING_SUNSET`).

Each warning has a `severity` (`info`, `warning`, `error`) and a human-readable `message` — surface them to whoever approves the transaction.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: PAR Contract Addresses
description: Deployed PAR contract addresses for the legacy Parallel V2 stablecoin, across Ethereum, Fantom and Polygon, with a link to each block explorer.
description: Deployed PAR contract addresses for the legacy Parallel V2 stablecoin, across Ethereum, Fantom and Polygon, with block explorer links on Ethereum and Polygon.
---

import { ContractAddressesPage } from '@/components/ContractAddressesPage';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ description: >-

This document details features related to the new [PRL](https://github.com/parallel-protocol/parallel-prl/blob/main/contracts/principal/PRL.sol) token and the migration from Mimo token to PRL.

:::info
The migration from MIMO to PRL closed on 15 April 2026 under PIP-65: the `PrincipalMigrationContract` on Ethereum is paused and no longer holds PRL. The migration contracts are described below for reference. See [MIMO to PRL Migration](/governance/parallel-governance-token-prl/mimo-to-prl-migration).
:::

3 types of contracts:

* The [`PRL`](https://github.com/parallel-protocol/parallel-prl/blob/main/contracts/principal/PRL.sol) token contract that inherit of Openzeppelin ERC20 and ERC20Permit standards.
Expand Down
Loading