# Overview
Source: https://docs.polygon.technology/blockchain-rails/index
The settlement layer for the Open Money Stack: Polygon Chain for public settlement and Polygon CDK for dedicated, compliance-grade infrastructure.
Every component of the Open Money Stack depends on reliable settlement. The OMS offers two options depending on your requirements.
## Polygon Chain
**Public, permissionless settlement.**
Polygon Chain is the public Layer 2 network that serves as the default settlement layer for the OMS. It is open, permissionless, and production-proven at scale.
* Sub-2-second finality
* Average transaction cost of \$0.002
* \$54B in stablecoin transfer volume
* 6.4B total transactions
* 159M unique wallet addresses
* Major integrations by Revolut, Stripe, Flutterwave, and more
Polygon Chain is the right choice for applications that benefit from public auditability, open access, and the broadest possible ecosystem of tooling, liquidity, and users.
Architecture, network stats, and how to start building on Polygon Chain.
***
## Polygon CDK
**Dedicated, compliance-grade blockchain infrastructure.**
For institutions that need their own rails, Polygon Chain Development Kit (CDK) lets you launch a production-ready rollup with custom throughput, fee structures, and compliance requirements.
### Why Polygon CDK
* **20,000+ TPS** when optimized for payment workloads
* **Rollup mode** with pessimistic proof security via Agglayer, validium, or full zkRollup
* **Granular network control**: deploy as fully private or with gated access, API keys, and ACLs for read/write permissions
* **EVM-equivalent**: deploy existing smart contracts without modification; standard Ethereum tooling works without reconfiguration
* **Execution clients**: supports both op-geth and op-reth, giving operators flexibility in resource usage and performance tuning
* **Implementation providers**: Conduit and Gateway are building open source tooling and support both execution clients for production deployments
* **Migration paths**: zero-downtime migration for chains running Hyperledger Besu, preserving full data history
### Agglayer connectivity
Cross-chain interoperability and unified liquidity are native features of every Polygon CDK chain. Agglayer provides shared state, cross-chain messaging, and trustless bridging across connected networks, without additional infrastructure. This means enterprise chains are not isolated: they can interoperate with Polygon Chain and other connected L2s from day one.
### Operating modes
| Mode | Description |
| ------------- | ----------------------------------------------------------------------------------------------- |
| **Sovereign** | Agglayer connectivity secured by pessimistic proofs. No prover required. Default configuration. |
| **Validium** | ZK-secured execution with offchain data availability via a DAC. |
| **zkRollup** | Fully onchain zero-knowledge rollup for maximum security and Ethereum-aligned trust. |
### Who Polygon CDK is for
* **Banks and financial institutions** needing dedicated settlement infrastructure with compliance controls and public auditability
* **Asset issuers** that need custom compliance rules, token economics, or throughput requirements on dedicated chain infrastructure
* **Enterprises and governments** building payment infrastructure that requires a sovereign rollup with full control over sequencing, fee policy, and data availability
Explore Polygon CDK capabilities, operating modes, and how to get started.
***
## Choosing between them
| | Polygon Chain | Polygon CDK |
| -------------------- | ------------------------------------------- | --------------------------------------------------------------------------- |
| **Access** | Public, permissionless | Private or gated |
| **Throughput** | 110 TPS | 20,000+ TPS (payment-optimized) |
| **Fee control** | Network-determined | Operator-defined |
| **Compliance** | Application-level | Chain-level controls, ACLs, API keys |
| **Infrastructure** | Shared public network | Dedicated rollup |
| **Interoperability** | Native ecosystem access | Via Agglayer |
| **Best for** | Open apps, broad reach, ecosystem liquidity | Regulated institutions, high-volume payment rails, sovereign infrastructure |
Most applications start on Polygon Chain. Institutions with regulatory requirements, dedicated throughput needs, or custom fee structures deploy Polygon CDK chains that remain connected to the broader ecosystem via Agglayer.
# Polygon CDK FAQs
Source: https://docs.polygon.technology/chain-development/cdk/additional-resources/faqs
Frequently asked questions about Polygon CDK, covering execution clients, rollup modes, and Agglayer integration.
## What is the Polygon CDK?
Polygon Chain Development Kit (CDK) is a toolkit for launching Ethereum Layer 2 chains with native Agglayer connectivity. It supports **op-geth** and **op-reth** execution clients, giving operators flexibility in performance tuning and resource usage.
## What execution clients does CDK support?
CDK supports two execution clients:
* **op-geth**: Geth-based client with OP Stack architecture and broad tooling support.
* **op-reth**: Reth-based client optimized for high throughput and lower resource consumption.
Both are supported by Conduit and Gateway for production deployments.
## What is a sovereign chain in CDK?
A sovereign chain operates without a prover. Instead, it uses pessimistic proofs via Agglayer to enforce safety and ensure that no chain can withdraw more than it deposits. This enables secure, low-cost execution with fast finality. Sovereign mode is the default configuration.
## What rollup modes are available?
Every CDK chain supports three operating modes:
* **Sovereign**: Agglayer connectivity secured by pessimistic proofs. No prover required.
* **Validium**: ZK-secured execution with offchain data availability via a DAC.
* **zkRollup**: Fully onchain ZK rollup for maximum security and Ethereum-aligned trust.
## How does Agglayer fit into Polygon CDK?
Agglayer is a native feature of every CDK chain. It enables cross-chain interoperability, unified liquidity, and shared state across the connected network. CDK chains are connected to Agglayer by default.
## How can I start building with CDK?
Use the CDK quickstarts to deploy a local testnet. For production-ready deployments, contact [**Conduit**](https://conduitxyz.typeform.com/to/CrvgqEeA?utm_source=polygonannouncement\&utm_medium=partnerblog) or [**Gateway**](https://share.hsforms.com/1toN701PtTBCpyc3bKQBCRAcy6wj?referrer=12118007123).
# Glossary
Source: https://docs.polygon.technology/chain-development/cdk/additional-resources/glossary
Definitions of technical terms used throughout the Polygon CDK documentation.
### Agglayer
Agglayer is a cross-chain settlement layer that connects the liquidity and users of any blockchain for fast, low-cost interoperability and growth. It enables trustless bridging, shared messaging, and unified state across Layer 2s using zero-knowledge (ZK) proofs.
[Visit agglayer.dev](https://www.agglayer.dev/)
### Polygon CDK (Chain Development Kit)
An enterprise-grade toolkit for building custom Ethereum Layer 2 chains. Each CDK chain is natively connected to Agglayer for cross-chain interoperability, shared liquidity, and integrated messaging. CDK supports **op-geth** and **op-reth** execution clients.
### Chain Operator
An individual, team, or DAO responsible for launching and managing a CDK-based chain. Operators may handle sequencing, bridge operations, infrastructure deployment, data availability configuration, and other network responsibilities.
### Data Availability (DA)
The requirement that transaction data remains accessible to Layer 1 validators for verifying off-chain execution. DA ensures the security of modular rollups. CDK chains can use onchain DA (e.g., Ethereum), off-chain DACs, or local solutions.
### Data Availability Committee (DAC)
A decentralized set of nodes that ensures off-chain data availability for CDK chains using Validium mode. DAC nodes fetch transaction data, verify it, sign it, and store it for later retrieval.
### Implementation Providers (IPs)
External infrastructure teams that assist developers with launching and maintaining CDK chains. Conduit and Gateway both support op-reth and op-geth execution clients and are building open source tooling for CDK deployments.
### Rollups
Layer 2 solutions that execute transactions off-chain and post state data to Ethereum. CDK supports multiple rollup types, including:
* [Optimistic rollups](https://ethereum.org/en/developers/docs/scaling/optimistic-rollups/)
* [ZK rollups](https://ethereum.org/en/developers/docs/scaling/zk-rollups/)
* Validium (ZK with off-chain DA)
* Sovereign (non-ZK with pessimistic proofs)
### Sovereign Mode
A rollup configuration that does not use a ZK prover. Instead, Agglayer enforces security through pessimistic proofs, ensuring that no chain can withdraw more than it deposits. Default configuration for CDK chains.
### Unified Bridge
The shared bridge infrastructure for CDK chains, providing secure cross-chain asset transfers and messaging. It supports shared escrow, sovereign and ZK rollup flows, and native Agglayer integration.
### Unified Escrow
A component of the Unified Bridge that holds tokens either bridged from Ethereum or minted natively on Layer 2. Ensures consistent accounting and solvency across all CDK chains.
### Validium
A rollup configuration that uses ZK proofs for execution but stores transaction data off-chain, reducing costs and increasing throughput. CDK Validium chains rely on DACs for off-chain data availability.
[Learn more about Validium](https://ethereum.org/en/developers/docs/scaling/validium/)
### ZK Proofs
Zero-knowledge proofs validate computations without revealing input data. Agglayer uses ZK proofs to provide cryptographic guarantees for execution, enabling fast finality and trustless security across chains.
# Deployment Modes
Source: https://docs.polygon.technology/chain-development/cdk/cdk-opgeth/architecture
Component reference for the three CDK deployment modes: sovereign, validium, and zkrollup.
The three `cdk-opgeth` deployment modes differ primarily in data availability and prover setup. All share the same Geth-based client and OP Stack components.
### `cdk-opgeth-sovereign`
| Component | Description / Link |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Execution Layer | [OP Geth Client](https://github.com/ethereum-optimism/op-geth): Ethereum client modified for Optimism |
| Consensus Layer | [OP Node](https://github.com/ethereum-optimism/optimism): Handles block production and synchronisation |
| AggKit - Oracle | [AggOracle](https://github.com/agglayer/aggkit): Updates Global Exit Root (GER) onchain |
| AggKit - Sender | Sends certificates from the chain to Agglayer |
| Bridge API | [zkevm-bridge-service](https://github.com/0xPolygonHermez/zkevm-bridge-service): Enables messaging between chains |
| Data Availability Layer | [OP Batcher](https://github.com/ethereum-optimism/optimism): Sends transaction data to Ethereum Mainnet (Layer 1) |
| Agglayer Network | [Agglayer](https://github.com/agglayer/agglayer), Agglayer Node, Agglayer Prover |
| Smart Contracts (L1 + L2) | [Optimism Contracts](https://github.com/ethereum-optimism/optimism/releases/tag/op-deployer%2Fv0.0.11) |
| Ethereum Bridge Contracts | [Polygon zkEVM Contracts](https://github.com/0xPolygonHermez/zkevm-contracts): Manages final settlement on Ethereum |
### `cdk-opgeth-zkrollup`
| Component | Description / Link |
| ------------------------- | ------------------------------------------------------------------------------------------------------ |
| Execution Layer | [OP Geth Client](https://github.com/ethereum-optimism/op-geth) |
| Consensus Layer | [OP Node](https://github.com/ethereum-optimism/optimism) |
| Proposer Service | [OP Proposer](https://github.com/ethereum-optimism/optimism) |
| AggKit - Oracle | [AggOracle](https://github.com/agglayer/aggkit) |
| AggKit - Sender | Sends certificates to Agglayer |
| Bridge API | [zkevm-bridge-service](https://github.com/0xPolygonHermez/zkevm-bridge-service) |
| Data Availability Layer | Ethereum Mainnet (onchain data only) |
| Agglayer Network | [Agglayer](https://github.com/agglayer/agglayer), Agglayer Node, Agglayer Prover |
| Smart Contracts (L1 + L2) | [Optimism Contracts](https://github.com/ethereum-optimism/optimism/releases/tag/op-deployer%2Fv0.0.11) |
| Ethereum Bridge Contracts | [Polygon zkEVM Contracts](https://github.com/0xPolygonHermez/zkevm-contracts) |
| Prover Network | [SP1 Prover](https://github.com/succinctlabs/sp1): zkVM-based prover |
### `cdk-opgeth-validium`
This mode shares the same architecture as `zkrollup`, but uses an alternative data availability (DA) layer.
| Component | Description / Link |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Execution Layer | [OP Geth Client](https://github.com/ethereum-optimism/op-geth) |
| Consensus Layer | [OP Node](https://github.com/ethereum-optimism/optimism) |
| Proposer Service | [OP Proposer](https://github.com/ethereum-optimism/optimism): Proposes blocks and batches |
| AggKit - Oracle | [AggOracle](https://github.com/agglayer/aggkit) |
| AggKit - Sender | Sends certificates to Agglayer |
| Bridge API | [zkevm-bridge-service](https://github.com/0xPolygonHermez/zkevm-bridge-service) |
| Data Availability Layer | [Alt-DA Mode (TBD)](https://docs.optimism.io/stack/beta-features/alt-da-mode): Off-chain or alternative DA provider |
| Agglayer Network | [Agglayer](https://github.com/agglayer/agglayer), Agglayer Node, Agglayer Prover |
| Smart Contracts (L1 + L2) | [Optimism Contracts](https://github.com/ethereum-optimism/optimism/releases/tag/op-deployer%2Fv0.0.11) |
| Ethereum Bridge Contracts | [Polygon zkEVM Contracts](https://github.com/0xPolygonHermez/zkevm-contracts) |
| Prover Network | [SP1 Prover](https://github.com/succinctlabs/sp1): zkVM-based prover |
# Devnet Deployment Guide
Source: https://docs.polygon.technology/chain-development/cdk/cdk-opgeth/devnet-deployment-guide
How to deploy a production-like Pessimistic Proof (cdk-opgeth-sovereign) devnet.
Use this guide to deploy a production-like **Pessimistic Proof (PP)** (`cdk-opgeth-sovereign`) devnet.
***
## Prerequisites
Before starting, ensure you have:
* A valid L1 RPC URL
* A wallet address with Sepolia testnet funds (deployer)
* [Docker](https://www.docker.com/) (or any other container orchestrator of your choice)
* [Cast](https://book.getfoundry.sh/cast/)
* [Polycli](https://github.com/0xPolygon/polygon-cli)
* [Reference Repo](https://github.com/0xPolygon/cdk-devnets/tree/main/cdk-opgeth-pp) (configuration files used in the guide)
Export these variables:
```bash theme={null}
export L1_RPC_URL=https://...
export SEPOLIA_PROVIDER=${L1_RPC_URL}
export DEPLOYER_PRIVATE_KEY=0x0000000000000000000000000000000000000000
```
The values shown are specific to this example. Replace them with values relevant to your own setup.
***
## Rollup Network Creation
### Step 1: Submit a Request
To initiate the rollup creation:
1. The **Implementation Provider (IP)** must submit a request to Polygon Labs.
2. Use the [Polygon Support Portal](https://polygon.atlassian.net/servicedesk/customer/portal/22) to raise a support ticket.
#### Required Parameters
| Parameter | Description | Example |
| ----------------- | --------------------------------------------- | -------------------------------------------- |
| Rollup Type | Type of rollup | `Pessimistic Proofs (PP)` |
| Chain ID | Unique identifier for your rollup | `473` |
| Admin Address | Your control address for rollup modifications | `0xBe46896822BD1d415522F3a5629Fe28447b95563` |
| Sequencer Address | Used by AggKit, must be under your control | `0x0769fcb9ca9369b0494567038E5d1f27f0CBE0aC` |
| Gas Token Address | Token or zero address | `0x0000000000000000000000000000000000000000` |
| Sequencer URL | *(Not used for PP)* | `https://...` |
| Datastream URL | *(Not used for OP)* | `https://...` |
| Network Name | Final network name (non-editable) | `bali-36-op` |
***
### Step 2: Setup by Polygon Labs
Once approved:
* Polygon Labs provisions your Rollup.
* A transaction is recorded (e.g., [example](https://sepolia.etherscan.io/tx/0x111618eedb16b416aef393db6dd2d73d5a190dd5e15bdaa704473ba89a497f92)).
You will receive:
* `combined.json`: Core deployment details
* `genesis-base.json`: Needed for genesis generation
Store these files securely. They are required for further deployment steps.
***
## Genesis File Generation
### Option Selection
Choose one:
* **Option 1 (Recommended)**: Merge OP + Polygon Genesis with pre-deployed contracts.
* **Option 2**: Manual L2 contract deployment.
This guide focuses on **Option 1**.
***
### Step-by-Step Instructions
1. **Environment Setup**
Checkout the Desired Version. We will be using`v10.0.0-rc.7` in this example
```bash theme={null}
git clone https://github.com/0xPolygonHermez/zkevm-contracts.git
cd zkevm-contracts
git checkout v10.0.0-rc.7
```
Install dependencies as described in the repo's [README](https://github.com/0xPolygonHermez/zkevm-contracts).
2. **Parameter File Creation**
```bash theme={null}
cp ./tools/createSovereignGenesis/create-genesis-sovereign-params.json.example ./tools/createSovereignGenesis/create-genesis-sovereign-params.json
```
Update this file with the relevant information from your `combined.json` and your wallet addresses.
```json theme={null}
{
"rollupManagerAddress": "polygonRollupManagerAddress",
"rollupID": rollupID,
"chainID": chainID,
"gasTokenAddress": "gasTokenAddress",
"bridgeManager": "admin address",
"sovereignWETHAddress": "0x0000000000000000000000000000000000000000",
"sovereignWETHAddressIsNotMintable": false,
"globalExitRootUpdater": "aggoracle address",
"globalExitRootRemover": "0x0000000000000000000000000000000000000000",
"emergencyBridgePauser": "admin address",
"setPreMintAccounts": true,
"preMintAccounts": [ # add as many as you like
{
"balance": "1000000000000000000",
"address": "admin address"
}
],
"setTimelockParameters": true,
"timelockParameters": {
"adminAddress": "admin address",
"minDelay": 0 # timelock delay, for devnets it's convinient to set it to zero
},
"formatGenesis": "geth"
}
```
3. **Base Genesis Template**
```bash theme={null}
cp ./tools/createSovereignGenesis/genesis-base.json.example ./tools/createSovereignGenesis/genesis-base.json
```
Paste the contents of your `genesis-base.json` into this file.
4. **Generate Genesis Files**
```bash theme={null}
npx hardhat run ./tools/createSovereignGenesis/create-sovereign-genesis.ts --network sepolia
```
5. **Rename the Output Files for Clarity**
```bash theme={null}
mv ./tools/createSovereignGenesis/genesis-rollupID-*.json ./tools/createSovereignGenesis/polygon-genesis.json
mv ./tools/createSovereignGenesis/output-rollupID-*.json ./tools/createSovereignGenesis/polygon-genesis-info.json
```
***
## Network Deployment
### Environment Variables
```bash theme={null}
export CLAIMTX_ADDRESS=0x0e40237b464f9945FDE774a2582109Aa943b9111
export AGGORACLE_ADDRESS=0x94e8844309E40f4FFa9146a7a890077561f925bc
export CHAIN_ID=473
```
The values shown are specific to this example. Replace them with values relevant to your own setup.
### L2 Deployment (Using op-deployer)
To set up your Layer 2 (L2) network using the OP Stack, we recommend starting with the [official Optimism L2 Rollup tutorial](https://docs.optimism.io/operators/chain-operators/tutorials/create-l2-rollup). This guide provides a practical reference based on that tutorial and uses the [op-deployer](https://docs.optimism.io/operators/chain-operators/tools/op-deployer) tool to streamline the deployment process.
All commands in this section are executed from the root directory of your project.
1. **Initialize deployer Folder**
```bash theme={null}
docker run --rm -v $(pwd)/deployer:/deployer -it us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:v0.0.13 \
init \
--l1-chain-id 11155111 \
--l2-chain-ids "${CHAIN_ID}" \
--workdir /deployer
```
2. **Edit `intent.toml`** with your parameters.
3. **Deploy L1 Contracts**
```bash theme={null}
docker run --rm -v $(pwd)/deployer:/deployer -it us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:v0.0.13 \
apply \
--workdir /deployer \
--l1-rpc-url ${L1_RPC_URL} \
--private-key ${DEPLOYER_PRIVATE_KEY}
```
4. **Merge Genesis Files**
Next, you'll need to combine the `polygon-genesis.json` file with the OP Stack's `genesis.json`. The recommended approach is to embed the contents of `polygon-genesis.json` directly into the op-deployer state.
```bash theme={null}
# extract the allocs
cat deployer/state.json | jq -r '.opChainDeployments[].allocs' | base64 -d | gzip -d > allocs.json
# merge
jq -s add allocs.json files/polygon-genesis.json | gzip | base64 > merge
# create a copy of the original state
cp deployer/state.json deployer/original-state.json
# replace the original allocs by the merged
cat deployer/state.json | jq ".opChainDeployments[].allocs=\"$( cat merge )\"" > state.json && mv state.json deployer/state.json
# cleanup
rm allocs.json merge
```
5. **Generate Files**
```bash theme={null}
docker run --rm -v $(pwd)/deployer:/deployer -it us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:v0.0.13 \
inspect genesis \
--workdir /deployer "${CHAIN_ID}" > ./deployer/genesis.json
docker run --rm -v $(pwd)/deployer:/deployer -it us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:v0.0.13 \
inspect rollup \
--workdir /deployer "${CHAIN_ID}" > ./deployer/rollup.json
```
***
## Component Setup
### OP Stack
1. **Create `.env` values**
```bash theme={null}
CHAIN_ID=473
SEQUENCER_PRIVATE_KEY=redacted
BATCHER_PRIVATE_KEY=redacted
L1_RPC_URL_HTTP=https://...
L1_RPC_URL_WS=wss://...
```
The values shown are specific to this example. Replace them with values relevant to your own setup.
2. **Generate secret**
```bash theme={null}
openssl rand -hex 32 > deployer/jwt.txt
```
3. **Fund the batcher wallet on L1**
4. **Initialize Data Directory**
```bash theme={null}
docker run --rm -it \
-v $(pwd)/deployer/genesis.json:/etc/optimism/genesis.json:ro \
-v $(pwd)/datadir:/datadir \
us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101411.3 \
init \
--state.scheme=hash \
--datadir=/datadir \
/etc/optimism/genesis.json
```
5. **Start Services**
```bash theme={null}
docker compose up -d op-geth
docker compose up -d op-node
docker compose up -d op-batcher
```
***
### Aggkit
1. **Config**
Update the `config/aggkit.toml` and `config/bridge.toml` config files with the relevant information from your `combined.json`, `polygon-genesis-info.json` and your wallet addresses.
2. **Create keystore files**
The aggkit and bridge services require the sequencer, aggoracle, and claimtx wallet addresses to be provided through an encrypted keystore.
```bash theme={null}
mkdir keystore
cast wallet import --private-key ${SEQUENCER_PRIVATE_KEY} --keystore-dir .keystore/ sequencer.keystore
cast wallet import --private-key ${AGGORACLE_PRIVATE_KEY} --keystore-dir .keystore/ aggoracle.keystore
cast wallet import --private-key ${CLAIMTX_PRIVATE_KEY} --keystore-dir .keystore/ claimtx.keystore
```
3. **Fund ClaimTX & Aggoracle**
```bash theme={null}
cast send \
--value 100ether \
--mnemonic "test test test test test test test test test test test junk" \
--rpc-url http://$(docker compose port op-geth 8545) \
"${CLAIMTX_ADDRESS}"
cast send \
--value 10ether \
--mnemonic "test test test test test test test test test test test junk" \
--rpc-url http://$(docker compose port op-geth 8545) \
${AGGORACLE_ADDRESS}
```
4. **Start PostgreSQL DB**
```bash theme={null}
docker compose up -d db
```
5. **Run Services**
```bash theme={null}
docker compose up -d bridge
docker compose up -d aggkit
```
***
## Bridge
### Environment Variables
```bash theme={null}
export ROLLUP_ID=36
export BRIDGE_ADDRESS=0x1348947e282138d8f377b467f7d9c2eb0f335d1f
export TEST_ADDRESS=0xda9f3DCA867C4Bc7b1f8e2DB47Aa8C338Ba2e056
export TEST_PRIVATE_KEY=redacted
```
The values shown are specific to this example. Replace them with values relevant to your own setup.
### L1 to L2
1. **Initiate the Deposit from L1**
Use the following command to bridge assets from L1 to your L2 network:
```bash theme={null}
polycli ulxly bridge asset \
--bridge-address ${BRIDGE_ADDRESS} \
--destination-network ${ROLLUP_ID} \
--private-key ${TEST_PRIVATE_KEY} \
--rpc-url ${L1_RPC_URL} \
--value $(cast to-wei 0.1)
```
2. **Verify the Deposit on L2**
Once the deposit is claimed on L2, you can verify it by checking the balance of the target address:
```bash theme={null}
cast balance ${TEST_ADDRESS} --ether --rpc-url=http://$(docker compose port op-geth 8545)
```
### L2 to L1
1. **Initiate a Deposit**
Start by making a deposit from L2 using the following command:
```bash theme={null}
polycli ulxly bridge asset \
--bridge-address ${BRIDGE_ADDRESS} \
--destination-network 0 \
--private-key ${TEST_PRIVATE_KEY} \
--rpc-url http://$(docker compose port op-geth 8545) \
--value $(date +%s) \
--destination-address ${TEST_ADDRESS}
```
2. **Wait for the Pessimistic Proof**
Once the deposit is made, a pessimistic proof will be generated. After this proof is available, the deposit becomes eligible for claiming.
3. **Verify the Deposit Status**
You can check the status of your deposit by querying the bridge service:
```bash theme={null}
curl -s http://$(docker compose port bridge 8080)/bridges/${TEST_ADDRESS} | jq '.'
{
"deposits": [
{
"leaf_type": 0,
"orig_net": 0,
"orig_addr": "0x0000000000000000000000000000000000000000",
"amount": "1746047601",
"dest_net": 0,
"dest_addr": "0xda9f3DCA867C4Bc7b1f8e2DB47Aa8C338Ba2e056",
"block_num": "14136",
"deposit_cnt": 0,
"network_id": 36,
"tx_hash": "0x4a5d4914e4ae315af941e1fad2b2aac54dfd6c9bc2ca6033e0ea0b325fbcd90e",
"claim_tx_hash": "",
"metadata": "0x",
"ready_for_claim": true,
"global_index": "150323855360"
}
],
"total_cnt": "1"
}
```
If `"ready_for_claim": true`, the deposit is ready to be claimed.
4. Claim the Deposit on L1
Run the following command to claim the deposit:
```bash theme={null}
polycli ulxly claim asset \
--bridge-address ${BRIDGE_ADDRESS} \
--bridge-service-url http://$(docker compose port bridge 8080) \
--deposit-count 0 \
--destination-address ${TEST_ADDRESS} \
--deposit-network ${ROLLUP_ID} \
--private-key ${TEST_PRIVATE_KEY} \
--rpc-url ${L1_RPC_URL}
```
5. Confirm the Claim
Finally, confirm that the deposit has been successfully claimed by checking the balance of the destination address:
```bash theme={null}
cast balance ${TEST_ADDRESS} --ether --rpc-url=${L1_RPC_URL}
```
***
Your PP network is now live.
# Going to Production
Source: https://docs.polygon.technology/chain-development/cdk/cdk-opgeth/going-to-production
Deploy a CDK chain to testnet or mainnet with support from implementation providers.
## Overview
Moving from a local devnet to a production CDK chain on testnet or mainnet requires infrastructure planning, bridge configuration, and AggLayer registration. Polygon's implementation providers handle this process end-to-end.
## Implementation providers
Two providers currently support production CDK deployments:
| Provider | Execution clients | What they handle |
| ----------------------------------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------ |
| [**Conduit**](https://conduitxyz.typeform.com/to/CrvgqEeA?utm_source=polygonannouncement\&utm_medium=partnerblog) | op-geth, op-reth | Full chain deployment, infrastructure management, monitoring, and ongoing operations |
| [**Gateway**](https://share.hsforms.com/1toN701PtTBCpyc3bKQBCRAcy6wj?referrer=12118007123) | op-geth, op-reth | Chain deployment, custom configurations, and managed infrastructure |
Both providers support all three [deployment modes](/chain-development/cdk/cdk-opgeth/architecture/): sovereign, validium, and zkrollup.
## Testnet deployment
Testnet deployments let you validate your chain configuration, bridge behavior, and application integrations before going to mainnet. Contact an implementation provider to set up a testnet environment. They will provision:
* L1 and L2 node infrastructure
* Bridge service and AggKit components
* AggLayer connectivity (testnet)
* Monitoring and alerting
## Mainnet deployment
Mainnet deployment follows the same process as testnet with additional requirements:
* **AggLayer registration:** Your chain must be registered with AggLayer through the [Polygon Support Portal](https://polygon.atlassian.net/servicedesk/customer/portal/22).
* **Bridge contract deployment:** L1 bridge contracts are deployed and verified on Ethereum mainnet.
* **Safe Proposer setup:** Rollup registration uses a Safe multisig proposer for governance.
Your implementation provider coordinates these steps as part of the deployment process.
## Self-operated chains
If you prefer to operate your own infrastructure, start with the [Quickstart](/chain-development/cdk/cdk-opgeth/local-guide/) and [Devnet Deployment Guide](/chain-development/cdk/cdk-opgeth/devnet-deployment-guide/) to understand the component architecture. For production self-operation, contact the Polygon team through the [Support Portal](https://polygon.atlassian.net/servicedesk/customer/portal/22) to discuss requirements.
# Quickstart
Source: https://docs.polygon.technology/chain-development/cdk/cdk-opgeth/local-guide
How to deploy a local cdk-opgeth testnet using Kurtosis, including L1, L2, Agglayer, and OP Stack components.
Use this guide to deploy a local testnet instance of `cdk-opgeth` using Kurtosis. This includes a local L1 + L2 environment with Agglayer components and OP Stack infrastructure.
***
## 1. Install Kurtosis
Follow the installation instructions from the [Kurtosis official docs](https://docs.kurtosis.com/install).
***
## 2. Launch the cdk-opgeth Stack
Use the command below to run the Kurtosis package:
```bash theme={null}
kurtosis run \
--enclave cdk \
--args-file https://raw.githubusercontent.com/0xPolygon/kurtosis-cdk/refs/tags/v0.4.0/.github/tests/chains/op-succinct.yml \
github.com/0xPolygon/kurtosis-cdk@v0.4.0
```
This will:
* Start an L1 devnet (Ethereum-like chain)
* Deploy Agglayer common contracts
* Deploy op-geth, op-node, and op-batcher
* Deploy Aggkit and op-succinct infrastructure
***
## 3. Bridge Funds from L1 to L2
Use `polycli` to bridge assets from L1 to L2:
```bash theme={null}
polycli ulxly bridge asset \
--bridge-address $(kurtosis service exec cdk contracts-001 'jq -r '.polygonZkEVMBridgeAddress' /opt/zkevm/combined.json') \
--private-key 0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625 \
--destination-address 0x9175f8176014543492234099F37a385335a017d6 \
--destination-network 1 \
--value 1000000000000000000 \
--rpc-url http://$(kurtosis port print cdk el-1-geth-lighthouse rpc)
```
***
**Command parameters:**
* `polycli ulxly bridge asset`: Initiates the bridge
* `--bridge-address`: Reads from `combined.json`
* `--private-key`: Specifies the sender wallet
* `--destination-address`: L2 recipient
* `--destination-network`: Target network ID (1 for devnet)
* `--value`: Amount to bridge in wei
* `--rpc-url`: Dynamically resolved RPC from Kurtosis
A successful call returns a transaction confirmation.
***
## 4. Check Balance on L2
```bash theme={null}
cast balance --ether \
--rpc-url $(kurtosis port print cdk op-el-1-op-geth-op-node-001 rpc) \
0x9175f8176014543492234099F37a385335a017d6
```
***
## 5. Send a Transaction on L2 (Inscription)
```bash theme={null}
cast send \
--rpc-url $(kurtosis port print cdk op-el-1-op-geth-op-node-001 rpc) \
--private-key 0xfa5f1cc57271a4ccbc5f0becd6bbca6a542973fa2b323d918c5e625fb67bdb20 \
0x9175f8176014543492234099F37a385335a017d6 \
$(echo -n 'data:,Hello Agglayer!' | xxd -p)
```
This command sends a transaction with `Hello Agglayer!` embedded in the calldata.
***
## 6. View the Inscription
```bash theme={null}
cast tx \
--rpc-url $(kurtosis port print cdk op-el-1-op-geth-op-node-001 rpc) \
0x2ec6e2097ef85360cdb1fde9d711412c4ce304a79da5afa69ef9abdbebd6757e input | \
xxd -r -p
```
Expected output:
```bash theme={null}
data:,Hello Agglayer!
```
# Why Choose CDK?
Source: https://docs.polygon.technology/chain-development/cdk/get-started/benefits
Key benefits of Polygon CDK, including EVM compatibility, Agglayer integration, rollup modes, and implementation support.
## EVM Compatibility
Chains built with the Polygon Chain Development Kit (CDK) are fully EVM-equivalent. Existing smart contracts can be deployed without modification, and standard Ethereum tooling works without reconfiguration.
## Performance
* 60 to 100+ Mgas/s throughput
* 20,000+ TPS when optimized for payment workloads
* Sub-60-minute finality in zkRollup and validium modes
CDK supports two execution clients: **op-geth** (Geth-based) and **op-reth** (Reth-based), giving operators flexibility in resource usage and performance tuning.
## Rollup Modes
Every CDK chain supports multiple rollup modes:
* **Sovereign**: No ZK prover required. Agglayer enforces security through pessimistic proofs. Default configuration.
* **Validium**: ZK-secured execution with offchain data availability. Lower cost and higher throughput than zkRollup.
* **zkRollup**: Fully onchain ZK rollup. Maximum security and Ethereum-aligned trust assumptions.
Operators select the mode that best balances cost, trust assumptions, and data availability for their use case.
## Agglayer Integration
All CDK chains connect to **Agglayer** by default. Cross-chain interoperability, unified liquidity, and shared state are native features of every enterprise chain, not add-ons that require additional infrastructure.
## Implementation Providers
For production deployments, CDK chains are typically launched with the support of an implementation provider:
* **Conduit** and **Gateway** both support **op-reth** and **op-geth** execution clients.
* Both providers are building open source tooling for CDK deployments.
Review common questions about execution clients, Agglayer integration, and deployment paths.
Contact Polygon about a dedicated enterprise chain and managed deployment support.
# What is CDK?
Source: https://docs.polygon.technology/chain-development/cdk/get-started/overview
Enterprise-grade toolkit for building custom Ethereum L2 chains. Agglayer interoperability is built in by default.
The Polygon Chain Development Kit (CDK) is an enterprise-grade toolkit for building custom Ethereum Layer 2 (L2) chains. Every CDK chain connects to **Agglayer** by default — cross-chain interoperability, shared liquidity, and unified state are native features of the infrastructure, not optional add-ons.
## Execution clients
CDK supports two execution clients, both maintained as open source by implementation providers:
* **op-geth**: Geth-based client with OP Stack architecture. Familiar developer environment and broad tooling support.
* **op-reth**: Reth-based client optimized for high throughput and lower resource consumption.
Both clients are supported by **Conduit** and **Gateway**, who are building open source tooling for production CDK deployments.
## Performance
* 60 to 100+ million gas per second (Mgas/s)
* Over 4,700 peak TPS in standard configuration
* **20,000+ TPS** when optimized for payment workloads
* Finality in under 60 minutes in zkRollup and validium modes
## Rollup modes
Every CDK chain supports three operating modes:
| Mode | Status | Description |
| ------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sovereign** | Live | Agglayer connectivity secured by pessimistic proofs. No prover required. Default configuration. |
| **Validium** | Live | ZK-secured execution with offchain data availability. Available today via OP Succinct AltDA; see [Privacy Configuration](/chain-development/cdk/privacy). |
| **zkRollup** | In development | Fully onchain ZK rollup for maximum security and Ethereum-aligned trust. |
## Agglayer integration
All CDK chains connect to Agglayer by default. This provides unified liquidity, shared state, and cross-chain messaging as a native feature of every enterprise chain, without additional bridging infrastructure.
## Projects using CDK
* Ternoa
* Merlin Chain
* Magic Labs (Newton)
* Silicon Network
* Witness Chain
* WireX
* Lumia (formerly Orion)
* Okto Wallet
* Palm Network
* Prom
* OKX
* Moonveil
*...and more*
Compare execution clients, operating modes, and implementation options.
Contact Polygon about a dedicated enterprise chain and managed deployment support.
# Overview
Source: https://docs.polygon.technology/chain-development/cdk/index
Deploy your own custom blockchain with built-in Agglayer interoperability. Enterprise-grade rollup infrastructure for payments, compliance, and dedicated chain rails.
Polygon Chain Development Kit (CDK) lets institutions deploy their own custom blockchain on production-ready rollup infrastructure. Every CDK chain connects to **Agglayer** by default, so cross-chain interoperability and unified liquidity are built into the infrastructure from day one — not a separate integration.
CDK provides enterprise-grade blockspace with predictable costs, privacy options, performance under load, and uptime guarantees, purpose-built for institutions that need compliance controls without sacrificing connectivity.
## Key capabilities
Every CDK chain connects to Agglayer by default. Cross-chain asset transfers and unified liquidity are native features — no additional bridging infrastructure required.
20,000+ TPS when optimized for payment workloads. 100+ Mgas/s throughput. Managed uptime SLA with lower maintenance complexity compared to Hyperledger Besu setups.
Deploy as fully private or with gated access and offchain data. API keys and access control lists let you filter read/write permissions and specific contract or RPC method calls.
Migration pathways for chains running Besu, preserving full data history without business disruption.
## Operating modes
| Mode | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sovereign** | Agglayer connectivity secured by pessimistic proofs. No prover required. Default configuration. |
| **Validium** | ZK-secured execution with offchain data availability. Available today via OP Succinct AltDA; see [Privacy Configuration](/chain-development/cdk/privacy). |
| **zkRollup** | Fully onchain zero-knowledge rollup for maximum security and Ethereum-aligned trust. |
## Get started
Review the architecture, execution stacks, and rollup modes before planning a deployment.
Contact Polygon about a dedicated enterprise chain and managed deployment support.
## Resources
Architecture, execution stacks, and technical capabilities.
Key benefits and why enterprises choose CDK over alternatives.
Frequently asked questions from developers and infrastructure providers.
Polygon CDK product overview on agglayer.dev.
# Privacy Configuration
Source: https://docs.polygon.technology/chain-development/cdk/privacy
Deploy a private Polygon CDK chain where transaction data stays in your infrastructure and Ethereum settles a validity proof. Built on OP Succinct AltDA.
Polygon CDK supports a privacy configuration for institutions that need to keep transaction data inside their own infrastructure while retaining Ethereum-anchored security and Agglayer interoperability. The chain runs as an OP Stack validium: raw transaction data is held in a data availability server the institution operates, and Ethereum sees only a Keccak256 commitment plus a SP1 Hypercube validity proof.
This configuration is intended for regulated institutions that want full control over where customer data lives while staying connected to the broader Ethereum ecosystem for liquidity and settlement.
## Stack At A Glance
The privacy configuration runs OP Stack in OP Succinct validium mode, with [SP1 Hypercube](https://blog.succinct.xyz/op-succinct-data-confidentiality/) handling proof generation and AltDA serving as the institution-operated data availability layer. Implementation partners like [Conduit](https://conduit.xyz) and [Gateway](https://gateway.fm) operate the stack as a service; institutions can also run sequencers themselves. The chain settles to Ethereum and connects to Agglayer for cross-chain liquidity and state.
## What This Configuration Delivers
**Customer data confidentiality.** Sensitive transaction data stays inside infrastructure the institution owns and operates. Regulators, auditors, and counterparties receive scoped access on the institution's terms.
**Self-hosted infrastructure.** Data availability runs behind the institution's existing security perimeter, in jurisdictions it has already cleared with compliance. There is no third-party dependency for data storage or retrieval.
**Global liquidity.** The chain stays connected to the Ethereum ecosystem through Agglayer, retaining access to stablecoins, tokenized assets, and counterparty capital across connected chains.
**Ethereum-anchored security.** Every chain transition is verified by a ZK validity proof settled to Ethereum, inheriting Ethereum's economic security for settlement.
## How It Works
The privacy configuration replaces the OP Stack's default L1 data availability with a Keccak256-commitment alt-DA flow. The pipeline runs in four stages:
1. **Batch submission to private DA.** When the sequencer produces a batch, `op-batcher` submits the batch data to the institution's data availability server using the standard OP Stack alt-DA HTTP API. `PUT /put` returns a Keccak256 commitment; `GET /get/0x{commitment}` retrieves the data.
2. **L1 anchoring.** The batcher posts an L1 transaction containing the `DerivationVersion1` prefix byte (`0x01`) followed by the 32-byte Keccak256 commitment. Raw transaction data never reaches a public network.
3. **Proof generation.** The OP Succinct proposer reads the L1 commitment, fetches the batch data from the DA server, and feeds it to SP1. Inside the zkVM, the proving program verifies `keccak256(data) == commitment` via the preimage oracle before executing the OP Stack derivation pipeline.
4. **Proof settlement.** The validity proof is posted to the chain's `OPSuccinctL2OutputOracle` contract on Ethereum, where it is verified against a range verification key and rollup config hash pinned at deployment.
```mermaid theme={null}
sequenceDiagram
participant Batcher as op-batcher
participant DA as Private DA server
participant L1 as Ethereum L1
participant Proposer as op-proposer
participant SP1 as SP1 zkVM
participant Oracle as L2 Output Oracle
Batcher->>DA: PUT /put (batch data)
DA-->>Batcher: Keccak256 commitment
Batcher->>L1: post 0x01 prefix + commitment
Proposer->>L1: read commitment
Proposer->>DA: GET /get/0x{commitment}
DA-->>Proposer: batch data
Proposer->>SP1: verify keccak256(data)==commitment, derive
SP1-->>Proposer: validity proof
Proposer->>Oracle: submit proof
Oracle->>Oracle: verify against pinned VK
```
## Trust Model
The privacy configuration splits trust between data integrity and data availability:
* **Data integrity is not trusted to the DA server.** SP1 verifies `keccak256(data) == commitment` inside the zkVM before accepting any data into the derivation pipeline. A malicious or compromised DA server cannot forge state.
* **Data availability is trusted to the DA server.** If the DA server loses data or refuses to serve it for a given commitment, no proof can be produced for the affected range. DA-layer redundancy, backup, and access control are the operator's responsibility.
For institutional deployments this is the intended trust split: the DA server sits inside the operator's perimeter precisely so the operator controls availability. Cryptographic integrity is enforced by the prover; operational availability is enforced by the institution's own infrastructure practices.
## Access Control
The privacy configuration is typically paired with chain-level access controls. These are operator-configurable surfaces; some require additional implementation by the operator or the rollup-as-a-service partner:
* **Gated RPC endpoints.** Custom RPC layers can be placed behind enterprise identity systems such as Okta, Azure AD, or any OIDC-compatible SSO.
* **Permissioned block explorers.** Block-explorer access can be restricted to authorized users.
* **Custom sequencer policies.** Sequencers can enforce KYC-gated mempools, allowlists, and other admission policies.
* **Contract-level access controls.** Application logic can enforce per-function and per-role access controls within each smart contract.
## Configuration Reference
The AltDA configuration is gated behind a Cargo feature and a single required environment variable:
* **Feature flag:** `altda` Cargo feature on the `validity` binary.
* **Environment variable:** `ALTDA_SERVER_URL` (no default; the deployment fails to start if unset).
* **Service name:** `op-succinct-altda`, with a separate `docker-compose-altda.yml` for the AltDA-mode stack.
* **Range ELF:** `altda-range-elf-embedded`, embedded at build time.
* **Deployment helpers:** `just deploy-oracle .env altda` and `just update-parameters .env altda`.
When deploying or upgrading, the range verification key, aggregation verification key, and rollup config hash must be regenerated with `--features altda`. Without this, `OPSuccinctL2OutputOracle` reverts with `ProofInvalid()` on proof submission.
## Limitations
The OP Succinct AltDA mode is marked experimental. Configuration keys, feature flags, and on-disk artifacts may change without notice across releases. Pin specific versions for production deployments, and treat the [OP Succinct AltDA documentation](https://succinctlabs.github.io/op-succinct/validity/experimental/altda.html) as the canonical source of truth.
Additional limitations to plan around:
* **Keccak256 commitments only.** The current implementation accepts only Keccak256 commitments. Generic commitments (the OP alt-DA `0x01` type byte) are explicitly rejected.
* **DA availability is operator-enforced.** The zkVM verifies that retrieved data matches the on-chain commitment, but it cannot force the DA server to serve data. Availability and censorship resistance are operator responsibilities.
* **No recovery past a missing commitment.** If the DA server loses or refuses to serve data for a given commitment, no proof can be produced for the affected range. There is no recovery path past a missing commitment.
* **Hardcoded 30-second HTTP timeout.** The DA fetch timeout is fixed in the current implementation and is not configurable.
* **Standard L1 head selection.** The proposer uses standard L1 head selection; there is no Blobstream-style finality tracking.
* **Components out of scope for OP Succinct.** The alt-DA server itself, generic-commitment encoding, and on-chain DA challenge/bonding logic are not provided by OP Succinct.
## Deployment Options
Three deployment patterns are supported:
* **Institution-operated.** The institution runs the full stack: sequencer, `op-node`, `op-batcher`, `op-proposer`, and the AltDA server.
* **Conduit-managed.** [Conduit](https://conduit.xyz) operates the full stack as a service, including the AltDA server, on behalf of the institution.
* **Gateway-managed.** [Gateway](https://gateway.fm) provides equivalent rollup-as-a-service operation of the stack.
## Settlement And Interop
Validity proofs are posted to the chain's `OPSuccinctL2OutputOracle` contract on Ethereum L1, where each proof is verified against a range verification key and rollup config hash pinned at deployment. The chain connects to Agglayer for cross-chain liquidity and state; see the [Agglayer documentation](/interoperability/agglayer) for cross-chain mechanics and integration patterns.
## Resources
Architecture, execution clients, rollup modes, and performance.
Canonical upstream reference for the AltDA mode used by this configuration.
Source code for the OP Succinct prover and AltDA integration.
Cross-chain liquidity and state for connected chains.
Announcement and positioning of the privacy configuration.
Technical write-up from Succinct on the AltDA shipment.
# Polygon Developer Docs
Source: https://docs.polygon.technology/index
The only open, full-stack payments infrastructure for global money movement.
PaymentsStablecoinsRWAs
Polygon Developer Docs
The open stack for global money movement.
Integrate compliant fiat access, stablecoin settlement, wallet infrastructure, interoperability, and blockchain rails with a single unified stack. Production-ready for institutions moving money at scale.
Open Money Stack
The modular components of the Open Money Stack span fiat & blockchain rails, wallet infrastructure, agentic payments, and interoperability.
Use Cases
The Open Money Stack is infrastructure for financial institutions that need to move money faster, reach more markets, and settle without the overhead of fragmented vendor stacks. These are the problems it solves:
Fund accounts and settle merchants on evenings, weekends, and holidays. Stablecoin rails don't stop when banks do.
Build global B2B payment corridors on licensed, compliant infrastructure, without assembling bank relationships from scratch.
Let users hold, move, and spend USD without a US bank account. Stablecoin-backed accounts that look like banking.
Pay contractors, sellers, and employees across dozens of countries in local fiat or USDC. No per-corridor integrations.
Accept stablecoin at checkout, settle in fiat. Near-zero fees, instant finality, and global reach for any merchant.
Power US-to-world money transfers with licensed corridors, debit and cash onramps, and stablecoin settlement under the hood.
Resources
}
href="https://github.com/0xPolygon"
>
Explore open-source repos, SDKs, and contracts across the Polygon ecosystem.
Check the current operational status of Polygon network services.
}
href="/tools/index"
>
SDKs, oracles, indexers, bridges, and security tools for building on Polygon.
# Architecture
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/architecture
AggKit's three-tier architecture: chain domain, synchronization components, and the Agglayer ecosystem, with certificate submission and GER propagation flows
## Overview
Traditional blockchain architecture assumes you're dealing with a single network. In the Agglayer ecosystem, you're dealing with multiple sovereign chains that each have their own block times, execution environments, and state management. AggKit provides the synchronization layer that keeps all of them coordinated.
## The Three-Tier Architecture
AggKit's architecture has three distinct tiers, each with a specific purpose:
### Tier 1: Your Chain's Domain
At the foundation, you have **your L2 chain** doing what it does best – processing transactions, executing smart contracts, maintaining state. This is your domain, where you have full sovereignty and control.
When users perform bridge operations on your chain, several things happen simultaneously: bridge contracts emit events, state gets updated, and your chain needs to communicate these changes to the broader ecosystem. This is where AggKit steps in.
### Tier 2: The AggKit Synchronization Layer
In the middle tier, AggKit components work together to maintain synchronization. Each component has a specialized role:
*Figure 1: The three-tier architecture – your chain, AggKit synchronization, and the broader ecosystem*
### Tier 3: The Unified Ecosystem
At the top tier, you have **the broader Agglayer ecosystem** – Agglayer itself, Ethereum L1, and all the other chains connected to the network. This is where the global state lives, where final settlement happens, and where the unified liquidity that makes everything possible is maintained.
## Data Flow Architecture
AggKit handles two directions of communication that keep chain state synchronized with Agglayer:
#### **Upward Flow: L2 → Agglayer**
**Purpose**: Submits L2 state transitions to Agglayer for validation and proof generation.
**Components Involved**:
* **BridgeSync**: Captures bridge events from L2 contracts
* **L1InfoTreeSync**: Provides L1 verification data and Merkle proofs
* **AggSender**: Packages data into signed certificates and submits to Agglayer
#### **Downward Flow: Agglayer → L2**
**Purpose**: Propagates global state updates from Agglayer/L1 to L2 chains for claim verification.
**Components Involved**:
* **L1InfoTreeSync**: Monitors L1 for Global Exit Root updates
* **AggOracle**: Propagates GER updates to L2 contracts (with v0.3.5 committee security)
* **L2GERSync**: Indexes and manages GER state locally on L2
## Component Interaction Patterns
### **Certificate Generation Pattern**
*Figure 2: Certificate generation and submission pattern*
### **Oracle Propagation Pattern**
*Figure 3: GER propagation with v0.3.5 committee security*
### **v0.3.5 Security Enhancements**
The major architectural improvement in v0.3.5 is the **elimination of single-address vulnerabilities**:
#### **Before v0.3.5: Single Point of Failure**
**Risk**: Single compromised address could steal funds or mint unauthorized assets.
#### **After v0.3.5: Distributed Security**
**Security**: Multiple parties must agree before any GER injection, eliminating single points of failure.
# AggchainProofGen
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/components/aggchain-proof-gen
How AggchainProofGen generates state transition proofs for chains requiring mathematical verification of internal operations beyond ECDSA signature authorization
## What AggchainProofGen Does
AggchainProofGen generates state transition proofs for chains that require mathematical verification of internal operations. While chains using a trusted sequencer model can use simple ECDSA signature authorization, some chains need comprehensive proofs that verify both internal operations and cross-chain bridge activities.
AggchainProofGen implements the chain-side component of the [State Transition Proof](/interoperability/agglayer/core-concepts/state-transition-proof/) system introduced in Agglayer v0.3. It verifies the chain's consensus mechanism, verifies bridge constraints, and produces an Aggchain Proof that AggSender includes in its certificate.
## When AggchainProofGen Is Needed
AggchainProofGen is required for chains that use `CONSENSUS_TYPE = 1` (generic validity proof) rather than `CONSENSUS_TYPE = 0` (ECDSA signature). This applies to:
* **Zero-knowledge rollups**: Chains that generate zk-SNARKs or zk-STARKs for state transitions
* **Custom consensus chains**: Chains with consensus mechanisms that cannot be authorized by a single trusted sequencer address
* **High-security deployments**: Chains where mathematical certainty about state correctness is required rather than trusted-party authorization
Chains using ECDSA authorization do not need AggchainProofGen and can operate with AggSender alone.
## How AggchainProofGen Works
AggchainProofGen implements a dual verification process:
**Step 1: Consensus verification.** AggchainProofGen verifies the chain's consensus: either validating an ECDSA signature from the trusted sequencer, or verifying a validity proof using the SP1 zkVM.
**Step 2: Bridge constraint verification.** AggchainProofGen verifies that all bridge operations comply with the Unified Bridge security constraints. This includes GER hash chain validation, claims hash chain validation, Local Exit Root correctness, and GER Merkle proof inclusion in the L1 Info Root.
**Step 3: Proof generation.** AggchainProofGen generates an Aggchain Proof combining both verifications. AggSender includes this proof in the certificate it submits to Agglayer.
## Proof Generation Modes
### ECDSA Mode
For chains with trusted sequencer models, AggchainProofGen verifies operations using ECDSA signature validation. This provides fast verification while maintaining compatibility with existing chain architectures.
### Validity Proof Mode
For chains requiring mathematical certainty, AggchainProofGen generates validity proofs using zero-knowledge virtual machines. This provides cryptographic certainty about the correctness of both internal operations and bridge activities, without relying on a trusted party.
## Integration with AggSender
When a chain uses AggchainProofGen, AggSender operates in AggchainProver mode:
For the full context of how Aggchain Proof fits into the verification pipeline, see [State Transition Proof](/interoperability/agglayer/core-concepts/state-transition-proof/) and [Aggchain Proof](/interoperability/agglayer/core-concepts/state-transition-proof/aggchain-proof/).
# AggOracle
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/components/aggoracle
How AggOracle propagates Global Exit Root updates from Ethereum L1 to L2 chains, and the v0.3.5 committee mode security improvement
## What AggOracle Does
AggOracle propagates Global Exit Root (GER) updates from Ethereum L1 to the L2 chain's Global Exit Root Manager contract. This synchronization keeps the L2 chain's local copy of global state current, which is required for verifying incoming cross-chain claims.
When a user bridges assets from another chain to your chain and submits a claim, your chain verifies the claim against the current GER. Without a current GER, the verification fails and claims cannot be processed.
## The Synchronization Problem
Cross-chain claim verification requires L2 chains to have current GER data from Ethereum L1. The two chains operate independently with different block times and finality requirements, so the L2 cannot query L1 directly on demand.
AggOracle resolves this by implementing a pull-based synchronization mechanism: it continuously monitors L1 for GER changes and injects updates into the L2 contract whenever a new GER is detected.
## The Two Operating Modes
### Direct Injection Mode (Pre-v0.3.5)
In the original design, AggOracle used single-address authorization for GER injection. One designated address had the authority to call `insertGlobalExitRoot()` on the L2 Global Exit Root Manager contract.
**Vulnerability**: If the single private key is compromised, an attacker could inject invalid GERs, causing the chain to accept fraudulent bridge claims.
### Committee Mode (v0.3.5)
v0.3.5 introduces multi-party consensus. Multiple independent AggOracle instances must agree before any GER injection occurs.
**How it works:**
1. Multiple AggOracle committee members independently monitor L1 for GER updates
2. When a new GER is detected, one member proposes it to the committee contract
3. Other members validate and vote by proposing the same GER
4. The committee contract automatically injects the GER when the threshold quorum is reached
**Security improvement**: Even if some committee members are compromised, the system remains secure because multiple independent parties must reach consensus before any state update occurs.
## Full GER Propagation Workflow
## Understanding Global Exit Roots
A [Global Exit Root (GER)](/interoperability/agglayer/core-concepts/unified-bridge/data-structures/#global-exit-root) is a cryptographic hash that represents the current state of all cross-chain bridge activities across the Agglayer ecosystem:
```
GER = hash(RollupExitRoot, MainnetExitRoot)
```
Where:
* **RollupExitRoot**: Aggregated root of all L2 chains' Local Exit Roots
* **MainnetExitRoot**: Root of all Ethereum L1 bridge transactions
Cross-chain claims require proof verification against the current GER. When users submit bridge claims to your chain, the claim proofs must reference a GER that your chain has recorded as valid.
# AggSender
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/components/aggsender
How AggSender packages L2 state transitions into cryptographically signed certificates and submits them to Agglayer for Pessimistic Proof generation
## What AggSender Does
AggSender is the component responsible for submitting L2 state transitions to Agglayer. It collects bridge events and L1 verification data, packages them into a signed certificate, and submits that certificate to Agglayer on each epoch. Agglayer then uses the certificate to generate a Pessimistic Proof and validate the state transition.
Without AggSender, an L2 chain cannot participate in Agglayer's security guarantees or cross-chain coordination. Every chain connected to Agglayer must run AggSender.
## The Trust Problem AggSender Solves
Agglayer connects multiple independent blockchains, and any of them could theoretically be compromised. The Pessimistic Proof system limits the damage a compromised chain can do, but it needs reliable input data to work correctly.
AggSender provides that input. Rather than simply reporting what happened on a chain, it creates a signed certificate that contains:
* Cryptographic proof that bridge transactions occurred
* Evidence that the chain has sufficient funds to back those transactions
* Mathematical verification data that the chain's state transitions are valid
* A digital signature committing to all of the above
This allows Agglayer to verify the submission rather than trust it.
## Certificate Generation
### Epoch-Based Operation
AggSender submits certificates on Agglayer epochs rather than per-transaction. On each epoch, it collects all bridge activity since the last certificate, packages it, and submits once. This batching is more efficient than per-transaction submissions and provides comprehensive context about the chain's activities.
### Certificate Lifecycle
### Certificate Structure
```go theme={null}
type Certificate struct {
NetworkID uint32 // L2 network identifier
Height uint64 // Certificate sequence number
PrevLocalExitRoot common.Hash // Previous Local Exit Root
NewLocalExitRoot common.Hash // New Local Exit Root
BridgeExits []BridgeExit // Outgoing bridge transactions
ImportedBridgeExits []ImportedBridgeExit // Incoming bridge claims
Signature []byte // Cryptographic signature
Metadata []byte // Additional chain-specific data
}
```
## Full Certificate Submission Flow
*Note: AggchainProofGen is only involved when the chain uses the advanced state transition proof system. Chains using basic ECDSA authorization do not require AggchainProofGen.*
# BridgeSync
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/components/bridge-sync
How BridgeSync monitors and indexes bridge events from L1 and L2 networks, and how it provides data to AggSender and Bridge Service
## What BridgeSync Does
BridgeSync monitors bridge contracts on both L1 and L2 networks, captures every bridge-related event, and organizes the data into a local database. It provides the data foundation that other AggKit components depend on: AggSender uses it for certificate construction, and Bridge Service uses it to serve API responses.
**Key responsibilities:**
* **Event monitoring**: Real-time monitoring of bridge contract events on L1 and L2
* **Data indexing**: Comprehensive indexing of bridge and claim transactions
* **State management**: Maintains bridge transaction history and status
* **Reorg handling**: Manages blockchain reorganizations and maintains data integrity
* **API support**: Provides data to Bridge Service APIs
## Why Indexing Matters
Bridge claim verification and status queries require access to bridge transaction history. Without an index, each query would require expensive, slow queries to blockchain nodes or onchain contract calls. BridgeSync captures events as they occur and makes them instantly queryable.
AggSender depends on BridgeSync's data to build certificates: it needs the list of bridge exits and imported bridge exits for each epoch. Bridge Service depends on it to respond to transaction status requests and proof generation queries.
## Architecture
## How BridgeSync Works
### Event Processing Workflow
### Bridge Event Types
#### BridgeEvent
Emitted when assets or messages are bridged from a network:
```solidity theme={null}
event BridgeEvent(
uint8 leafType, // 0 = asset, 1 = message
uint32 originNetwork, // Source network ID
address originAddress, // Sender address
uint32 destinationNetwork, // Destination network ID
address destinationAddress, // Recipient address
uint256 amount, // Amount (for assets)
bytes metadata, // Additional data
uint32 depositCount // Index in Local Exit Tree
);
```
#### ClaimEvent
Emitted when assets or messages are claimed on a network:
```solidity theme={null}
event ClaimEvent(
uint256 globalIndex, // Global transaction index
uint32 originNetwork, // Source network ID
address originAddress, // Original sender
address destinationAddress, // Claim recipient
uint256 amount // Claimed amount
);
```
### Data Processing Pipeline
## Integration with Other Components
### AggSender Integration
BridgeSync provides the bridge exit and imported bridge exit data that AggSender packages into certificates:
**Data provided:**
* Bridge exits (outbound transactions from the L2)
* Imported bridge exits (inbound claims to the L2)
* Transaction proofs and metadata
* Block range information for certificate scope
### Bridge Service Integration
BridgeSync is the primary data source for Bridge Service API endpoints:
All Bridge Service endpoints query BridgeSync's local database for transaction data.
# Components Overview
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/components/index
The 7 AggKit components, their roles, dependencies, and which combinations are appropriate for different deployment scenarios
## Modular Design
AggKit consists of 7 specialized components. Rather than a single monolithic service, each component handles a specific responsibility. This means you deploy only the components your use case requires.
* **Basic Agglayer connectivity**: 2 components (AggSender + AggOracle)
* **Full bridge infrastructure**: 5 components (add BridgeSync, L1InfoTreeSync, L2GERSync)
* **Bridge API services**: 4 components (BridgeSync, L1InfoTreeSync, L2GERSync, Bridge Service)
* **Advanced state transition proofs**: Add AggchainProofGen
## Communication Components
These two components are required for any chain connecting to Agglayer.
### AggSender
AggSender packages L2 state transitions into cryptographically signed certificates and submits them to Agglayer. On each Agglayer epoch, it collects bridge events from BridgeSync and L1 verification data from L1InfoTreeSync, builds a certificate containing bridge exits and imported bridge exits, signs it, and submits it to Agglayer for Pessimistic Proof generation.
Without AggSender, a chain cannot participate in Agglayer's security guarantees or cross-chain coordination.
### AggOracle
AggOracle monitors the Global Exit Root contract on Ethereum L1, detects new GER updates, and injects those updates into the L2 chain's Global Exit Root Manager contract. This keeps the chain's local copy of global state current, which is required for verifying incoming cross-chain claims.
In v0.3.5, AggOracle operates in committee mode: multiple independent instances must agree on a new GER before it is injected into the L2 contract, eliminating the single-address vulnerability of earlier versions.
## Synchronization Components
These components maintain the data that AggSender and AggOracle depend on.
### L1InfoTreeSync
L1InfoTreeSync monitors Ethereum L1 and maintains two Merkle trees locally:
* **L1 Info Tree**: An append-only tree of historical Global Exit Roots, sourced from `UpdateL1InfoTree` events on the Global Exit Root contract
* **Rollup Exit Tree**: A tree of L2 Local Exit Roots, sourced from `VerifyBatches` events on the Rollup Manager contract
These trees provide the Merkle proofs that AggSender includes in certificates and that AggOracle uses for GER detection.
### L2GERSync
L2GERSync indexes GER injections on the L2 side. When AggOracle writes a new GER to the L2 Global Exit Root Manager contract, L2GERSync captures the event and stores it locally. This local index enables fast GER lookups during claim verification and API responses without requiring onchain queries.
### BridgeSync
BridgeSync monitors bridge contracts on both L1 and L2, captures every `BridgeEvent` and `ClaimEvent`, and organizes them into a local database. It provides the bridge exit and imported bridge exit data that AggSender uses for certificate construction, and the transaction history that Bridge Service exposes through its API.
## Service Components
These components extend AggKit with external-facing capabilities.
### Bridge Service
Bridge Service exposes a REST API over BridgeSync's data. It provides endpoints for bridge transaction status, token mappings, and Merkle proof generation. Applications such as wallets, block explorers, and DeFi protocols use Bridge Service to query bridge data without running the full AggKit infrastructure.
### AggchainProofGen
AggchainProofGen generates state transition proofs for chains that require mathematical verification of internal operations rather than simple ECDSA signature authorization. It verifies the chain's consensus (either ECDSA signature or validity proof), verifies bridge constraints, and produces an Aggchain Proof that AggSender includes in its certificate. See [State Transition Proof](/interoperability/agglayer/core-concepts/state-transition-proof/) for the full context of when this is needed.
## Component Dependencies
## Individual component pages
Certificate generation, epoch-based operation, and the certificate lifecycle.
GER propagation, direct injection vs. committee mode, and the v0.3.5 security change.
Bridge event monitoring, data indexing, event types, and integration with AggSender and Bridge Service.
The two Merkle trees maintained, event sources, Merkle proof generation, and integration with AggSender and AggOracle.
Local GER state management on the L2 side, indexing workflow, and how it supports claim verification.
State transition proof generation for chains requiring mathematical verification of internal operations.
# L1InfoTreeSync
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/components/l1infotree-sync
How L1InfoTreeSync monitors Ethereum L1 and maintains the L1 Info Tree and Rollup Exit Tree for proof generation and certificate building
## What L1InfoTreeSync Does
L1InfoTreeSync monitors Ethereum L1 and maintains two local Merkle trees that other AggKit components use for proof generation and certificate building:
1. **L1 Info Tree**: An append-only tree tracking historical Global Exit Root updates from the Global Exit Root contract
2. **Rollup Exit Tree**: An updatable tree tracking rollup state submissions from the Rollup Manager contract
Different AggKit operations need different types of L1 data. The L1 Info Tree provides historical GER context for claim verification proofs. The Rollup Exit Tree provides rollup state data for certificate construction.
**Key responsibilities:**
* Maintaining the L1 Info Tree and Rollup Exit Tree from L1 events
* Generating Merkle proofs for cross-chain verification
* Handling blockchain reorganizations and maintaining data integrity
* Respecting configurable finality requirements (latest, safe, finalized)
## Why L1 State Indexing Matters
Cross-chain operations require accurate L1 state for several functions:
1. **Proof generation**: Merkle proofs must reference correct historical L1 states
2. **Certificate building**: AggSender needs L1 data to construct valid certificates
3. **Claim verification**: Cross-chain claims must be verified against settled L1 state
4. **Reorg handling**: L1 reorganizations must be detected and handled
Rather than querying L1 on demand for each operation, L1InfoTreeSync maintains a local index that makes proof generation fast and efficient.
## Architecture
## How L1InfoTreeSync Works
### Event Processing
L1InfoTreeSync monitors two types of events from Ethereum L1 and processes them into separate tree structures:
### The Two Tree Types
**L1 Info Tree (append-only):**
* **Source**: `UpdateL1InfoTree` events from the Global Exit Root contract
* **Structure**: Append-only tree that grows with each Global Exit Root update
* **Purpose**: Provides historical GER data for claim verification
* **Use case**: When claim proofs must demonstrate inclusion in a specific historical state
**Rollup Exit Tree (updatable):**
* **Source**: `VerifyBatches` events from the Rollup Manager contract
* **Structure**: Updatable tree where L2 chains can update their submitted state
* **Purpose**: Tracks which L2 chains have submitted state and their current exit roots
* **Use case**: When AggSender builds certificates that need rollup state context
### Tree Structures
#### L1 Info Tree
#### Rollup Exit Tree
## Integration with Other Components
### AggSender Integration
L1InfoTreeSync provides the L1 data that AggSender includes in certificates:
**Data provided:**
* Current L1 Info Tree root and leaf data
* Merkle proofs for imported bridge exits
* L1 block finality information
* Historical Global Exit Root data
### AggOracle Integration
L1InfoTreeSync feeds GER detection into the AggOracle pipeline:
# L2GERSync
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/aggkit/components/l2ger-sync
How L2GERSync indexes Global Exit Root updates on the L2 side and provides fast local GER state access for claim verification and Bridge Service APIs
## What L2GERSync Does
L2GERSync manages Global Exit Root synchronization on the L2 side. When AggOracle injects a new GER into the L2 Global Exit Root Manager contract, L2GERSync captures the resulting event and stores the GER data in a local index.
This local index provides fast access to current and historical GER state for several operations:
1. **Claim verification**: Bridge claims must be verified against the current GER
2. **Proof generation**: Merkle proofs require accurate GER state
3. **API responses**: Bridge Service APIs need fast GER data access
4. **Historical queries**: Applications may need access to historical GER transitions
Without L2GERSync, each of these operations would require onchain queries or external service calls, adding latency and external dependencies.
## Architecture
## How L2GERSync Works
### GER Synchronization Workflow
L2GERSync automatically detects your contract's capabilities at startup and configures itself for compatibility with the deployed version of the L2 GER Manager contract.
# Architecture
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/architecture
Agglayer's technical architecture: the Agglayer node, Pessimistic Proof, Unified Bridge, and State Transition Proof and how they coordinate
## Overview
Agglayer provides a cross-chain interoperability framework built around four main components: the Agglayer node, the Pessimistic Proof, the Unified Bridge, and the State Transition Proof. Together they enable secure, verifiable cross-chain transactions between heterogeneous blockchain networks.
## High-Level Architecture
*Figure 1: Agglayer Overall Data Flows - showing transaction sequencing, settlement, and L1-L2 synchronization processes*
## Core Components
### 1. Agglayer Node
The Agglayer Node is a Rust-based service responsible for processing and verifying zero-knowledge (ZK) proofs from chains connected to the Agglayer.
**Key Functions:**
* **Zero-knowledge proof verification**: The node receives and verifies cryptographic proofs from connected chains before sending them to L1
* **Certificate management**: Handles certificates that attest to the state transitions of connected chains
* **Orchestration of epochs**: Manages state updates in a structured manner through epochs
### 2. Pessimistic Proof
The pessimistic proof mechanism ensures that any withdrawal claims made to the Agglayer are backed by legitimate deposits in the Unified Bridge. It uses a novel zero-knowledge proof system implemented in Rust, leveraging the SP1 zkVM and the Plonky3 proving system.
**Key Functions:**
* **Security validation**: Ensures that each chain connected to the Agglayer remains as secure as if it were operating independently
* **State consistency**: Provides a complete view of all token and message transfers occurring across the Agglayer
* **Fraud prevention**: Prevents chains from withdrawing more assets than they have legitimately received
### 3. Unified Bridge
The unified bridge is responsible for maintaining the data structures related to chain states, cross-chain transactions, and the Agglayer's Global Exit Root, ensuring cross-chain transactions are indeed finalized on the L1 before they can be claimed.
**Key Functions:**
* **Cross-chain asset transfers**: Allows users to bridge assets between different chains
* **Message passing**: Enables contract-to-contract interactions across chains
* **State management and accounting**: Maintains Merkle proofs that ensure transactions are finalized before being processed on the destination chain
### 4. State Transition Proof
The State Transition Proof is a two-layer verification system that validates both individual chain operations and cross-chain transfers before Agglayer accepts a state update.
**How it works:**
**State Transition Proof (Validity Proof)**: This layer verifies that each chain's internal state transitions are valid. Every operation within the chain is verified against the chain's execution rules, and the chain's resulting state must be consistent. Additional verification types can be added in the future without changing Agglayer's external interface.
**Cross-Chain Verification (Aggchain Proof and Pessimistic Proof)**: This layer verifies that cross-chain operations, such as asset transfers between chains, are valid. It ensures that when assets move between chains, operations are atomic and secure.
**Key Functions:**
* **End-to-end security**: A transaction is finalized only when both its internal validity proof and its cross-chain proof are accepted
* **Atomic cross-chain execution**: Guarantees that assets and messages move between chains in a single, indivisible step
* **Modular extensibility**: New proof mechanisms (optimistic, fraud, etc.) can be integrated without altering Agglayer's external interface
# Architecture Overview
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/pessimistic-proof/architecture
How Pessimistic Proofs are generated and validated, and the financial isolation guarantees they provide
## Overview
The Pessimistic Proof is a security mechanism in Agglayer that prevents compromised chains from draining funds beyond their deposits. It enforces a financial firewall between chains so that security issues on one chain cannot spread to the rest of the network.
*Figure 1: Complete Pessimistic Proof generation and validation flow*
## How Pessimistic Proofs Work
### Step 0: Local Chain Preparation
The local chain prepares data and sends it to Agglayer:
* **Initial Network State**: The complete state of the local chain before any state transition occurs, including the Local Exit Tree (recording outbound transactions), Local Balance Tree (tracking token balances), and Nullifier Tree (tracking claimed inbound transactions). This represents the baseline state that will be modified.
* **Bridge Exits**: Assets and messages being sent to other chains from the local chain, represented as a vector of BridgeExit structures containing destination information, token details, amounts, and metadata. These represent the outbound state changes that will update the Local Exit Tree and decrease Local Balance Tree balances.
* **Imported Bridge Exits**: Assets and messages being claimed to the local chain from other chains, represented as ImportedBridgeExit structures with cryptographic proofs demonstrating their validity. These represent inbound state changes that will update the Nullifier Tree and increase Local Balance Tree balances.
### Step 1: Agglayer Client Data Population
Agglayer Client populates the `MultiBatchHeader` using the `Certificate` data:
* **Target**: Expected transitioned local chain state (`StateCommitment`) that represents what the new state should look like after applying all the bridge exits and imported bridge exits. This serves as the verification target that the computed state must match.
* **Batch Header**: Packaged data with authentication information including previous state roots, all state transition data, balance proofs for affected tokens, cryptographic signatures, and the target state commitment. This comprehensive package contains everything needed for proof generation.
### Step 2: Native Rust Execution
Before running expensive zkVM computation, Agglayer runs the Pessimistic Proof Program in native Rust:
```rust theme={null}
// Compute new transitioned state
let new_state = compute_state_transition(initial_network_state, batch_header);
// Compare with expected state
if new_state == batch_header.target {
return Ok(PessimisticProofOutput);
} else {
return Err(InvalidStateTransition);
}
```
**Process:**
1. Compute new transitioned state using initial state and batch header by applying all bridge exits (reducing balances, updating exit tree) and imported bridge exits (increasing balances, updating nullifier tree) to generate the new Local Balance Tree, Nullifier Tree, and Local Exit Tree roots.
2. Compare computed state with expected state in `batch_header.target` to ensure that the chain's proposed state transition matches the mathematically computed result, validating that the chain is not attempting invalid operations like spending more than available balances.
3. If equal, data is valid and state transition is correct, meaning the chain has provided legitimate state transition data that respects balance constraints and doesn't attempt double-spending or other invalid operations.
4. Return `PessimisticProofOutput` containing the verified state transition data, or error code if validation fails, ensuring that only mathematically valid state transitions can proceed to zkVM proof generation.
### Step 3: zkVM Proof Generation
If native execution passes, run the same program in zkVM:
* **SP1 Prover Network**: Agglayer uses Succinct's SP1 Prover Network for faster, distributed proof generation. GPU acceleration and optimized precompiles handle the Keccak-heavy computation profile of Pessimistic Proof programs efficiently.
* **Same Inputs**: Identical program and inputs as native execution to ensure that the zkVM proof verifies exactly the same computation that was validated in native Rust, maintaining consistency between validation and proof generation phases.
* **Proof Generation**: Creates cryptographic proof of correct execution that can be verified by anyone without re-executing the program, providing mathematical certainty that the state transition was computed correctly according to the Pessimistic Proof rules.
### Step 4: Proof Validation
Agglayer validates the zk proof returned from the Prover Network:
* **Proof Verification**: Verify the cryptographic proof locally using the SP1 verifier to ensure that the proof is mathematically valid and that it corresponds to the expected program execution with the correct inputs and outputs.
* **Result Acceptance**: Accept pessimistic proof result if verification passes, confirming that the chain's proposed state transition is mathematically valid and respects all balance and security constraints enforced by the Pessimistic Proof program.
* **State Commitment**: Update network state based on verified proof by accepting the new state roots and allowing the chain to proceed with its state transition, enabling subsequent bridge operations to build on the verified state.
## Security Guarantees
### Financial Isolation
Each chain effectively has a financial "blast radius" limited to its own deposits:
* **Deposit Limit**: Compromised chains cannot drain more than their current deposits because the Pessimistic Proof program mathematically enforces that outbound bridge exits cannot exceed the available token balances in the Local Balance Tree, creating a hard mathematical constraint on fund drainage.
* **Containment**: Security issues cannot spread to other chains because each chain's state is validated independently through its own Pessimistic Proof, and the proof verification ensures that compromised chains cannot affect the balance trees or state transitions of other chains in the network.
* **Risk Isolation**: Each chain's risk is isolated from the broader ecosystem through the financial "blast radius" concept, where the maximum possible loss from any single chain compromise is limited to the assets currently deposited on that specific chain, protecting the overall network.
### State Transition Verification
* **Mathematical Verification**: All state transitions are cryptographically verified through zkVM proof generation that creates mathematical certainty about the correctness of balance updates, nullifier tree modifications, and exit tree changes, preventing any invalid state modifications.
* **Proof Requirements**: State changes require valid pessimistic proofs generated through the complete validation pipeline (native execution + zkVM proof generation + verification), ensuring that only mathematically sound state transitions are accepted by the network.
* **Consensus Protection**: Invalid proofs are rejected at multiple stages (native execution failure, zkVM proof generation failure, or proof verification failure), maintaining system integrity by preventing any invalid state transitions from being accepted into the network state.
### Network Protection
* **Ecosystem Safety**: Broader network remains secure even with individual chain compromises because the Pessimistic Proof system isolates each chain's financial impact and prevents compromised chains from affecting the balance trees, state transitions, or security of other chains in the network.
* **Continued Operation**: Other chains continue operating normally during individual chain compromises because each chain's Pessimistic Proof validation is independent, and the failure or compromise of one chain doesn't block or affect the proof generation and validation processes of other chains.
* **Trust Boundaries**: Clear trust boundaries between different chains are established through separate Local Balance Trees, independent proof generation, and isolated state validation, ensuring that trust assumptions about one chain don't extend to or affect other chains in the network.
## Performance Characteristics
Pessimistic Proof computation is primarily focused on state transition verification:
* **75%+ Keccak Operations**: Most computation involves Keccak hash functions used for Merkle tree operations, making Keccak optimization through precompiles and hardware acceleration critical for overall performance of the Pessimistic Proof generation process.
* **Merkle Tree Updates**: Efficient updates to Local Balance and Nullifier trees using Sparse Merkle Tree algorithms that only modify affected branches, significantly reducing computation compared to full tree reconstruction while maintaining cryptographic integrity.
* **zkVM Optimization**: Performance varies significantly across different zkVM implementations based on their Keccak precompile efficiency, GPU acceleration support, and CPU vectorization capabilities, with SP1 chosen for production due to optimal GPU performance and prover network infrastructure.
*Figure 2: Execution profile showing Keccak hash dominance in computation*
# Benchmarks
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/pessimistic-proof/benchmarks
Experimental performance analysis and benchmarks across different zkVM implementations
## Overview
For experimental research and performance analysis of Pessimistic Proof across different zkVM implementations, comprehensive benchmarks have been conducted comparing SP1, RiscZero, Pico, OpenVM, and other zkVMs.
**Note**: These benchmarks are for research purposes only. **Production Agglayer uses SP1 and Succinct's Prover Network exclusively.**
## Benchmark Repository
For detailed performance analysis, benchmark results, and implementation comparisons across different zkVMs, visit the dedicated benchmark repository:
**[Agglayer Pessimistic Proof Benchmarks](https://github.com/BrianSeong99/Agglayer_PessimisticProof_Benchmark/)**
## Repository Contents
The benchmark repository includes:
* **Performance comparisons**: Cycle counts and execution times across zkVMs
* **Implementation details**: How Pessimistic Proof runs on different zkVMs
* **Benchmark results**: Data tables and performance graphs
* **Setup instructions**: How to run benchmarks locally
* **Technical analysis**: Detailed breakdown of computation profiles
## Key Insights
Based on the benchmark research:
* **Keccak Dominance**: 75%+ of computation involves Keccak hash functions
* **Performance Variation**: Significant differences between zkVM implementations
* **Hardware Impact**: GPU acceleration and CPU optimizations affect performance
* **Production Choice**: SP1 chosen for optimal GPU performance and reliability
# Data Structures
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/pessimistic-proof/data-structures
Reference for the Sparse Merkle Trees and data structures used by Pessimistic Proof: Local Balance Tree, Nullifier Tree, Bridge Exits, MultiBatchHeader, and Certificate
## Overview
Pessimistic Proof computes state transitions between bridging events using three main Sparse Merkle Trees and several supporting data structures.
**Core components:**
* **Sparse Merkle Trees**: Local Exit Tree, Nullifier Tree, Local Balance Tree
* **State transitions**: Bridge Exits, Imported Bridge Exits
* **State representations**: Local State, Multi Batch Header, Proof Output
## Unified Bridge Foundation
Pessimistic Proof builds on top of the Unified Bridge data structure. For complete understanding, refer to the [Unified Bridge Data Structures](/interoperability/agglayer/core-concepts/unified-bridge/data-structures/).
**Key Unified Bridge Components:**
* **Local Exit Tree**: Records outgoing cross-chain transactions as cryptographic commitments in a 32-level Sparse Merkle Tree, with each leaf representing a hash of bridge transaction details including destination, amount, and metadata.
* **Global Exit Root**: Combines all chain states for verification by computing `hash(RollupExitRoot, MainnetExitRoot)`, providing a single unified root that represents the complete state of cross-chain activities across the entire network.
* **Global Index**: Unique 256-bit reference for transactions within Global Exit Root, encoding the source network type (mainnet flag), rollup identifier, and local transaction index to enable precise transaction location across the hierarchical tree structure.
*Figure 1: Unified Bridge data structure foundation*
## Local Balance Tree & TokenInfo
The Local Balance Tree tracks all token balances on a chain using a 192-bit depth Sparse Merkle Tree.
### TokenInfo Structure
```rust theme={null}
pub struct TokenInfo {
/// Network which the token originates from
pub origin_network: NetworkId,
/// The address of the token on the origin network
pub origin_token_address: Address,
}
```
### Key Layout
The `TokenInfo` key uses a clever bit layout for efficient storage and lookup:
* **First 32 bits**: Origin network ID where the token originally exists, enabling the system to track tokens across multiple chains while maintaining their original identity and preventing confusion between tokens with the same address on different chains.
* **Next 160 bits**: Token address on the origin chain (standard Ethereum address size), ensuring that each token can be uniquely identified by combining its origin network and original contract address, even when wrapped versions exist on other chains.
### Balance Updates
When assets are bridged out or claimed, the token balance in the Local Balance Tree is updated accordingly through atomic operations that ensure balance conservation and prevent overdraft conditions. Outbound bridging decreases the balance while inbound claiming increases it, with all changes verified through Merkle proof validation.
*Figure 2: Local Balance Tree structure showing token balance tracking*
## Nullifier Tree
The Nullifier Tree prevents double-spending and ensures transaction uniqueness across the network. Each chain maintains its own 64-bit depth Sparse Merkle Tree.
### Key Structure
The Nullifier Tree key is constructed using a 64-bit identifier that uniquely identifies each claimable transaction:
* **First 32 bits**: Network ID of the chain where the transaction originated, enabling the system to track which source chain a claimed transaction came from and prevent confusion between transactions from different networks.
* **Last 32 bits**: Index of the bridge exit within the Local Exit Tree of the source chain (also called Local Index or depositCount), providing the exact position of the transaction within the source chain's bridge transaction history.
### Double-Spending Prevention
*Figure 3: Nullifier Tree structure preventing double-spending*
## Bridge Exits
Bridge Exits represent outbound transactions from a chain.
### Structure
```rust theme={null}
pub struct BridgeExit {
/// Enum, 0 is asset, 1 is message
pub leaf_type: LeafType,
/// Unique ID for the token being transferred
pub token_info: TokenInfo,
/// Network which the token is transferred to
pub dest_network: NetworkId,
/// Address which will own the received token
pub dest_address: Address,
/// Token amount sent
pub amount: U256,
/// PermitData, CallData, etc.
pub metadata: Vec,
}
```
### Usage
All outbound transactions from a chain are represented in a `BridgeExit` vector during pessimistic proof generation. Each `BridgeExit` contains complete transaction information needed to validate that the chain has sufficient balance for the outbound transfer and to update the Local Exit Tree with the new transaction commitment.
## Imported Bridge Exits
Imported Bridge Exits represent inbound transactions to a chain.
### Structure
```rust theme={null}
pub struct ImportedBridgeExit {
/// The bridge exit from the source network
pub bridge_exit: BridgeExit,
/// The claim data
pub claim_data: Claim,
/// The global index of the imported bridge exit
pub global_index: GlobalIndex,
}
```
### Claim Data Types
```rust theme={null}
pub enum Claim {
Mainnet(Box),
Rollup(Box),
}
```
**Separation Reason**: L1 and Rollup claims require different proof paths due to their different positions in the hierarchical tree structure:
* **Mainnet**: Requires direct proof from Mainnet Exit Root to L1 Info Root since L1 transactions are recorded directly in the Mainnet Exit Tree and don't need to go through the Rollup Exit Tree aggregation layer.
* **Rollup**: Requires a two-step proof path from Local Exit Root → Rollup Exit Root → L1 Info Root because L2 transactions must first prove inclusion in the L2's Local Exit Tree, then prove that the L2's Local Exit Root was properly submitted to the Rollup Exit Tree on L1.
## Local State
Local State represents the complete state of a local chain.
### Structure
```rust theme={null}
pub struct LocalNetworkState {
/// Commitment to the BridgeExit
pub exit_tree: LocalExitTree,
/// Commitment to the balance for each token
pub balance_tree: LocalBalanceTree,
/// Commitment to claimed assets on foreign networks
pub nullifier_tree: NullifierTree,
}
```
### Components
* **Exit Tree**: Records all outgoing bridge transactions as a 32-level Sparse Merkle Tree, storing cryptographic commitments of `bridgeAsset` and `bridgeMessage` operations that represent assets and messages being sent to other chains.
* **Balance Tree**: Tracks token balances for all assets on the chain using a 192-bit depth Sparse Merkle Tree, with TokenInfo keys enabling precise tracking of token origins and current balances for every asset type on the chain.
* **Nullifier Tree**: Prevents double-spending of claimed assets by maintaining a 64-bit depth Sparse Merkle Tree that marks imported bridge exits as claimed, ensuring that each cross-chain transaction can only be processed once on the destination chain.
## Multi Batch Header
The comprehensive state transition record for pessimistic proof generation.
### Structure
```rust theme={null}
pub struct MultiBatchHeader {
/// Network that emitted this MultiBatchHeader
pub origin_network: NetworkId,
/// Previous local exit root
pub prev_local_exit_root: H::Digest,
/// Previous local balance root
pub prev_balance_root: H::Digest,
/// Previous nullifier tree root
pub prev_nullifier_root: H::Digest,
/// List of bridge exits created in this batch
pub bridge_exits: Vec,
/// List of imported bridge exits claimed in this batch
pub imported_bridge_exits: Vec<(ImportedBridgeExit, NullifierPath)>,
/// Commitment to the imported bridge exits
pub imported_exits_root: Option,
/// L1 info root used to import bridge exits
pub l1_info_root: H::Digest,
/// Token balances with Merkle proofs
pub balances_proofs: BTreeMap)>,
/// Signer committing to the state transition
pub signer: Address,
/// Signature committing to the state transition
pub signature: Signature,
/// State commitment target hashes
pub target: StateCommitment,
}
```
### Purpose
Serves as the master input capturing the complete set of changes between old and new local states, containing all data required for pessimistic proof generation. This structure packages together the previous state roots, all state transition data (bridge exits and imported bridge exits), balance proofs, and target state commitments needed to mathematically verify that the proposed state transition is valid and secure.
## Pessimistic Proof Output
The final result of Pessimistic Proof computation.
### Structure
```rust theme={null}
pub struct PessimisticProofOutput {
/// The previous local exit root
pub prev_local_exit_root: Digest,
/// The previous pessimistic root
pub prev_pessimistic_root: Digest,
/// The l1 info root for proving imported bridge exits
pub l1_info_root: Digest,
/// The origin network of the pessimistic proof
pub origin_network: NetworkId,
/// The consensus hash
pub consensus_hash: Digest,
/// The new local exit root
pub new_local_exit_root: Digest,
/// The new pessimistic root (balance + nullifier tree)
pub new_pessimistic_root: Digest,
}
```
### Pessimistic Root Formula
```
prev_pessimistic_root = hash(prev_local_balance_root, prev_nullifier_root)
new_pessimistic_root = hash(new_local_balance_root, new_nullifier_root)
```
## Certificate
A Certificate represents a state transition of a chain that gets submitted to Agglayer.
### Structure
```rust theme={null}
pub struct Certificate {
/// NetworkID of the origin network
pub network_id: NetworkId,
/// Simple increment to count the Certificate per network
pub height: Height,
/// Previous local exit root
pub prev_local_exit_root: Digest,
/// New local exit root
pub new_local_exit_root: Digest,
/// List of bridge exits included in this state transition
pub bridge_exits: Vec,
/// List of imported bridge exits included in this state transition
pub imported_bridge_exits: Vec,
/// Signature committed to the bridge exits and imported bridge exits
pub signature: Signature,
/// Fixed size field of arbitrary data for the chain needs
pub metadata: Metadata,
}
```
### Validation
If a certificate is invalid, any state transitions in the current epoch will be reverted, protecting the network from invalid state changes. The validation process ensures that all bridge exits have sufficient balances, all imported bridge exits have valid proofs and haven't been double-claimed, and that the cryptographic signature properly commits to all the state transition data. This atomic validation prevents partial state updates that could compromise network security.
# Proof Generation
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/pessimistic-proof/proof-generation
The Pessimistic Proof generation pipeline: certificate submission, native Rust validation, zkVM proof generation, and proof verification
## Overview
Pessimistic Proof generation is a multi-step process that verifies state transitions mathematically before Agglayer accepts them. The process runs the proof program first in native Rust for fast validation, then in a zkVM to produce a cryptographic proof.
## Proof Generation Flow
### Complete Flow
### Step 1: Certificate Submission
Local chains submit a **`Certificate`** containing comprehensive state transition data:
* **Previous and new local exit roots** that define the starting and ending states of the chain's **Local Exit Tree**, providing the boundary conditions for validating the proposed state transition.
* **Bridge exits** (outbound transactions) representing all assets and messages being sent from this chain to other chains, with complete destination information, token details, and amounts that will reduce the chain's **Local Balance Tree**.
* **Imported bridge exits** (inbound transactions) representing all assets and messages being claimed on this chain from other chains, with **cryptographic proofs** demonstrating their validity and **Global Index** information for verification.
* **Cryptographic signature** from the chain's authorized signer that commits to all the state transition data, ensuring that the certificate represents an authorized state transition approved by the chain's governance or operator.
### Step 2: Data Population
Agglayer Client creates a **`MultiBatchHeader`** from the Certificate:
* Adds **previous state roots** (balance, nullifier, exit) from the initial network state to establish the baseline from which state transitions will be computed and validated.
* Includes **balance proofs** for affected tokens by providing **Merkle proofs** demonstrating the current balances in the **Local Balance Tree** for all tokens involved in bridge exits, ensuring that sufficient funds exist for outbound transfers.
* Sets **target state commitment** representing the expected final state after applying all bridge exits and imported bridge exits, serving as the verification target that the computed state transition must match.
* Adds **authentication data** including **cryptographic signatures**, signer addresses, and **L1 Info Root** references needed to validate the legitimacy and timing of the state transition request.
### Step 3: Native Execution
Before expensive zkVM computation, Agglayer runs the Pessimistic Proof program in native Rust to validate the state transition:
**The Process:**
1. **Apply State Transitions**: The program takes the **initial network state** and applies all the changes specified in the **batch header**. For **bridge exits**, it reduces token balances and adds new leaves to the **Local Exit Tree**. For **imported bridge exits**, it checks **nullifier uniqueness**, increases token balances, and marks transactions as claimed in the **Nullifier Tree**.
2. **Compute New State**: After applying all transitions, the program computes the new roots for all three **Merkle trees** (**Exit Tree**, **Balance Tree**, **Nullifier Tree**) and creates a new **state commitment** that represents the final state after all changes.
3. **Validate Against Target**: The computed new state is compared against the **target state** provided in the certificate. If they match, it proves that the chain's proposed state transition is **mathematically correct** and respects all balance and security constraints.
4. **Generate Output**: If validation succeeds, the program generates a **`PessimisticProofOutput`** containing both the previous and new state roots, which serves as the verified result of the state transition computation.
```rust theme={null}
pub fn generate_pessimistic_proof(
initial_state: &LocalNetworkState,
batch_header: &MultiBatchHeader,
) -> Result {
// Compute new state based on transitions
let new_state = apply_state_transitions(initial_state, batch_header)?;
// Verify against expected target
if new_state.commitment() != batch_header.target {
return Err(ProofError::InvalidStateTransition);
}
// Return proof output
Ok(PessimisticProofOutput {
prev_local_exit_root: batch_header.prev_local_exit_root,
prev_pessimistic_root: compute_pessimistic_root(
batch_header.prev_balance_root,
batch_header.prev_nullifier_root
),
new_local_exit_root: new_state.exit_tree.root(),
new_pessimistic_root: compute_pessimistic_root(
new_state.balance_tree.root(),
new_state.nullifier_tree.root()
),
// ... other fields
})
}
```
### Step 4: zkVM Execution
If native execution succeeds, run the identical program in zkVM:
* **Same Program**: Exact same proof generation function executed in the zkVM environment to ensure that the cryptographic proof verifies the identical computation that was validated in native Rust, maintaining consistency between validation and proof phases.
* **Same Inputs**: Identical initial state and batch header data fed to the zkVM to guarantee that the proof generation uses exactly the same parameters that were validated in native execution, preventing any discrepancies between validation and proving.
* **Cryptographic Proof**: Generates verifiable proof of correct execution that can be validated by anyone without re-executing the program, providing mathematical certainty that the state transition computation was performed correctly according to Pessimistic Proof rules.
### Step 5: Proof Validation
Agglayer validates the returned zk proof:
* **Proof Verification**: Cryptographic verification of the proof using the SP1 verifier to ensure mathematical validity and confirm that the proof corresponds to the expected program execution with correct inputs and outputs.
* **Output Validation**: Ensure proof output matches expected results by comparing the PessimisticProofOutput from the zkVM execution with the results from native execution, validating consistency between both execution environments.
* **State Acceptance**: Update network state if proof is valid by accepting the new state roots and allowing the chain to proceed with its state transition, enabling subsequent operations to build on the verified state.
## State Transition Logic
The state transition mechanism validates and applies changes from the old local state to the new local state using bridge exits and imported bridge exits, then compares the computed results with the expected certificate data.
## Proof Verification
### Mathematical Constraints
The proof generation enforces several mathematical constraints:
1. **Balance Conservation**: Total outbound amounts ≤ available balances, mathematically enforced by verifying that the sum of all bridge exit amounts for each token does not exceed the current balance stored in the Local Balance Tree, preventing overdraft conditions.
2. **Nullifier Uniqueness**: No double-claiming of imported exits, enforced by checking that each imported bridge exit's nullifier key is not already marked as claimed in the Nullifier Tree, preventing replay attacks and duplicate processing.
3. **Root Consistency**: All tree roots must be computed correctly using the standard Keccak256 hashing algorithm and Sparse Merkle Tree algorithms, ensuring that state transitions produce mathematically valid tree structures.
4. **Signature Validity**: State transitions must be properly signed by the chain's authorized signer using valid cryptographic signatures that commit to all the state transition data, ensuring that only authorized parties can propose state changes.
### Security Guarantees
* **No Overdraft**: Chains cannot spend more than they have because the Pessimistic Proof program mathematically enforces balance conservation by verifying that outbound bridge exit amounts do not exceed available token balances in the Local Balance Tree, creating a hard constraint against fund drainage.
* **No Double-Spending**: Each transaction can only be claimed once due to the Nullifier Tree mechanism that marks imported bridge exits as claimed and prevents re-processing, ensuring that the same cross-chain transaction cannot be used multiple times to inflate balances.
* **Cryptographic Integrity**: All state changes are cryptographically verified through Merkle tree operations using Keccak256 hashing, zkVM proof generation, and signature validation, ensuring that no invalid state modifications can be accepted by the system.
* **Atomic Updates**: All changes succeed or fail together through the comprehensive validation process where any failure in balance verification, nullifier checking, or signature validation causes the entire state transition to be rejected, preventing partial updates that could compromise system integrity.
# Aggchain Proof
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/state-transition-proof/aggchain-proof
Reference for Aggchain Proof: ECDSA and generic validity proof consensus types, data structures, verification steps, and bridge constraint details
## Overview
Aggchain Proof is the verification layer in Agglayer that handles different consensus mechanisms for proving chain state transitions. It combines consensus verification with bridge constraint verification, producing a single proof that covers both a chain's internal operations and its cross-chain transfers.
## Supported Consensus Types
### ECDSA Signature (CONSENSUS\_TYPE = 0)
The original consensus mechanism used in Agglayer where a **trusted sequencer** acts as a security authority, signing off on state changes to ensure they are valid and authorized.
**Characteristics:**
* **Trusted Sequencer Model**: A designated address signs off on state changes, acting as the primary security authority for the chain
* **Simple Verification**: Like having a security guard verify and approve changes - fast and straightforward
* **Fast Processing**: Minimal computational overhead with efficient signature verification
* **Trust Assumption**: Relies on sequencer integrity and key security
**How ECDSA Verification Works:**
1. **Message Construction**: Create standardized message combining **SHA256** of public values, **new local exit root**, and **commitment to imported bridge exits**
2. **Signature Recovery**: Use elliptic curve cryptography to recover the signer's address from the signature
3. **Authority Validation**: Compare recovered address with configured **trusted sequencer** address
```rust theme={null}
// ECDSA Verification Implementation
pub fn verify(&self) -> Result<(), ProofError> {
let signature_commitment = keccak256_combine([
self.sha256_public_values(),
new_local_exit_root.0,
commit_imported_bridge_exits.0,
]);
let recovered_signer = signature
.recover_address_from_prehash(&B256::new(signature_commitment.0))?;
if recovered_signer != self.trusted_sequencer {
return Err(ProofError::InvalidSigner);
}
Ok(())
}
```
### Generic Proof / Validity Proof (CONSENSUS\_TYPE = 1)
Advanced consensus mechanism providing comprehensive verification of chain operations through mathematical proofs rather than trusted parties.
**Characteristics:**
* **Flexible Proof System**: Can work with any type of chain-specific proof system
* **Mathematical Verification**: Provides comprehensive state transition validation with cryptographic certainty
* **Universal Compatibility**: Like having a universal translator for different security protocols
* **Enhanced Security**: Mathematical certainty about state correctness without trust assumptions
**How Validity Proof Works:**
1. **State Transition Verification**: Mathematically verify every operation within the chain, including transaction processing, smart contract execution, and state machine transitions
2. **Bridge Constraint Verification**: Verify that cross-chain transfers are valid and properly integrated with internal state changes
3. **SP1 zkVM Verification**: Use **SP1 zkVM** to cryptographically verify the validity proof with mathematical certainty
```rust theme={null}
// Validity Proof Verification Implementation
pub fn verify(&self) -> Result<(), ProofError> {
// Verify L1 head for synchronization
self.verify_l1_head(l1_info_root)?;
// Verify the validity proof using SP1 zkVM
sp1_zkvm::lib::verify::verify_sp1_proof(
&self.aggregation_vkey_hash.to_hash_u32(),
&self.sha256_public_values().into(),
);
Ok(())
}
```
**Use Cases:**
* **Zero-Knowledge Rollups**: Chains generating **zk-SNARKs** or **zk-STARKs** for state transitions
* **Custom Consensus**: Chains with unique consensus algorithms and specialized verification requirements
## Aggchain Proof Data Structure
### Witness Structure
```rust theme={null}
pub struct AggchainProofWitness {
/// Previous local exit root
pub prev_local_exit_root: Digest,
/// New local exit root
pub new_local_exit_root: Digest,
/// L1 info root used to import bridge exits
pub l1_info_root: Digest,
/// Origin network for which the proof was generated
pub origin_network: u32,
/// Full execution proof with its metadata
pub fep: FepInputs,
/// Commitment on the imported bridge exits minus the unset ones
pub commit_imported_bridge_exits: Digest,
/// Bridge witness related data
pub bridge_witness: BridgeWitness,
}
```
### Bridge Witness
```rust theme={null}
pub struct BridgeWitness {
/// List of inserted GER minus the removed ones
pub inserted_gers: Vec,
/// Raw list of inserted GERs which includes also the ones which get removed
pub raw_inserted_gers: Vec,
/// List of removed GER
pub removed_gers: Vec,
/// List of each imported bridge exit containing global index and leaf hash
pub bridge_exits_claimed: Vec,
/// List of global index of each unset bridge exit
pub global_indices_unset: Vec,
/// State sketch for the prev L2 block
pub prev_l2_block_sketch: EvmSketchInput,
/// State sketch for the new L2 block
pub new_l2_block_sketch: EvmSketchInput,
}
```
### Public Values Output
```rust theme={null}
pub struct AggchainProofPublicValues {
/// Previous local exit root
pub prev_local_exit_root: Digest,
/// New local exit root
pub new_local_exit_root: Digest,
/// L1 info root used to import bridge exits
pub l1_info_root: Digest,
/// Origin network for which the proof was generated
pub origin_network: NetworkId,
/// Commitment to the imported bridge exits indexes
pub commit_imported_bridge_exits: Digest,
/// Chain-specific commitment forwarded by the PP
pub aggchain_params: Digest,
}
```
## Verification Process
### Step 1: Consensus Verification
The system first verifies the chain's consensus proof:
```rust theme={null}
// Verify the FEP proof or ECDSA signature
self.fep.verify(
self.l1_info_root,
self.new_local_exit_root,
self.commit_imported_bridge_exits,
)?;
```
**For ECDSA**: Verifies signature from trusted sequencer
**For Generic**: Verifies validity proof using chain-specific verification logic
### Step 2: Bridge Constraints Verification
Then verifies bridge-related constraints:
```rust theme={null}
// Verify the bridge constraints
self.bridge_constraints_input().verify()?;
```
**Bridge Verification Components:**
1. **GER Hash Chains**: Verifies **Global Exit Root** insert/remove sequences recorded in hash chains that act as a stack using LIFO rules
2. **Claims Hash Chains**: Verifies claimed and unset claims hash chains where valid claims are added to claimed chain and invalid ones to unset chain
3. **Local Exit Root**: Verifies the **Local Exit Root** is computed correctly
4. **Imported Bridge Exits**: Verifies **`commit_imported_bridge_exits`** is constructed correctly from claimed and unset bridge events
5. **GER Inclusion**: Verifies each inserted **Global Exit Root** has valid **Merkle proof** inclusion in the **L1 Info Root**
## Execution Flow
### Complete Aggchain Proof Process
## Bridge Constraint Details
### GER Stack Management
**Global Exit Root** updates are managed as a sophisticated stack structure that ensures proper sequencing and validation:
* **Insertion Process**: New **Global Exit Roots** are added when bridge operations occur on any connected chain. Each insertion represents a state change in the **Unified Bridge** system and must be properly validated and sequenced to maintain network consistency.
* **Removal Mechanism**: Faulty **Global Exit Roots** can be removed from the stack in rare cases where invalid state updates are detected. This removal process ensures that incorrect state updates don't propagate through the network and compromise security.
* **LIFO Order**: Last-in-first-out ordering ensures proper sequence validation where the most recent **GER** updates are processed first. This ordering is critical for maintaining temporal consistency and ensuring that state updates are applied in the correct chronological order.
* **Hash Chain Tracking**: All **GER** operations (both insertions and removals) are recorded in cryptographically linked hash chains that provide an immutable audit trail. These hash chains enable verification that the **GER** stack operations were performed correctly and in the proper sequence.
### Claims Processing
**Bridge Exit Claims** are processed with comprehensive dual tracking that ensures security and prevents double-spending:
* **Claimed Hash Chain**: Valid claims that successfully increase balances on destination chains are recorded in a cryptographically linked hash chain. Each entry in this chain represents a legitimate cross-chain transfer that has been properly verified and processed, creating an immutable record of successful bridge operations.
* **Unset Hash Chain**: Invalid claims that are rejected due to insufficient proofs, double-spending attempts, or other validation failures are recorded in a separate hash chain. This tracking ensures that invalid operations are properly documented and cannot be reprocessed, maintaining system integrity.
* **Atomic Processing**: All claims in a batch succeed or fail together through comprehensive validation where any single claim failure causes the entire batch to be rejected. This atomic processing prevents partial state updates that could compromise balance consistency across chains.
* **Double-Spend Prevention**: Ensures each bridge exit can only be claimed once by tracking all processed claims in the **Nullifier Tree** and validating that imported bridge exits haven't been previously processed. This mechanism prevents replay attacks and maintains economic security.
### Inclusion Proof Verification
**L1 Info Root Inclusion** provides cryptographic proof that **Global Exit Root** updates are legitimate:
* **Merkle Proof Validation**: Each inserted **Global Exit Root** must have a valid **Merkle proof** demonstrating inclusion in the **L1 Info Tree**. These proofs use **Keccak256** hashing to create cryptographic certainty that the **GER** was properly recorded on L1.
* **L1 Settlement Verification**: Proofs demonstrate that **Global Exit Root** updates were properly recorded on L1 and achieved finality, ensuring that cross-chain operations are backed by Ethereum's security guarantees and cannot be reverted.
* **Leaf Index Validation**: Ensures correct positioning in the **L1 Info Tree** by validating that the **Merkle proof** corresponds to the expected leaf index. This prevents manipulation of proof paths and ensures that proofs reference the correct historical state.
* **Root Verification**: Confirms that **Merkle proofs** lead to the correct **L1 Info Root** by reconstructing the proof path and validating that it produces the expected root hash. This verification ensures that proofs are based on legitimate L1 state and haven't been tampered with.
# Architecture Overview
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/state-transition-proof/architecture
The State Transition Proof dual proof system: Local Chain, AggProver, and Agglayer roles, and how ECDSA and generic consensus types are handled
## Overview
State Transition Proof implements a dual proof system that verifies both internal chain operations and cross-chain transfers. It supports chains with different consensus mechanisms while maintaining consistent security guarantees at the Agglayer level.
## Dual Proof Architecture
### Complete Verification Flow
## System Components
### Local Chain
**Purpose**: Chains connected to Agglayer (Katana, X Layer, and others) that generate state transition proofs.
**Key Responsibilities:**
* **State Transition Confirmation**: Validates that internal state changes are mathematically correct and follow the chain's consensus rules
* **Proof Generation**: Creates either **Validity Proofs** (comprehensive state verification) or **ECDSA Signatures** (trusted sequencer authorization) depending on the chain's security model
* **Certificate Submission**: Packages state transition data into certificates for Agglayer verification
### AggProver
**Purpose**: Critical component that generates cryptographic proofs for state transitions and bridge operations.
**Key Responsibilities:**
* **Consensus Verification**: Validates either **Validity Proofs** or **ECDSA Signatures** from local chains to ensure state transitions are authorized and mathematically correct
* **Bridge Constraint Validation**: Verifies **Global Exit Root** sequences, claimed/unset bridge events, **Local Exit Root** correctness, and **L1 Info Root** inclusion proofs
* **Aggchain Proof Generation**: Creates comprehensive proofs that combine consensus verification with bridge validation, ensuring both internal and cross-chain operations are secure
## Verification Systems
### Internal Chain Validation
**State Transition Verification:**
* Validates that each chain's internal state transitions are mathematically correct through comprehensive verification of all operations within the chain
* Ensures all operations follow proper execution rules and that new states are properly derived from previous states through valid state transition logic
* Provides the foundation for secure cross-chain operations by ensuring individual chains are operating correctly before allowing bridge operations
**Consensus Mechanisms:**
* **Validity Proof**: Comprehensive verification of every operation in the chain using mathematical proofs, providing cryptographic certainty about state correctness without requiring trusted parties
* **ECDSA Signature**: Trusted sequencer authorization where designated addresses validate and sign off on state changes, providing fast verification with trusted party assumptions
### Cross-Chain Validation
**Aggchain Proof:**
* Combines consensus verification with bridge constraint validation to ensure both internal operations and cross-chain transfers are secure and mathematically correct
* Supports flexible consensus mechanisms while maintaining strict bridge security requirements, enabling different chain types to participate safely
* Acts as the bridge between internal chain validation and cross-chain operation validation
**Pessimistic Proof:**
* Validates cross-chain asset transfers and balance conservation by ensuring chains cannot drain more funds than currently deposited, creating financial isolation between chains
* Prevents compromised chains from affecting other chains in the network through mathematical constraints on fund movement
* Ensures atomic cross-chain operations where all components succeed or fail together
## Consensus Flexibility
### ECDSA Consensus (CONSENSUS\_TYPE = 0)
**Characteristics:**
* **Trusted Sequencer**: Designated address signs state transitions
* **Simple Verification**: Signature validation using elliptic curve cryptography
* **Fast Processing**: Minimal computational overhead
* **Trust Model**: Relies on sequencer integrity
### Generic Consensus (CONSENSUS\_TYPE = 1)
**Characteristics:**
* **Validity Proofs**: Comprehensive mathematical verification of state transitions
* **Flexible Integration**: Supports various proof systems and zkVMs
* **Enhanced Security**: Mathematical certainty about state correctness
* **Modular Design**: Can integrate with different chain architectures
## Security Guarantees
### Comprehensive Validation
* **Internal Security**: Every chain's internal operations are verified through either **Validity Proofs** or **ECDSA signatures** before any cross-chain operations are allowed, ensuring that only properly functioning chains can participate in bridge operations
* **Cross-Chain Security**: Bridge operations are validated through multiple proof systems (**Aggchain Proof** + **Pessimistic Proof**) that ensure mathematical correctness and balance conservation across all connected chains
* **Mathematical Certainty**: All verifications use cryptographic proofs and mathematical constraints to provide certainty about operation validity, eliminating reliance on trust assumptions where possible
### Isolation and Containment
* **Chain Independence**: Issues in one chain cannot affect others due to isolated verification processes where each chain's state transitions are validated independently through separate proof generation and verification
* **Atomic Operations**: All operations succeed or fail completely through the comprehensive validation pipeline where any failure in consensus verification, bridge constraints, or pessimistic proof validation causes the entire state transition to be rejected
* **Proof Requirements**: Invalid operations cannot proceed without proper verification because the system requires valid cryptographic proofs at multiple stages (consensus + bridge + balance conservation) before accepting any state changes
# Architecture Overview
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/unified-bridge/architecture
The Unified Bridge architecture: onchain contracts, off-chain services, data flow, and security properties
## Overview
The Unified Bridge combines onchain smart contracts, off-chain services, and cryptographic verification to enable cross-chain communication across Agglayer-connected chains.
*Figure 1: Complete Unified Bridge architecture showing all components and interactions*
## System Architecture
## Data Flow Architecture
### Cross-Chain Transaction Flow
## Component Interactions
### Smart Contract Layer
**L1 Contracts (Ethereum):**
* **RollupManager**: Coordinates L2 state submissions and manages the Rollup Exit Tree. When L2s submit their Local Exit Roots, this contract updates the aggregated rollup state and triggers Global Exit Root updates.
* **GlobalExitRoot**: Maintains the unified Global Exit Root by combining Rollup Exit Root and Mainnet Exit Root. Also manages the L1 Info Tree that stores historical Global Exit Roots for L2 synchronization.
* **Bridge**: Handles L1 ↔ L2 transactions and maintains the Mainnet Exit Tree. Processes asset and message bridging from L1 to connected L2s, and validates claims from L2s to L1.
**L2 Contracts (Connected Chains):**
* **Bridge**: Handles all cross-chain transactions for the L2, including bridging to other L2s and L1. Maintains the chain's Local Exit Tree and processes both outbound bridging and inbound claims.
* **GlobalExitRootL2**: Syncs with L1 Global Exit Root updates to enable claim verification. Fetches the latest Global Exit Root from L1 to validate cross-chain transaction proofs.
### Service Layer
**Bridge Services:**
* **Chain Indexer**: Monitors blockchain events in real-time, parsing and organizing bridge transaction data. Each connected chain has its own indexer instance that processes `BridgeEvent` and `ClaimEvent` logs.
* **Transaction API**: Provides real-time bridge transaction status and details for user interfaces. Returns transaction status, token information, source/destination chains, and deposit counts needed for proof generation.
* **Proof API**: Generates Merkle proofs required for claiming bridged assets and messages. Creates `smtProofLocalExitRoot` and `smtProofRollupExitRoot` along with other verification data needed for claims.
### Security Architecture
## Bridge Operation Types
### Asset Bridging Architecture
**Source Chain Process:**
1. Lock/burn tokens based on token type
2. Record transaction in Local Exit Tree
3. Emit bridge event for indexing
**Destination Chain Process:**
1. Verify Merkle proofs against Global Exit Root
2. Transfer/mint tokens based on token type
3. Mark transaction as claimed
### Message Bridging Architecture
**Source Chain Process:**
1. Package message data and ETH value
2. Record message in Local Exit Tree
3. Emit bridge event for indexing
**Destination Chain Process:**
1. Verify Merkle proofs against Global Exit Root
2. Execute message on target contract
3. Handle ETH/WETH value transfer
## State Synchronization
### Global Exit Root Updates
### Merkle Tree Hierarchy
The architecture maintains a sophisticated hierarchical Merkle tree structure that enables secure cross-chain verification:
* **Local Exit Trees**: Each connected chain maintains its own 32-level Sparse Merkle Tree that records all outgoing bridge transactions. Every time a user initiates a `bridgeAsset` or `bridgeMessage` call, a new leaf is added to this tree and the root is updated.
* **Rollup Exit Tree**: L1's RollupManager maintains a Sparse Merkle Tree where each leaf represents a Local Exit Root from a connected L2. When L2s submit their updated Local Exit Roots to L1, this tree is updated, creating a unified view of all L2 bridge activities.
* **Mainnet Exit Tree**: L1 maintains its own Local Exit Tree (called Mainnet Exit Tree) that records all bridge transactions originating from L1 to connected L2s. This operates similarly to L2 Local Exit Trees but specifically for L1 activities.
* **Global Exit Root**: A single root hash computed as `hash(RollupExitRoot, MainnetExitRoot)` that represents the complete state of all cross-chain activities across the entire network. This root is updated whenever either the Rollup Exit Root or Mainnet Exit Root changes.
* **L1 Info Tree**: A historical ledger that stores every Global Exit Root update as leaves in a 32-level Sparse Merkle Tree. This enables L2s to sync with specific historical states and provides the foundation for Merkle proof verification during claims.
## Scalability Design
### Horizontal Scaling
* **Multiple L2s**: The architecture supports unlimited connected chains without performance degradation. Each new L2 simply adds another leaf to the Rollup Exit Tree, and the system scales linearly with the number of connected chains.
* **Parallel Processing**: Connected chains operate independently and can process bridge transactions simultaneously. There's no coordination required between chains for individual transactions, enabling true parallel execution across the network.
* **Load Distribution**: Proof generation and transaction indexing are distributed across multiple service instances. Each chain can have its own indexer, and proof generation can be handled by distributed prover networks.
### Vertical Scaling
* **Batch Submissions**: L2s have flexibility in how frequently they submit their Local Exit Roots to L1. They can submit immediately for each transaction or batch multiple transactions together before submitting, optimizing for gas costs and throughput.
* **Efficient Proofs**: Merkle proof generation is optimized using Sparse Merkle Trees that only store non-zero values, significantly reducing storage and computation requirements. Proofs are generated on-demand and cached for frequently accessed transactions.
* **State Compression**: The hierarchical tree structure provides natural compression where multiple L2 states are represented by a single Rollup Exit Root, and the entire network state is compressed into a single Global Exit Root.
## Security Properties
### Trust Model
* **L1 Security**: The system inherits Ethereum's security guarantees because all cross-chain transactions must be settled and finalized on Ethereum before they can be claimed on destination chains. This means the security of cross-chain operations is backed by Ethereum's consensus mechanism and economic security.
* **Cryptographic Proofs**: Every claim requires valid Merkle proofs that mathematically demonstrate the transaction was properly recorded and settled. The verification process uses cryptographic hash functions to ensure that no invalid or fraudulent claims can be processed.
* **No Trusted Parties**: The system operates without requiring trust in any centralized entity. Users, claimers, and even the bridge operators cannot manipulate the system because all operations are governed by smart contract logic and cryptographic verification.
### Failure Isolation
* **Chain Independence**: If one connected chain experiences issues or becomes compromised, it cannot affect the security or operation of other chains. Each chain's bridge transactions are isolated in separate Local Exit Trees, preventing cross-contamination of security issues.
* **Atomic Operations**: Bridge transactions either succeed completely or fail completely - there are no partial states. If any part of a cross-chain transaction fails (such as insufficient balance or invalid proofs), the entire operation is reverted without affecting the system state.
* **Proof Requirements**: Invalid operations cannot be executed because the system requires valid Merkle proofs for all claims. Without proper cryptographic proof that a transaction was settled on L1, no assets can be claimed or messages executed on destination chains.
# Asset Bridging
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/unified-bridge/asset-bridging
How asset bridging works in the Unified Bridge: token type handling, bridgeAsset/claimAsset function signatures, and Merkle proof verification
## Overview
Asset bridging enables the transfer of tokens and native assets between Agglayer-connected chains. The Unified Bridge handles different token types with distinct mechanisms depending on whether the token originates from the source chain or is a foreign token.
*Figure 1: Complete asset bridging flow from L1 to L2*
## Supported Token Types
The Unified Bridge handles different token types with specific mechanisms:
| Token Type | Source Chain Action | Destination Chain Action |
| -------------------------------------------- | --------------------------------- | ------------------------------------- |
| **Native Gas Token** (ETH, Custom Gas Token) | Bridge contract holds tokens | Bridge contract transfers tokens |
| **WETH** | Burn WETH tokens from user | Mint WETH tokens to user |
| **Foreign ERC20** (Not native to source) | Burn ERC20 tokens from user | Mint wrapped tokens to user |
| **Native ERC20** (Native to source) | Transfer ERC20 to bridge contract | Transfer from bridge contract to user |
## Bridge Asset Function
The `bridgeAsset` function initiates asset transfers between chains.
### Function Signature
```solidity theme={null}
function bridgeAsset(
uint32 destinationNetwork,
address destinationAddress,
uint256 amount,
address token,
bool forceUpdateGlobalExitRoot,
bytes calldata permitData
) external payable
```
### Parameters
* **`destinationNetwork`**: Network ID of the destination chain
* **`destinationAddress`**: Address to receive assets on destination chain
* **`amount`**: Amount of tokens to bridge
* **`token`**: Token contract address (0x0 for native gas token)
* **`forceUpdateGlobalExitRoot`**: Whether to update GER immediately
* **`permitData`**: Raw permit data for ERC20 tokens (optional)
### Process Steps
1. **Validation**: Check destination network is not the source network
2. **Token Preparation**: Handle token based on type (lock, burn, or transfer)
3. **Event Emission**: Emit `BridgeEvent` with transaction details
4. **Tree Update**: Add transaction to Local Exit Tree as leaf node
### Token Preparation Logic
The bridge handles different token types with specific mechanisms based on their origin and nature:
> Note that in case `ETH` is the native token, WETHToken will be at `0x0` address.
#### Native Gas Token (ETH, Custom Gas Token)
```solidity theme={null}
// Bridge contract holds the tokens
// The native gas token is already transferred via msg.value
// No additional token transfer required
```
#### WETH Token
```solidity theme={null}
// Burn WETH tokens from user's address
IWETH(token).burnFrom(msg.sender, amount);
```
#### Foreign ERC20 Token (Not native to source chain)
```solidity theme={null}
// If the token contract is not originally from the source network,
// burn the ERC20 token from user's address
IERC20(token).burnFrom(msg.sender, amount);
```
#### Native ERC20 Token (Native to source chain)
```solidity theme={null}
// If the token contract is originally from the source network:
// 1. Execute permit if provided
if (permitData.length > 0) {
IERC20Permit(token).permit(...);
}
// 2. Transfer tokens from user to bridge contract
IERC20(token).transferFrom(msg.sender, address(this), amount);
```
## Claim Asset Function
The `claimAsset` function claims bridged assets on the destination chain.
### Function Signature
```solidity theme={null}
function claimAsset(
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot,
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot,
uint256 globalIndex,
bytes32 mainnetExitRoot,
bytes32 rollupExitRoot,
uint32 originNetwork,
address originTokenAddress,
uint32 destinationNetwork,
address destinationAddress,
uint256 amount,
bytes calldata metadata
) external
```
### Parameters
* **`smtProofLocalExitRoot`**: Merkle proof for Local Exit Root
* **`smtProofRollupExitRoot`**: Merkle proof for Rollup Exit Root
* **`globalIndex`**: Global index identifying the transaction
* **`mainnetExitRoot`**: Mainnet Exit Root at time of transaction
* **`rollupExitRoot`**: Rollup Exit Root at time of transaction
* **`originNetwork`**: Network ID of source chain
* **`originTokenAddress`**: Token address on source chain
* **`destinationNetwork`**: Network ID of destination chain
* **`destinationAddress`**: Address to receive assets
* **`amount`**: Amount of tokens to claim
* **`metadata`**: Additional metadata (if any)
### Process Steps
1. **Validation**: Verify destination network matches current chain
2. **Proof Verification**: Verify Merkle proofs against Global Exit Root
3. **Duplicate Check**: Ensure transaction hasn't been claimed before
4. **Token Transfer**: Transfer tokens based on token type (see Token Transfer Logic below)
5. **Claim Record**: Mark transaction as claimed
### Token Transfer Logic
Once the proof verification passes, the bridge claims tokens using different mechanisms based on the token type:
| Token type | Action |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **ETH is gas token** | Bridge contract transfers the amount from itself to the destination address |
| **WETH where ETH is not gas token** | Mint new WETH tokens to the destination address |
| **Custom gas token** | Bridge contract transfers the amount from itself to the destination address |
| **Native ERC20 Token** | If the token contract is originally from this destination network, transfer the ERC20 token from bridge contract to destination address |
| **Foreign ERC20 Token, First time bridging** | Deploy a new ERC20 Token contract to host this new Foreign ERC20 Token, and mint the transfer amount to destination address |
| **Foreign ERC20 Token, Contract exists** | Mint the transfer amount to destination address |
### Proof Verification Logic
```solidity theme={null}
// Construct Global Exit Root
bytes32 globalExitRoot = keccak256(abi.encodePacked(mainnetExitRoot, rollupExitRoot));
// Verify against synchronized GER
require(globalExitRoot == getGlobalExitRoot(), "Invalid global exit root");
// Verify Merkle proofs based on origin
if (originNetwork == 0) {
// L1 to L2: Verify against mainnet exit root
verifyMerkleProof(smtProofLocalExitRoot, mainnetExitRoot, globalIndex);
} else {
// L2 to L2: Verify against rollup exit root
verifyMerkleProof(smtProofLocalExitRoot, rollupExitRoot, globalIndex);
verifyMerkleProof(smtProofRollupExitRoot, rollupExitRoot, globalIndex);
}
```
## Bridging Flows
### L1 to L2 Bridging
### L2 to L1 Bridging
### L2 to L2 Bridging
# Bridge Components
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/unified-bridge/bridge-components
Reference for the Unified Bridge's onchain contracts, off-chain services, and APIs
## Overview
The Unified Bridge consists of three main component categories: onchain smart contracts, off-chain services, and developer tools.
*Figure 1: Complete Unified Bridge architecture showing all components and their interactions*
## Smart Contracts
The core onchain infrastructure deployed on each connected chain.
### PolygonZKEVMBridgeV2.sol
**Purpose**: Main bridge contract that serves as the interface for all cross-chain transactions.
**Key Functions**:
* `bridgeAsset()`: Initiates asset transfers between chains by validating destination networks, handling different token types (native gas tokens, WETH, ERC20), locking or burning tokens on the source chain, and recording the transaction in the Local Exit Tree.
* `bridgeMessage()`: Initiates message transfers between chains by packaging message data with optional ETH value, validating gas token conditions, and recording the message in the Local Exit Tree for later execution on the destination chain.
* `claimAsset()`: Claims bridged assets on the destination chain by verifying Merkle proofs against the Global Exit Root, ensuring the transaction hasn't been claimed before, and transferring or minting the appropriate tokens to the recipient address.
* `claimMessage()`: Claims bridged messages on the destination chain by verifying Merkle proofs, executing the message on the target contract (if it implements `IBridgeMessageReceiver`), and handling ETH/WETH value transfers.
**Data Management**:
* Maintains Local Exit Tree (LET) for the chain as a 32-level Sparse Merkle Tree, storing cryptographic commitments of all outgoing bridge transactions and updating the root with each new transaction.
* Records all outgoing bridge transactions with complete transaction details including destination network, recipient address, token amounts, and metadata, creating an immutable audit trail.
* Handles complex token operations including locking native tokens in escrow, burning foreign tokens, transferring native ERC20 tokens, and minting wrapped tokens on destination chains based on token origin and type.
**Deployment**: Deployed on both L1 and all connected L2s
### PolygonRollupManager.sol
**Purpose**: L1 contract that manages rollup state updates and coordinates L2 submissions.
**Key Functions**:
* `updateRollupExitRoot()`: Updates the rollup exit root when L2s submit their Local Exit Roots, validating the cryptographic proofs and ensuring the submitted state transitions are legitimate before updating the aggregated rollup state.
* `verifyBatches()`: Verifies L2 batch submissions by checking zero-knowledge proofs that demonstrate the validity of state transitions, ensuring that all transactions in the batch were executed correctly according to the L2's rules.
* `sequenceBatches()`: Sequences L2 batches on L1 by ordering and timestamping batch submissions, providing a canonical ordering of L2 operations that enables deterministic state reconstruction.
**Data Management**:
* Maintains Rollup Exit Tree (RET) as a Sparse Merkle Tree where each leaf represents a Local Exit Root from a connected L2, enabling efficient aggregation of all L2 bridge states into a single root hash.
* Tracks all L2 Local Exit Roots by storing the latest submitted root from each connected chain along with metadata like submission timestamps and batch numbers for audit and synchronization purposes.
* Updates Global Exit Root when RET changes by automatically triggering updates in the `PolygonZkEVMGlobalExitRootV2.sol` contract, ensuring the unified global state reflects all L2 bridge activities.
**Deployment**: Deployed only on L1
### PolygonZkEVMGlobalExitRootV2.sol
**Purpose**: L1 contract that maintains the Global Exit Root (GER) and L1 Info Tree.
**Key Functions**:
* `updateGlobalExitRoot()`: Updates the Global Exit Root when either the Rollup Exit Root or Mainnet Exit Root changes, computing the new GER as `hash(RollupExitRoot, MainnetExitRoot)` and appending it to the L1 Info Tree for historical tracking and L2 synchronization.
* `getGlobalExitRoot()`: Returns the current Global Exit Root that represents the unified state of all cross-chain activities across the entire network, used by L2s for synchronization and by users for generating claim proofs.
* `getL1InfoTreeRoot()`: Returns the root of the L1 Info Tree which contains all historical Global Exit Roots, enabling L2s to sync with specific historical states and generate valid Merkle proofs for transactions from any point in time.
**Data Management**:
* Maintains Global Exit Root (hash of RER and MER) as the single source of truth for the entire network's cross-chain state, automatically recalculating whenever either component root changes to ensure consistency.
* Maintains L1 Info Tree with historical GERs as a 32-level Sparse Merkle Tree, storing every Global Exit Root update as a timestamped leaf to enable historical state queries and proof generation for past transactions.
* Provides GER synchronization for L2s by exposing the latest Global Exit Root and L1 Info Tree root, allowing L2 contracts to fetch and verify the current unified state for processing incoming cross-chain claims.
**Deployment**: Deployed only on L1
### PolygonZkEVMGlobalExitRootL2.sol
**Purpose**: L2 contract that synchronizes with L1 Global Exit Root updates.
**Key Functions**:
* `updateExitRoot()`: Syncs with the latest Global Exit Root from L1 by calling the L1 Global Exit Root contract, fetching the current GER and L1 Info Tree root, and updating the L2's local copy to enable validation of incoming cross-chain claims.
* `getGlobalExitRoot()`: Returns the current synchronized Global Exit Root stored on this L2, which is used by the bridge contract to verify Merkle proofs during claim operations and ensure claims are based on the latest global state.
* `getL1InfoTreeRoot()`: Returns the synchronized L1 Info Tree root that corresponds to the current Global Exit Root, enabling the L2 to validate that claim proofs are based on legitimate historical states from the L1 Info Tree.
**Data Management**:
* Maintains synchronized copy of L1 GER by periodically fetching updates from the L1 Global Exit Root contract and storing them locally, ensuring the L2 has the latest unified network state for claim verification.
* Maintains synchronized copy of L1 Info Tree root along with the corresponding Global Exit Root, creating a consistent state snapshot that enables proper validation of Merkle proofs during cross-chain claim operations.
* Enables L2 to verify cross-chain claims by providing the necessary Global Exit Root and L1 Info Tree root data that the bridge contract uses to validate Merkle proofs and ensure claimed transactions were properly settled on L1.
**Deployment**: Deployed on all connected L2s
## Bridge Service
Off-chain infrastructure that provides indexing, APIs, and proof generation services.
### Chain Indexer Framework
**Purpose**: EVM blockchain data indexer that parses and organizes blockchain data.
**Key Features**:
* **Real-time Indexing**: Continuously monitors the blockchain for bridge-related events by subscribing to new blocks and scanning for `BridgeEvent` and `ClaimEvent` logs, ensuring that all cross-chain transactions are captured immediately as they occur.
* **Data Parsing**: Extracts and structures bridge transaction data from raw blockchain logs, converting hex-encoded event data into structured formats that include transaction details, token information, addresses, amounts, and timestamps.
* **Event Processing**: Processes `BridgeEvent` logs (emitted during bridging) and `ClaimEvent` logs (emitted during claiming) to track the complete lifecycle of cross-chain transactions from initiation to completion.
* **Database Storage**: Stores indexed data in structured databases optimized for API queries, enabling fast retrieval of transaction history, status updates, and proof generation data for user interfaces and applications.
**Deployment**: One instance per connected chain
**Technology**: Built on Polygon's Chain Indexer Framework
### Transaction API
**Purpose**: Provides real-time bridge transaction status and details for user interfaces.
**Key Endpoints**:
* **Testnet**: `https://api-gateway.polygon.technology/api/v3/transactions/testnet?userAddress={userAddress}`
* **Mainnet**: `https://api-gateway.polygon.technology/api/v3/transactions/mainnet?userAddress={userAddress}`
**Response Data**:
* Transaction status (pending, completed, failed) with real-time updates as transactions progress through the bridging and claiming phases
* Token details including contract addresses, transfer amounts, token symbols, and decimals for accurate display in user interfaces
* Source and destination chain information including network IDs, chain names, and block numbers where transactions were processed
* Timestamps for transaction initiation, L1 settlement, and claim completion to track transaction lifecycle timing
* Deposit count (Local Exit Tree index) required for Merkle proof generation during the claim process
**Authentication**: Requires API key in request header
**Example Usage**:
```bash theme={null}
curl --location 'https://api-gateway.polygon.technology/api/v3/transactions/mainnet?userAddress=0x...' \
--header 'x-api-key: '
```
### Proof Generation API
**Purpose**: Generates Merkle proofs required for claiming bridged assets and messages.
**Key Endpoints**:
* **Testnet**: `https://api-gateway.polygon.technology/api/v3/proof/testnet/merkle-proof?networkId={sourceNetworkId}&depositCount={depositCount}`
* **Mainnet**: `https://api-gateway.polygon.technology/api/v3/proof/mainnet/merkle-proof?networkId={sourceNetworkId}&depositCount={depositCount}`
**Parameters**:
* `networkId`: Network ID registered on Agglayer that identifies the source chain (0 for Ethereum/Sepolia, 1 for Polygon zkEVM/Cardona, etc.), used to determine which Local Exit Tree contains the transaction.
* `depositCount`: The leaf index from the source chain's Local Exit Tree (obtained from Transaction API response), which specifies exactly which transaction leaf to generate proofs for.
**Response Data**:
* `smtProofLocalExitRoot`: Merkle proof demonstrating that the specific transaction exists in the source chain's Local Exit Tree, providing the cryptographic path from the transaction leaf to the Local Exit Root.
* `smtProofRollupExitRoot`: Merkle proof demonstrating that the source chain's Local Exit Root exists in the Rollup Exit Tree on L1 (only needed for L2 to L1/L2 transactions), proving the L2's state was properly submitted to L1.
* `globalIndex`: The 256-bit Global Index that uniquely identifies this transaction within the entire network, encoding the source network type, rollup ID, and local index information.
* `mainnetExitRoot`: The Mainnet Exit Root at the time this transaction was processed, used for constructing the Global Exit Root during claim verification.
* `rollupExitRoot`: The Rollup Exit Root at the time this transaction was processed, used for constructing the Global Exit Root during claim verification.
**Authentication**: Requires API key in request header
### Auto Claim Service (only on L2s)
**Purpose**: Automated service that claims bridged transactions on destination chains.
**Key Features**:
* **Automatic Claiming**: Continuously monitors for claimable transactions across all connected chains by querying the Transaction API, detecting when transactions are ready to be claimed (L1 finalized), and automatically executing claim transactions to complete the cross-chain transfer.
* **Gas Optimization**: Optimizes gas usage for claim transactions by batching multiple claims together when possible, using dynamic gas pricing based on network conditions, and implementing efficient claim strategies to minimize transaction costs.
* **Error Handling**: Handles failed claims gracefully by implementing retry mechanisms with exponential backoff, logging detailed error information for debugging, and providing fallback strategies when primary claim methods fail.
* **Monitoring**: Provides comprehensive monitoring and alerting for claim operations including success/failure rates, processing times, gas usage statistics, and automated notifications when manual intervention is required.
**Deployment Options**:
* **DApp Integration**: Deploy as part of your dApp infrastructure to provide automatic claiming for your users, eliminating manual claim steps in cross-chain workflows.
* **Chain Integration**: Deploy as a chain-level service where the L2 operator runs the claiming service for all users, providing a public good that improves the overall user experience on that chain.
* **Standalone Service**: Deploy as an independent claiming service that can serve multiple dApps or chains, potentially monetized through small fees or operated as a community service.
**Configuration**:
* Source and destination chain RPC URLs for monitoring bridge events and submitting claim transactions across all supported networks
* Bridge contract addresses for each supported chain to interact with the correct bridge instances
* Private keys for claiming with appropriate security measures and key rotation policies
* Gas price settings including maximum gas prices, priority fees, and dynamic pricing strategies based on network congestion
# Data Structures
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/unified-bridge/data-structures
Reference for the Unified Bridge Merkle tree hierarchy: Local Exit Root, Rollup Exit Root, Mainnet Exit Root, Global Exit Root, and Global Index
## Overview
The Unified Bridge uses a hierarchical Merkle tree structure to track and verify all cross-chain transactions. Every transaction is cryptographically verifiable, and source chain transactions must be finalized on L1 before they can be claimed on the destination chain.
*Figure 1: Complete data structure hierarchy showing how Local Exit Roots, Rollup Exit Root, Mainnet Exit Root, and Global Exit Root work together*
## Local Exit Root & Local Index
Each Agglayer connected chain maintains its own Local Exit Tree (LET) that records all outgoing cross-chain transactions.
### Local Exit Tree (LET)
* **Structure**: A 32-level binary Sparse Merkle Tree that efficiently stores bridge transaction data. The tree uses a sparse representation, meaning it only stores non-zero values, making it highly efficient for storing mostly-empty trees with occasional transactions.
* **Purpose**: Records all bridge transactions initiated on the chain as cryptographic commitments. Each outgoing `bridgeAsset` or `bridgeMessage` transaction is hashed and stored as a leaf node, creating an immutable record of all cross-chain activities originating from this chain.
* **Storage**: Maintained in the `PolygonZKEVMBridgeV2.sol` contract deployed on each chain. This contract serves as both the user interface for bridge operations and the storage mechanism for the Local Exit Tree state.
* **Updates**: The tree root is recalculated and updated with each new cross-chain transaction. This ensures that the Local Exit Root always represents the current state of all bridge transactions from this chain.
### Local Index (depositCount)
* **Definition**: The sequential index of the leaf node in the Local Exit Tree, starting from 0 and incrementing by 1 for each new transaction. This creates a unique identifier for each bridge transaction within the chain's Local Exit Tree.
* **Value**: Each leaf at this index represents a Keccak256 hash of a complete cross-chain transaction, including all transaction details like destination chain, recipient address, token amount, and metadata.
* **Increment**: Automatically incremented with each new bridge transaction, ensuring that every cross-chain operation gets a unique position in the tree. This index is crucial for generating Merkle proofs during the claim process.
*Figure 2: Local Exit Tree structure showing how bridge transactions are recorded as leaves*
## Rollup Exit Root
The Rollup Exit Root (RER) is the Merkle root of all L2s' Local Exit Roots, maintained on L1.
### How it Works
1. **L2 Submission**: Connected L2s periodically submit their updated Local Exit Root to the `PolygonRollupManager.sol` contract on L1. This submission includes cryptographic proof that the Local Exit Root represents valid bridge transactions that have been properly sequenced and finalized on the L2.
2. **Frequency**: L2s have flexibility in submission timing - they can submit their Local Exit Root immediately after each bridge transaction for fastest finality, or batch multiple transactions together before submitting to optimize for L1 gas costs and throughput.
3. **RER Update**: The RollupManager validates the submitted Local Exit Root and updates the corresponding leaf in the Rollup Exit Tree. This creates a new Rollup Exit Root that represents the aggregated state of all connected L2s' bridge activities.
4. **GER Update**: When the Rollup Exit Root changes, it automatically triggers an update to the Global Exit Root in the `PolygonZkEVMGlobalExitRootV2.sol` contract, ensuring the unified state is always current.
### Key Contracts
* **PolygonRollupManager.sol**: Manages L2 state updates on L1 by validating submitted Local Exit Roots, maintaining the Rollup Exit Tree, and coordinating with the Global Exit Root contract for unified state updates.
* **PolygonZkEVMGlobalExitRootV2.sol**: Automatically updates the Global Exit Root whenever the Rollup Exit Root or Mainnet Exit Root changes, and manages the L1 Info Tree for historical GER tracking.
*Figure 3: Rollup Exit Tree showing how L2 Local Exit Roots are aggregated*
## Mainnet Exit Root
The Mainnet Exit Root (MER) tracks L1 to L2 bridge transactions, similar to how L2s track their outgoing transactions.
### How it Works
1. **L1 Bridge**: When users initiate bridge transactions from L1 to connected L2s, these transactions are recorded directly in L1's own Local Exit Tree (called Mainnet Exit Tree). This happens immediately within the same transaction that initiates the bridge operation.
2. **MER Update**: The Mainnet Exit Root is automatically updated in the `PolygonZkEVMGlobalExitRootV2.sol` contract whenever L1 bridge transactions occur. Unlike L2s, L1 doesn't need to submit its Local Exit Root separately since the Global Exit Root contract is on L1 itself.
3. **GER Update**: Any Mainnet Exit Root update immediately triggers a Global Exit Root recalculation, which then gets appended to the L1 Info Tree for L2 synchronization.
### Key Difference
* **L2s**: Must submit their Local Exit Roots to L1 via the RollupManager contract, creating a two-step process where L2 transactions are first finalized locally, then submitted to L1 for global state updates.
* **L1**: Updates its own Mainnet Exit Root directly within the Global Exit Root contract during the bridge transaction itself, eliminating the need for separate submission transactions.
*Figure 4: Mainnet Exit Tree showing how L1 bridge transactions are tracked*
## Global Exit Root
The Global Exit Root (GER) is the root hash that combines both Rollup Exit Root and Mainnet Exit Root.
### Formula
```
GER = hash(RollupExitRoot, MainnetExitRoot)
```
### L1 Info Tree
The L1 Info Tree is a 32-level binary Sparse Merkle Tree that maintains all Global Exit Roots:
* **Purpose**: Serves as a historical record of all Global Exit Root updates, enabling L2s to synchronize with specific points in time and generate valid Merkle proofs for claims. This tree is essential for the claim verification process.
* **Height**: Uses 32 levels to provide sufficient capacity for storing Global Exit Root updates over the system's lifetime. The sparse nature means only populated leaves consume storage.
* **Updates**: A new leaf is added to the tree each time the Global Exit Root changes (either from Rollup Exit Root or Mainnet Exit Root updates). Each leaf contains the new Global Exit Root value along with timestamp information.
* **Sync**: L2s periodically call the `updateExitRoot` function on their `PolygonZkEVMGlobalExitRootL2.sol` contract to fetch and synchronize with the latest Global Exit Root from L1, ensuring they can validate incoming claims.
### Global Index
The Global Index is a 256-bit identifier that uniquely locates each cross-chain transaction within the global system:
| Bits | Purpose | Description |
| -------- | ---------------- | ------------------------------------------------------------------------------------------------------ |
| 191 bits | Unused | Reserved bits typically filled with zeros for cost efficiency in storage and computation |
| 1 bit | Mainnet Flag | Indicates transaction origin: 0 = transaction from L2 rollup, 1 = transaction from L1 mainnet |
| 32 bits | Rollup Index | Identifies the specific L2 rollup within the Rollup Exit Tree (only used when mainnet flag = 0) |
| 32 bits | Local Root Index | The depositCount/leaf index within the source chain's Local Exit Tree where this transaction is stored |
This structure enables efficient lookup of any transaction across the entire network by encoding the path through the hierarchical tree structure.
*Figure 5: L1 Info Tree structure showing how Global Exit Roots are maintained*
## Data Flow
### Flow for L1 -> L2 Bridge Asset
1. User/Developer/Dapp initiate `bridgeAsset` call on L1
2. Bridge contract on L1 appends an exit leaf to mainnet exit tree of the L1, and update its mainnet exit root.
3. Global exit root manager appends the new L1 mainnet exit root to global exit tree and computes the new global exit root.
4. L2 sequencer fetches and updates the latest global exit root from the global exit root manager.
5. User/Developer/Dapp/Chain initiates `claimAsset` call, and also provides the smtProof.
6. Bridge contract on destination L2 chain validates the smtProof against the global exit root on its chain. If passes next step.
7. Transfer/Mint the asset to the destination address.
### Flow for L2 -> L1 Bridge Message
1. User/Developer/Dapp initiate `bridgeMessage` call on L2
2. Bridge contract on L2 appends an exit leaf to local exit tree of the L2, and update its local exit root on L2.
3. Sends the new local exit root to L1 to verify, once passed the L2's local exit root, aka the leaf node in the rollup exit tree will be updated, which will cause a chain of updates to Global exit root updates on L1 and also L1InfoTree updates.
4. User/Developer/Dapp/Chain initiates `claimMessage` call, and also provides the smtProof.
5. Bridge contract on destination L1 chain validates the smtProof against the global exit root on its chain. If passes next step.
6. Execute `onMessageReceived` process.
# Message Bridging
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/unified-bridge/message-bridging
How message bridging works in the Unified Bridge: bridgeMessage/claimMessage function signatures, the IBridgeMessageReceiver interface, and execution flows
## Overview
Message bridging enables smart contracts on different chains to communicate and trigger execution across chains. A contract on the source chain calls `bridgeMessage` with encoded function data, and the bridge executes that data on the destination contract when the message is claimed.
*Figure 1: Complete message bridging flow from L2 to L1*
## Message Model
### What Message Bridging Supports
* **Contract-to-contract communication**: Smart contracts can trigger function calls on other chains
* **Cross-chain state updates**: Contracts can update state on destination chains
* **Trustless communication**: Cryptographic verification of all cross-chain messages via Merkle proofs
### Message Structure
Cross-chain messages contain:
* **Destination contract**: Address of the contract to execute on the destination chain
* **Function data**: Encoded function call data
* **Value**: ETH value to send with the message (if any)
* **Gas limit**: Maximum gas for execution on the destination chain
* **Metadata**: Additional data for the message
## Bridge Message Function
The `bridgeMessage` function initiates message transfers between chains.
### Function Signature
```solidity theme={null}
function bridgeMessage(
uint32 destinationNetwork,
address destinationAddress,
uint256 gasLimit,
bytes calldata data
) external payable
```
### Parameters
* **`destinationNetwork`**: Network ID of the destination chain
* **`destinationAddress`**: Address of the contract to execute on destination chain
* **`gasLimit`**: Maximum gas for execution on destination chain
* **`data`**: Encoded function call data
### Process Steps
1. **Validation**: Check destination network is not the source network
2. **Value Handling**: Handle ETH value if provided
3. **Event Emission**: Emit `BridgeEvent` with message details
4. **Tree Update**: Add message to Local Exit Tree as leaf node
### Example Usage
```solidity theme={null}
// Bridge a message to call a function on destination chain
bridgeMessage(
1, // destinationNetwork (L2)
0x..., // destinationAddress (contract address)
100000, // gasLimit
abi.encodeWithSignature("updateValue(uint256)", 123) // data
);
```
## Claim Message Function
The `claimMessage` function claims and executes bridged messages on the destination chain.
### Function Signature
```solidity theme={null}
function claimMessage(
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot,
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot,
uint256 globalIndex,
bytes32 mainnetExitRoot,
bytes32 rollupExitRoot,
uint32 originNetwork,
address originAddress,
uint32 destinationNetwork,
address destinationAddress,
uint256 gasLimit,
bytes calldata data
) external
```
### Parameters
* **`smtProofLocalExitRoot`**: Merkle proof for Local Exit Root
* **`smtProofRollupExitRoot`**: Merkle proof for Rollup Exit Root
* **`globalIndex`**: Global index identifying the message
* **`mainnetExitRoot`**: Mainnet Exit Root at time of message
* **`rollupExitRoot`**: Rollup Exit Root at time of message
* **`originNetwork`**: Network ID of source chain
* **`originAddress`**: Address that sent the message
* **`destinationNetwork`**: Network ID of destination chain
* **`destinationAddress`**: Address of the contract to execute
* **`gasLimit`**: Maximum gas for execution
* **`data`**: Encoded function call data
### Process Steps
1. **Validation**: Verify destination network matches current chain
2. **Proof Verification**: Verify Merkle proofs against Global Exit Root
3. **Duplicate Check**: Ensure message hasn't been claimed before
4. **Message Execution**: Execute the message on destination contract
5. **Claim Record**: Mark message as claimed
### Message Execution
```solidity theme={null}
// Execute the message on destination contract
(bool success, bytes memory returnData) = destinationAddress.call{
value: msg.value,
gas: gasLimit
}(data);
require(success, "Message execution failed");
```
**Important Notes:**
* Messages can only be executed if the `destinationAddress` is a smart contract that implements the `IBridgeMessageReceiver` interface
* If the receiving address is an EOA, the call will result as a success, meaning that the amount of ether will be transferred correctly, but the message will not trigger any execution
* If the native gas token is `ETH`, then transfer `ETH` to the `destinationAddress` and execute the message
* If `ETH` is not the native gas token, then mint `WETH` to the `destinationAddress` and execute the message
### IBridgeMessageReceiver Interface
For a contract to receive bridged messages, it must implement the `IBridgeMessageReceiver` interface:
```solidity theme={null}
interface IBridgeMessageReceiver {
function onMessageReceived(
address originAddress,
uint32 originNetwork,
bytes calldata data
) external payable;
}
```
**Parameters:**
* `originAddress`: Address that sent the message on the source chain
* `originNetwork`: Network ID of the source chain
* `data`: The message data/metadata sent from source chain
## Bridging Flows
### L1 to L2 Message Bridging
### L2 to L1 Message Bridging
### L2 to L2 Message Bridging
# What is Agglayer?
Source: https://docs.polygon.technology/interoperability/agglayer/core-concepts/what-is-agglayer
CDK's interoperability layer: how it addresses blockchain fragmentation and what it enables for CDK chains
Agglayer is the interoperability layer built into every Polygon Chain Development Kit (CDK) chain. When you deploy a CDK chain, it connects to Agglayer by default — the cross-chain capabilities described here come as part of the infrastructure, not as a separate integration step.
## The Blockchain Fragmentation Problem
Today's blockchain ecosystem is fragmented by design. Ethereum provides well-established security and a deep DeFi ecosystem. Polygon offers fast transactions and low fees. Arbitrum uses optimistic rollup technology. Each chain has evolved to solve specific problems, but they exist in isolation.
The result is that users must manage multiple wallets, developers rebuild the same functionality across chains, and assets remain trapped in their respective ecosystems. Moving ETH from Ethereum into a DeFi protocol on Polygon requires bridging, waiting for confirmations, dealing with wrapped tokens, and accepting the risk of multi-step cross-chain operations.
## What Agglayer Is
Agglayer is CDK's built-in interoperability layer. Rather than building bridges between specific pairs of chains, it creates shared infrastructure where:
* **Assets maintain their identity across chains.** No wrapped tokens. ETH on Ethereum is the same ETH you use on a connected chain.
* **Operations are atomic across multiple chains.** A cross-chain transaction either succeeds on all involved chains or fails entirely, with no partial states.
* **Security is enforced mathematically.** Cryptographic proofs replace trusted validators. The system assumes any chain could be compromised and builds constraints around that assumption.
* **Chains remain sovereign.** Connected chains keep their own architecture, consensus rules, and governance. Agglayer provides interoperability without requiring chains to give up independence.
## How Agglayer Works
### The Three Security Gateways
**Gateway 1: The Unified Bridge**
The Unified Bridge is where cross-chain transactions execute. When assets or messages move between chains, the Unified Bridge handles cryptographic verification and state management using Merkle tree structures that track every cross-chain operation. Transactions are settled on Ethereum before they can be claimed on destination chains.
**Gateway 2: Pessimistic Proof**
Rather than assuming all chains are honest, Agglayer assumes they might be compromised. The Pessimistic Proof system mathematically enforces that even if a chain's prover is unsound, it cannot withdraw more funds than are currently deposited on that chain. A compromised chain's damage is limited to its own deposits and cannot spread to the rest of the network.
**Gateway 3: State Transition Proof**
Introduced in Agglayer v0.3, the State Transition Proof adds a second verification layer that validates individual chain operations before cross-chain proofs are applied. This ensures both that a chain is operating correctly internally and that cross-chain operations are secure.
### Why Distrust Is the Foundation
Traditional bridges require trust: a multi-signature wallet, a validator set, or a smart contract with administrative keys. There is always a point of centralized risk.
Agglayer inverts this model. It starts from the assumption that any connected chain could be compromised and then enforces mathematical constraints that bound the damage:
* Compromised provers cannot drain more than their chain's deposits.
* Cryptographic proofs verify every operation rather than relying on authoritative validation.
* Problems on one chain cannot spread to others because each chain's state is verified independently.
## Why This Matters for CDK Chains
### For chain operators
Your CDK chain participates in a shared network by default. Assets on your chain can move to and from other connected chains without bridging infrastructure you need to build or maintain. Cross-chain liquidity and connectivity come with the deployment.
### For developers building on CDK chains
Applications can use the best properties of each chain in the network. High-value operations can rely on Ethereum's security. Frequent transactions can use chains optimized for throughput. Domain-specific chains can handle specialized functionality. All of this works within a single application without per-chain integration overhead.
### For users
Cross-chain operations become comparable in complexity to single-chain operations. The underlying routing, proof generation, and state verification happen at the protocol level rather than requiring manual steps from the user.
## Technical Components
Agglayer coordinates several systems:
* **Multiple cryptographic proof systems**: SP1 zkVM, Pessimistic Proofs, State Transition Proofs
* **State synchronization** across connected chains
* **Mathematical verification** of every cross-chain operation
* **Hierarchical Merkle tree structures** (Local Exit Trees, Rollup Exit Tree, Global Exit Root) for efficient verification
For detailed coverage of each component, see [Architecture](/interoperability/agglayer/core-concepts/architecture/), [Unified Bridge](/interoperability/agglayer/core-concepts/unified-bridge/), [Pessimistic Proof](/interoperability/agglayer/core-concepts/pessimistic-proof/), and [State Transition Proof](/interoperability/agglayer/core-concepts/state-transition-proof/).
# Get Started
Source: https://docs.polygon.technology/interoperability/agglayer/get-started/index
Build cross-chain applications on AggLayer-connected CDK chains.
## Build Cross-Chain Applications on CDK
AggLayer is built into every Chain Development Kit (CDK) chain. You can work with AggLayer's cross-chain capabilities in development and production without setting up separate interoperability infrastructure.
### What you can build on CDK chains
* **Atomic cross-chain operations**: Execute operations across multiple chains in a single transaction
* **Unified asset management**: Build applications where users' assets work across all supported chains
* **Cross-chain smart contracts**: Create contracts that trigger actions and transfer value across different networks
* **Multi-chain interfaces**: Build UIs that abstract chain selection away from the user
## Next steps
* **[Supported chains](/interoperability/agglayer/supported-chains/)**: See which chains are connected to AggLayer, with RPC endpoints and chain IDs.
* **[Integrations](/interoperability/agglayer/integrations/)**: Use Bridge Hub or the AggKit Bridge Service to query bridge data, generate claim proofs, and automate claiming across chains.
# Overview
Source: https://docs.polygon.technology/interoperability/agglayer/index
Agglayer connects chains with shared liquidity, atomic cross-chain transactions, and cryptographic security.
## Overview
Agglayer is Polygon's interoperability layer. It connects chains so assets can move without wrapping, operations can be atomic across chains, and security is enforced through cryptographic proofs rather than trusted parties.
CDK chains connect to Agglayer by default. Other chains can integrate independently.
**What Agglayer provides:**
* **Unified liquidity**: Assets maintain their identity across connected chains. No wrapped tokens required.
* **Atomic cross-chain operations**: Transactions either succeed on all involved chains or fail entirely — no partial states.
* **Mathematical security**: Pessimistic proofs ensure a compromised chain cannot drain more than its own deposits. Damage is contained.
* **Chain sovereignty**: Connected chains keep their own architecture and governance. Agglayer adds interoperability without requiring changes to how a chain operates.
## Explore Agglayer
Understand how Agglayer works: the three security gateways, proof systems, and how chains connect to the network.
Start building cross-chain applications on Agglayer-connected chains.
APIs and services for integrating Agglayer bridge functionality into your applications.
# API Reference
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/aggkit-bridge-service/api-reference
REST API documentation for the AggKit Bridge Service: endpoints, parameters, and response formats.
## API Base URL
All Bridge Service endpoints follow this structure:
```
{base_url}/bridge/v1/{endpoint}
```
## Health & Status
### `GET /` - Service Health Check
**Example Request:**
```bash theme={null}
curl "http://localhost:5577/"
```
**Response:**
```json theme={null}
{
"status": "healthy",
"version": "1.0.0",
"network_id": 1,
"uptime": "2h15m30s",
"last_processed_block": 15234567
}
```
**Response Fields:**
* `status`: Service health indicator
* `version`: API version for compatibility checks
* `network_id`: Primary network being served
* `uptime`: Service runtime duration
* `last_processed_block`: Latest indexed block number
### `GET /sync-status` - Component Synchronization Status
**Example Request:**
```bash theme={null}
curl "http://localhost:5577/bridge/v1/sync-status"
```
**Response:**
```json theme={null}
{
"l1_sync": {
"current_block": 15234567,
"synced_block": 15234560,
"is_synced": true
},
"l2_sync": {
"current_block": 8765432,
"synced_block": 8765432,
"is_synced": true
},
"bridge_sync": {
"l1_bridges_indexed": 12345,
"l2_bridges_indexed": 6789,
"last_update": "2024-09-24T10:30:45Z"
}
}
```
## Bridge Transaction Queries
### `GET /bridges` - Query Bridge Transactions
**Parameters:**
| Parameter | Type | Required | Description | Example |
| --------------- | ------- | -------- | ------------------------------------ | ----------- |
| `network_id` | integer | ✅ | Origin network ID (0=L1, 1=L2, etc.) | `0` |
| `page_number` | integer | ❌ | Page number (default: 1) | `1` |
| `page_size` | integer | ❌ | Page size (default: 100, max: 1000) | `10` |
| `deposit_count` | integer | ❌ | Filter by specific deposit count | `42` |
| `from_address` | string | ❌ | Filter by sender address | `0xf39F...` |
| `network_ids` | array | ❌ | Filter by destination network IDs | `1,2` |
**Example Request Patterns:**
```bash theme={null}
# Get recent bridges for a user
curl "http://localhost:5577/bridge/v1/bridges?network_id=0&from_address=0xf39F...&page_size=10"
# Find specific transaction by deposit count
curl "http://localhost:5577/bridge/v1/bridges?network_id=0&deposit_count=42"
# Check ready-to-claim transactions
curl "http://localhost:5577/bridge/v1/bridges?network_id=0&ready_for_claim=true"
```
**Response Structure:**
```json theme={null}
{
"bridges": [
{
"tx_hash": "0x8d1b60d0eaab6f609955bdd371e8004f47349cc809ff1bee81dc9d37237a031c",
"deposit_count": 42,
"origin_network": 0,
"destination_network": 1,
"origin_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"destination_address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"amount": "1000000000000000000",
"block_timestamp": 1695563045,
"ready_for_claim": true
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 150
}
}
```
**Key Response Fields:**
* `tx_hash`: Bridge transaction identifier
* `deposit_count`: Sequential index for proof generation
* `amount`: Bridge amount in wei/token units
* `ready_for_claim`: Whether transaction can be claimed
* `block_timestamp`: When the bridge occurred
## Claim Tracking
### `GET /claims` - Query Claim Transactions
**Key Parameters:**
| Parameter | Type | Required | Description |
| -------------- | ------- | -------- | ------------------------------- |
| `network_id` | integer | ✅ | Network where claims occurred |
| `from_address` | string | ❌ | Filter by claimer address |
| `page_size` | integer | ❌ | Results per page (default: 100) |
**Example Usage:**
```bash theme={null}
# Check claims on a specific network
curl "http://localhost:5577/bridge/v1/claims?network_id=1&page_size=10"
# Find claims by specific user
curl "http://localhost:5577/bridge/v1/claims?network_id=1&from_address=0x7099..."
```
**Response:**
```json theme={null}
{
"claims": [
{
"tx_hash": "0xa9fa5418144f7c8c1b78cd0e5560d6550411667ef937b554636a613f933b3d9f",
"global_index": "0x000000000000000000000000000000000000000000000000000000000000002a",
"amount": "1000000000000000000",
"block_timestamp": 1695563145,
"bridge_tx_hash": "0x8d1b60d0eaab6f609955bdd371e8004f47349cc809ff1bee81dc9d37237a031c"
}
]
}
```
## Proof Generation
### `GET /claim-proof` - Generate Claim Proof
Generates the cryptographic proofs required to claim bridged assets. Pass the entire `proof` object returned to your claim contract function.
**Required Parameters:**
| Parameter | Type | Description | Example |
| --------------- | ------- | ------------------------------------ | ------- |
| `network_id` | integer | Origin network of bridge transaction | `0` |
| `deposit_count` | integer | Deposit count from bridge event | `42` |
| `leaf_index` | integer | L1 Info Tree index for proof | `15` |
**Example Request:**
```bash theme={null}
curl "http://localhost:5577/bridge/v1/claim-proof?network_id=0&deposit_count=42&leaf_index=15"
```
**Response Structure:**
```json theme={null}
{
"proof": {
"smtProofLocalExitRoot": ["0x...", "0x...", "0x..."],
"smtProofRollupExitRoot": ["0x...", "0x..."],
"l1InfoTreeLeaf": {
"globalExitRoot": "0x...",
"blockNumber": 15234567,
"timestamp": 1695563045
}
}
}
```
## Additional Utilities
### `GET /l1-info-tree-index` - Get L1 Info Tree Index
Returns the L1 Info Tree index required for the `leaf_index` parameter in `/claim-proof`.
```bash theme={null}
curl "http://localhost:5577/bridge/v1/l1-info-tree-index?network_id=0&deposit_count=42"
```
**Response:**
```json theme={null}
{
"l1_info_tree_index": 15,
"block_number": 15234567,
"global_exit_root": "0x..."
}
```
### `GET /token-mappings` - Token Information
Get token mapping information for cross-chain token relationships.
**Use Case**: Display token information and cross-chain mappings in bridge interfaces.
```bash theme={null}
curl "http://localhost:5577/bridge/v1/token-mappings"
```
## Error Handling
**Common Error Scenarios:**
| Error Code | When It Happens | What To Do |
| --------------------- | ---------------------------- | ----------------------------------------- |
| `INVALID_NETWORK_ID` | Using unsupported network ID | Check supported networks via `/` endpoint |
| `BRIDGE_NOT_FOUND` | Transaction not yet indexed | Wait for indexing to complete |
| `PROOF_NOT_AVAILABLE` | L1 finality not reached | Wait for L1 confirmation |
| `RATE_LIMIT_EXCEEDED` | Too many requests | Implement backoff and retry |
**Example Error Response:**
```json theme={null}
{
"error": {
"code": "INVALID_NETWORK_ID",
"message": "Network ID 999 is not supported"
}
}
```
# AggKit Bridge Service
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/aggkit-bridge-service/index
REST API for bridge data, transaction status, and proof generation without running AggKit infrastructure.
**Source code:** [github.com/agglayer/aggkit](https://github.com/agglayer/aggkit)
## Overview
The Bridge Service API is a REST API that provides access to bridge data from AggKit: transaction histories, claim statuses, cryptographic proofs, and network information. It exposes this data through simple HTTP endpoints, removing the need to run and maintain blockchain indexers or proof generation infrastructure.
All endpoints follow the pattern:
```
{base_url}/bridge/v1/{endpoint}
```
No authentication is required for most operations.
## What the API provides
* **Bridge transaction data**: Query bridge transactions by address, network, or transaction hash
* **Claim status**: Check whether a bridge transaction is ready to claim
* **Proof generation**: Retrieve the cryptographic proof required for claim transactions
* **Network information**: Access bridge contract addresses and network configuration
## Quick start
```bash theme={null}
# Check if the API is running
curl "http://localhost:5577/bridge/v1/"
# Get recent bridge transactions on a network
curl "http://localhost:5577/bridge/v1/bridges?network_id=0&page_size=5"
# Get bridges for a specific address
curl "http://localhost:5577/bridge/v1/bridges?network_id=0&from_address=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
# Check for transactions ready to claim
curl "http://localhost:5577/bridge/v1/bridges?network_id=0&from_address=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266&ready_for_claim=true"
# Generate a claim proof
curl "http://localhost:5577/bridge/v1/claim-proof?network_id=0&deposit_count=42&leaf_index=15"
```
## Reference
All endpoints, parameters, and response formats.
# API Reference
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/bridge-hub/api-reference
Bridge Hub REST API: endpoints, parameters, and response formats for querying bridge transactions.
The Bridge Hub API exposes interactive OpenAPI documentation at the `/docs` endpoint. You can browse endpoints and test API calls directly in the browser.
The Bridge Hub API is a read-only REST API. All endpoints use the `GET` method, and all responses are returned as `application/json`.
## GET /transactions
Query bridge transactions with filtering and cursor-based pagination.
### Parameters
| Parameter | Type | Required | Description |
| ----------------------- | ------- | -------- | -------------------------------------------------------- |
| `status` | string | No | Filter by status: `BRIDGED`, `READY_TO_CLAIM`, `CLAIMED` |
| `sourceNetworkIds` | string | No | Comma-separated source network IDs |
| `destinationNetworkIds` | string | No | Comma-separated destination network IDs |
| `fromAddress` | string | No | Filter by sender address |
| `receiverAddress` | string | No | Filter by receiver address |
| `limit` | integer | No | Results per page (default: 50) |
| `startAfter` | string | No | Cursor for pagination |
### Example request
```bash theme={null}
curl "https://bridge-hub.example.com/transactions?status=READY_TO_CLAIM&limit=2"
```
### Example response
```json theme={null}
{
"data": [
{
"hubUID": "tx-0001",
"sourceNetwork": 1,
"destinationNetwork": 2442,
"transactionHash": "0xabc123...",
"blockNumber": 19500000,
"timestamp": 1710000000,
"leafType": "ASSET",
"originTokenNetwork": 1,
"originTokenAddress": "0x0000000000000000000000000000000000000000",
"receiverAddress": "0xdef456...",
"fromAddress": "0x789abc...",
"amount": "1000000000000000000",
"depositCount": 42,
"leafIndexForProof": 42,
"globalIndex": "42",
"status": "READY_TO_CLAIM",
"lastUpdatedAt": 1710000300,
"claimTransactionHash": null,
"claimBlockNumber": null,
"claimTimestamp": null
}
],
"nextStartAfterCursor": "eyJsYXN0SWQiOiJ0eC0wMDAxIn0="
}
```
### Response fields
| Field | Type | Description |
| ---------------------- | -------------- | ---------------------------------------------- |
| `hubUID` | string | Unique business key for the transaction |
| `sourceNetwork` | number | Source chain ID |
| `destinationNetwork` | number | Destination chain ID |
| `transactionHash` | string | Source transaction hash |
| `blockNumber` | number | Block number on the source chain |
| `timestamp` | number | Unix timestamp of the bridge event |
| `leafType` | string | `ASSET` or `MESSAGE` |
| `originTokenNetwork` | number | Network ID where the token originates |
| `originTokenAddress` | string | Token contract address on the origin network |
| `receiverAddress` | string | Address that will receive funds on destination |
| `fromAddress` | string | Sender address on the source chain |
| `amount` | string | Transfer amount (BigInt encoded as string) |
| `depositCount` | number | Deposit counter from the bridge event |
| `leafIndexForProof` | number | Leaf index in the merkle tree |
| `globalIndex` | string | Global index (encoded as string) |
| `status` | string | `BRIDGED`, `READY_TO_CLAIM`, or `CLAIMED` |
| `lastUpdatedAt` | number | Unix timestamp of last status update |
| `claimTransactionHash` | string or null | Claim transaction hash (populated after claim) |
| `claimBlockNumber` | number or null | Block number of the claim transaction |
| `claimTimestamp` | number or null | Unix timestamp of the claim |
## GET /claim-proof
Generate a merkle proof for claiming a bridge transaction. This endpoint proxies to the source chain's AggKit Bridge Service to retrieve the proof data needed to submit a claim on the destination chain.
### Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------- | -------- | --------------------------------- |
| `sourceNetworkId` | integer | Yes | Source chain network ID |
| `depositCount` | integer | Yes | Deposit counter from bridge event |
| `leafIndex` | integer | Yes | Leaf index in merkle tree |
### Example request
```bash theme={null}
curl "https://bridge-hub.example.com/claim-proof?sourceNetworkId=1&depositCount=42&leafIndex=42"
```
### Example response
```json theme={null}
{
"proof_local_exit_root": ["0x...", "0x..."],
"proof_rollup_exit_root": ["0x...", "0x..."],
"l1_info_tree_leaf": {
"l1InfoTreeIndex": 100,
"rer": "0x...",
"mer": "0x...",
"innerBlock": 19500000,
"innerTimestamp": 1710000000
},
"bridge_tx_metadata": "0x..."
}
```
### Response fields
| Field | Type | Description |
| ------------------------ | --------- | ------------------------------------------------- |
| `proof_local_exit_root` | string\[] | Merkle proof siblings for the local exit root |
| `proof_rollup_exit_root` | string\[] | Merkle proof siblings for the rollup exit root |
| `l1_info_tree_leaf` | object | L1 info tree leaf data used in claim verification |
| `bridge_tx_metadata` | string | Encoded metadata for the bridge transaction |
## GET /token-mappings
Get token address mappings between networks. Returns the relationship between original token addresses and their wrapped counterparts on destination networks.
### Example request
```bash theme={null}
curl "https://bridge-hub.example.com/token-mappings"
```
### Example response
```json theme={null}
{
"data": [
{
"originNetwork": 1,
"originTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"wrappedTokenAddress": "0x37eAA0eF3549a5bB7D431be78a3D99BD6fd6b57D",
"destinationNetwork": 2442
}
]
}
```
## GET /token-metadata
Get token metadata including name, symbol, and decimals for tokens tracked by Bridge Hub.
### Example request
```bash theme={null}
curl "https://bridge-hub.example.com/token-metadata"
```
### Example response
```json theme={null}
{
"data": [
{
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"networkId": 1,
"name": "USD Coin",
"symbol": "USDC",
"decimals": 6
}
]
}
```
## GET /health-check
Returns the service health status.
### Example request
```bash theme={null}
curl "https://bridge-hub.example.com/health-check"
```
### Example response
```json theme={null}
{
"status": "success",
"data": {
"status": "success",
"message": "All services are working correctly"
}
}
```
## Pagination
The Bridge Hub API uses cursor-based pagination for the `/transactions` endpoint.
1. Make an initial request with an optional `limit` parameter.
2. If more results exist, the response includes a `nextStartAfterCursor` value.
3. Pass that value as the `startAfter` parameter in your next request to retrieve the following page.
```bash theme={null}
# First page
curl "https://bridge-hub.example.com/transactions?limit=50"
# Next page (using the cursor from the previous response)
curl "https://bridge-hub.example.com/transactions?limit=50&startAfter=eyJsYXN0SWQiOiJ0eC0wMDUwIn0="
```
Cursor-based pagination provides several benefits over offset-based approaches:
* **Stable results.** Concurrent inserts or updates do not cause skipped or duplicated entries between pages.
* **Consistent performance.** Fetching page 1,000 is as fast as fetching page 1, since the database seeks directly to the cursor position.
* **No deep pagination issues.** There is no growing cost as you move further through the result set.
## Error handling
The API uses standard HTTP status codes and returns structured error responses.
### Status codes
| Code | Meaning | Description |
| ---- | ------------ | ------------------------------------------ |
| 200 | Success | Request completed successfully |
| 400 | Bad Request | Invalid or missing query parameters |
| 404 | Not Found | Requested resource does not exist |
| 500 | Server Error | An unexpected error occurred on the server |
### Error response format
All error responses follow a consistent structure:
```json theme={null}
{
"status": "error",
"message": "Invalid query parameter: status must be one of BRIDGED, READY_TO_CLAIM, CLAIMED"
}
```
Error messages describe the problem without exposing sensitive internal details. If you receive a `500` response, retry the request after a short delay. For persistent errors, check the `/health-check` endpoint to verify service availability.
# Architecture
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/bridge-hub/architecture
Bridge Hub system architecture: consumer internals, multi-network deployment, and database design.
## System architecture
The Bridge Hub is a microservices-based system that monitors, indexes, exposes, and automatically claims cross-chain bridge transactions. It consists of four packages that work together around a shared MongoDB database.
```
┌──────────────────────────────────────────────────────────────────┐
│ External Systems │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Aggkit │ │ Blockchain │ │ MongoDB │ │
│ │ Bridge │ │ │ │ Database │ │
│ │ service │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼─────────────────┼─────────────────┼────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ Bridge Hub Packages │
│ │
│ ┌───────────────┐ │
│ │ COMMONS │ │
│ │ (Shared Types)│ │
│ └───────┬───────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌─────────┐ ┌───────────┐ │
│ │ CONSUMER │ │ API │ │AUTO-CLAIM │ │
│ │ (Indexer)│ │(Service)│ │(Claimer) │ │
│ └─────┬────┘ └────┬────┘ └─────┬─────┘ │
│ │ │ │ │
│ │ Writes │ Reads │ HTTP │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────┐ ┌─────────────┐ │
│ │ MongoDB Database │ │ Blockchain │ │
│ └──────────────────────────┘ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
```
The four packages serve distinct roles:
| Package | Layer | Responsibility |
| -------------- | -------------- | -------------------------------------------------------------------------------- |
| **Commons** | Foundation | Shared TypeScript types, interfaces, and schemas used by all other packages |
| **Consumer** | Data ingestion | Polls AggKit Bridge Service APIs to index bridge transactions into MongoDB |
| **API** | Service | Exposes indexed transaction data and proxies claim proofs over a REST interface |
| **Auto-Claim** | Automation | Polls the API for claimable transactions and submits claim transactions on-chain |
**Commons** acts as the foundation layer. It contains pure TypeScript types with no runtime code, providing type-safe contracts that the other three packages depend on. **Consumer** writes data into MongoDB. **API** reads from the same database and serves it to clients. **Auto-Claim** consumes the API over HTTP and interacts with the blockchain to finalize claims.
## Consumer internals
The Consumer package runs as a single Node.js process per network. It contains two components that together run four cron jobs.
### BridgeAPIConsumer
This component runs three cron jobs that fetch data from the AggKit Bridge Service:
1. **bridgesCron**: Polls AggKit for new bridge deposit transactions. Each new deposit is inserted into the `transactions` collection with a status of `BRIDGED`. The cron tracks its progress by updating `lastIndexedBridgeDepositCount` in the metadata collection.
2. **claimsCron**: Polls AggKit for claim events that have occurred on-chain. When a claim is detected, the corresponding transaction is updated to `CLAIMED` and the `claimTransactionHash` and timestamp are recorded. Progress is tracked via `lastIndexedClaimBlockNumber`.
3. **mappingsCron**: Polls AggKit for token mapping events. New or updated mappings are upserted into the `mappings` collection. Progress is tracked via `lastIndexedMappingBlockNumber`.
### ClaimReadinessConsumer
This component runs a single cron job:
4. **readyToClaimCron**: Checks the L1 info tree data from AggKit and compares it against transactions that are currently in `BRIDGED` status. When a transaction becomes claimable, the cron updates it to `READY_TO_CLAIM` and sets the `leafIndexForProof` field needed for merkle proof generation.
### Consumer data flow
```
Aggkit Bridge Service (per network)
│
├── /bridges API ────▶ bridgesCron ────▶ transactions collection (BRIDGED)
│
├── /claims API ─────▶ claimsCron ─────▶ transactions collection (CLAIMED)
│
├── /mappings API ───▶ mappingsCron ───▶ mappings collection
│
└── /l1-info-tree ───▶ readyToClaimCron ▶ transactions collection (READY_TO_CLAIM)
```
All four cron jobs run at configured intervals within the same process. The metadata collection tracks each cron's indexing checkpoint so the consumer can resume from the correct position after a restart.
## Multi-network deployment
In production, the Bridge Hub runs **one consumer instance per source network** being indexed. Each network connected to the AggLayer has a unique network ID (for example, 0 for Ethereum, 1 for Polygon zkEVM). A single shared API service reads from the database and serves all networks, while one auto-claim instance runs per destination network.
The consumer does **not** directly monitor the blockchain. It polls the AggKit Bridge Service APIs to fetch already-indexed data. Each chain's AggKit Bridge Service is maintained by the chain operators and is external to the Bridge Hub deployment.
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ AGGLAYER HUB API CLUSTER │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────────────────────┐ │
│ │ Aggkit │ │ netId_0 Consumer │ │
│ │ Bridge │──────▶ │ ┌───────────────────────────┐ │ │
│ │ Service │ │ │ bridgesCron │ │ │
│ │ (net 0) │ │ │ claimsCron │ │──┐ │
│ └─────────────┘ │ │ readyToClaimCron │ │ │ │
│ │ │ mappingsCron │ │ │ │
│ │ └───────────────────────────┘ │ │ │
│ └─────────────────────────────────┘ │ │
│ │ │
│ ┌─────────────┐ ┌─────────────────────────────────┐ │ │
│ │ Aggkit │ │ netId_1 Consumer │ │ │
│ │ Bridge │──────▶ │ ┌───────────────────────────┐ │ │ │
│ │ Service │ │ │ bridgesCron │ │ │ │
│ │ (net 1) │ │ │ claimsCron │ │──┤ │
│ └─────────────┘ │ │ readyToClaimCron │ │ │ │
│ │ │ mappingsCron │ │ │ │
│ │ └───────────────────────────┘ │ │ │
│ └─────────────────────────────────┘ │ │
│ │ │
│ ┌─────────────┐ ┌─────────────────────────────────┐ │ │
│ │ Aggkit │ │ netId_n Consumer │ │ │
│ │ Bridge │──────▶ │ ┌───────────────────────────┐ │ │ │
│ │ Service │ │ │ bridgesCron │ │ │ │
│ │ (net n) │ │ │ claimsCron │ │──┤ │
│ └─────────────┘ │ │ readyToClaimCron │ │ │ │
│ │ │ mappingsCron │ │ │ │
│ │ └───────────────────────────┘ │ │ │
│ └─────────────────────────────────┘ │ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────┐ │
│ │ MongoDB Database │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Collections (per environment): │ │ │
│ │ │ • bridge_hub_api_transactions │ │ │
│ │ │ • bridge_hub_api_mappings │ │ │
│ │ │ • bridge_hub_api_metadata │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ Reads from │
│ ┌────────────────────────────────────────────┐ │
│ │ API Service │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ /transactions │ │ │
│ │ │ /token-mappings │ │ │
│ │ │ /token-metadata │ │ │
│ │ │ /claim-proof (proxies to Aggkit) │ │───┐ │
│ │ └──────────────────────────────────────┘ │ │ │
│ └────────────────────────────────────────────┘ │ │
│ │ │
│ HTTP Calls │ │
│ ┌─────────────────────────────────────────────┐ │ │
│ │ Auto-Claim Service (per dest network) │◀─┘ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Polls /transactions │ │ │
│ │ │ Fetches /claim-proof │ │ │
│ │ │ Submits claims to blockchain │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
```
Key deployment points:
* **Consumer instances**: One per source network being indexed (`netId_0`, `netId_1`, and so on).
* **Shared database**: All consumers write to the same MongoDB instance.
* **Single API**: One API service reads from the database and serves all networks.
* **Auto-Claim deployment**: One instance per destination network you want to auto-claim for.
## Database design
Bridge Hub uses a single shared MongoDB instance. Collections are organized by environment using the naming convention `bridge_hub_api_[type]_[environment]`, where the environment suffix is omitted for mainnet, `_testnet` for testnet, and `_devnet` for development.
### Collections
The database contains three collection types:
1. **transactions**: Stores all bridge transactions across all networks. Modified by `bridgesCron` (upserts), `claimsCron` (status updates), and `readyToClaimCron` (status updates). Transactions move through the statuses `BRIDGED`, `LEAF_INCLUDED`, `READY_TO_CLAIM`, and `CLAIMED`.
2. **mappings**: Stores token address mappings between AggLayer networks. Modified by `mappingsCron`. Each record maps an origin token address on one network to its wrapped token address on another.
3. **metadata**: Tracks indexing progress per network. Each cron job updates its own checkpoint field in this collection. One document exists per network ID being indexed.
### Transaction document schema
```javascript theme={null}
{
_id: ObjectId, // MongoDB primary key
hubUID: String (unique), // Business key
// Network Information
sourceNetwork: Number, // Source chain ID
destinationNetwork: Number, // Destination chain ID
// Transaction Details
transactionHash: String, // Source transaction hash
blockNumber: Number, // Block number on source
timestamp: Number, // Unix timestamp
bridgeHash: String,
// Bridge Details
leafType: String, // "ASSET" or "MESSAGE"
originTokenNetwork: Number,
originTokenAddress: String,
receiverAddress: String,
fromAddress: String,
amount: String, // BigInt as string
depositCount: Number, // Deposit counter
// Claiming Details
leafIndexForProof: Number, // Index for merkle proof
globalIndex: String, // Global index as string
// Status Tracking
status: String, // BRIDGED, LEAF_INCLUDED, READY_TO_CLAIM, CLAIMED
lastUpdatedAt: Number, // Last update timestamp
// Claim Information (populated after claim)
claimTransactionHash: String,
claimBlockNumber: Number,
claimTimestamp: Number
}
```
### Indexes
```javascript theme={null}
{
hubUID: 1 // Unique index
status: 1, // Query by status
{ sourceNetwork: 1, destinationNetwork: 1 }, // Filter by networks
depositCount: 1, // Order by deposit count
{ status: 1, destinationNetwork: 1 } // Combined index for common queries
}
```
### Metadata and resume capability
The metadata collection is critical for operational resilience. When a consumer instance restarts after a crash, planned maintenance, or redeployment, it reads its metadata document to find the last indexed position for each cron job. Without this checkpoint data, the consumer would need to re-index from the beginning, duplicating hours or days of work.
Each metadata document tracks three resume points:
* `lastIndexedBridgeDepositCount`: where `bridgesCron` should resume
* `lastIndexedClaimBlockNumber`: where `claimsCron` should resume
* `lastIndexedMappingBlockNumber`: where `mappingsCron` should resume
On startup, each cron reads its respective checkpoint and picks up exactly where it left off.
## Data synchronization
The system maintains eventual consistency through three distinct data paths:
* **Write path** (Consumer to MongoDB): Consumers poll AggKit APIs and write new or updated records into MongoDB. All writes use upsert operations, making them idempotent. Duplicate events from AggKit are handled gracefully.
* **Read path** (API from MongoDB): The API service reads directly from MongoDB and serves the data over REST endpoints. Because the API is stateless and read-only, it can be scaled horizontally behind a load balancer.
* **Claim path** (Auto-Claim to Blockchain): The Auto-Claim service polls the API for transactions in `READY_TO_CLAIM` status, fetches merkle proofs through the API's `/claim-proof` endpoint, and submits claim transactions on-chain. Claims are processed sequentially to avoid nonce conflicts.
### Consistency guarantees
* Transactions are immutable once created; only status fields are updated.
* Status updates are atomic at the document level.
* Duplicate events are handled via upsert, so re-processing the same data is safe.
* No distributed transactions are needed because all state lives in a single MongoDB instance.
* There is a small window of delay between an on-chain event occurring and the consumer indexing it. During this window, the API may serve slightly stale data. This is acceptable for the bridge use case, where transactions take time to become claimable regardless.
# Auto-Claim Service
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/bridge-hub/auto-claim
Automated bridge transaction claiming: configuration, deployment, and security.
## Overview
The auto-claim service automates the claiming of bridge transactions on the destination chain. It polls the Bridge Hub API for transactions in `READY_TO_CLAIM` status, fetches Merkle proofs, and submits claim transactions. It handles both `ASSET` and `MESSAGE` claim types and filters out zero-amount `MESSAGE` transactions.
## How it works
The auto-claim service runs a continuous polling loop on a 30-second interval. Each cycle follows this sequence:
1. Poll the Bridge Hub API for transactions with `READY_TO_CLAIM` status.
2. Filter the returned transactions by source network and claim type.
3. For each transaction: fetch the Merkle proof, compute the global index, submit the claim transaction, and wait for on-chain confirmation.
4. Sleep for 30 seconds, then repeat.
```
┌─────────────────┐
│ Poll API │
│ (30s interval) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Filter Txs │
└────────┬────────┘
│
▼
┌─────────────────┐
│ For Each Tx: │
│ 1. Get Proof │
│ 2. Compute Index│
│ 3. Submit Claim │
│ 4. Wait Confirm │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Sleep 30s │
└────────┬────────┘
│
└──────→ (repeat)
```
Claims are processed sequentially to avoid nonce conflicts. One auto-claim instance should run per destination network.
Errors on one transaction do not affect others. The blockchain enforces claim uniqueness, so the service is safe to restart at any time without risk of double-claiming. Failed claim transactions are reverted on-chain and do not result in lost funds.
## Configuration
The auto-claim service is configured through environment variables.
| Variable | Required | Example | Description |
| ----------------------------- | -------- | --------------------------- | ---------------------------------------- |
| `BRIDGE_HUB_API_URL` | Yes | `http://api:3000` | Bridge Hub API URL |
| `SOURCE_NETWORKS` | Yes | `[1,137]` | Source network IDs (JSON array) |
| `DESTINATION_NETWORK` | Yes | `2442` | Destination network ID |
| `DESTINATION_NETWORK_CHAINID` | Yes | `2442` | Destination chain ID |
| `BRIDGE_CONTRACT` | Yes | `0x...` | Bridge contract address |
| `PRIVATE_KEY` | Yes | `0x...` | Wallet private key for submitting claims |
| `RPC_CONFIG` | Yes | `{"2442":"https://..."}` | RPC endpoints (JSON object) |
| `SENTRY_DSN` | No | `https://...@sentry.io/...` | Error tracking DSN |
## Deployment
Deploy one auto-claim instance for each destination network you want to auto-claim for.
### Docker Compose
Add the following service to your `docker-compose.yml`:
```yaml theme={null}
autoclaim:
build:
context: .
dockerfile: Dockerfile.autoclaim
environment:
NODE_ENV: production
BRIDGE_HUB_API_URL: http://api:3001
SOURCE_NETWORKS: ${SOURCE_NETWORKS}
DESTINATION_NETWORK: ${DESTINATION_NETWORK}
DESTINATION_NETWORK_CHAINID: ${DESTINATION_NETWORK_CHAINID}
BRIDGE_CONTRACT: ${BRIDGE_CONTRACT}
PRIVATE_KEY: ${PRIVATE_KEY}
RPC_CONFIG: ${RPC_CONFIG}
SENTRY_DSN: ${SENTRY_DSN}
depends_on:
- api
restart: unless-stopped
networks:
- bridge-hub
```
### Direct execution with Bun
You can also run the service directly:
```bash theme={null}
# Auto-claim for destination network 2442
BRIDGE_HUB_API_URL=http://localhost:3001 \
DESTINATION_NETWORK=2442 \
DESTINATION_NETWORK_CHAINID=2442 \
SOURCE_NETWORKS=[0,1,137] \
BRIDGE_CONTRACT=0x... \
PRIVATE_KEY=0x... \
RPC_CONFIG='{"2442":"https://rpc.example.com"}' \
bun start
```
To auto-claim for multiple destination networks, run a separate instance for each:
```bash theme={null}
# Instance 1: destination 2442
DESTINATION_NETWORK=2442 DESTINATION_NETWORK_CHAINID=2442 bun start
# Instance 2: destination 1101
DESTINATION_NETWORK=1101 DESTINATION_NETWORK_CHAINID=1101 bun start
```
## Security
The auto-claim service requires a private key to submit on-chain transactions. Follow these guidelines to protect it:
* **Never log or expose private keys.** Ensure your logging configuration does not capture environment variables.
* **Load keys from a secret management system** such as AWS Secrets Manager, HashiCorp Vault, or Kubernetes Secrets. Avoid hardcoding keys in configuration files.
* **Use a dedicated wallet with minimal funding.** The wallet only needs enough native tokens to cover gas on the destination chain.
* **Monitor for unusual activity.** Set up alerts for unexpected balance changes or transaction patterns on the claiming wallet.
Claims are atomic: they either succeed or revert entirely. The blockchain enforces uniqueness, so a transaction cannot be claimed twice even if the service processes it again after a restart.
## Troubleshooting
| Problem | Solution |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Transactions stuck in `READY_TO_CLAIM` | Check that the claiming wallet has sufficient gas on the destination chain. |
| "API unreachable" errors | Verify `BRIDGE_HUB_API_URL` is correct and accessible from the auto-claim service. |
| Nonce conflicts | Ensure only one auto-claim instance runs per destination network. |
| Claims failing | Check RPC endpoint connectivity. Verify the `BRIDGE_CONTRACT` address is correct for the destination network. |
# Deployment
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/bridge-hub/deployment
Deploy Bridge Hub to production: Docker, Kubernetes, configuration, and troubleshooting.
## Prerequisites
### Infrastructure requirements
Each Bridge Hub component has specific compute requirements:
| Component | CPU | RAM | Notes |
| ---------- | ------- | ---- | --------------- |
| API | 2+ vCPU | 4 GB | Per instance |
| Consumer | 2+ vCPU | 4 GB | Per network |
| Auto-Claim | 1+ vCPU | 2 GB | Per destination |
You also need a MongoDB instance (version 4.4 or later). A 3-node replica set is recommended for production, with at least 100 GB of storage that can grow with transaction volume.
### Software requirements
* **Bun** >= 1.0.0
* **MongoDB** >= 4.4
* **Docker** (optional; for containerized deployment)
* **Kubernetes** (optional; for orchestrated deployment)
### External services
* **Bridge Service API** access for each network you plan to index.
* **Blockchain RPC endpoints** from a reliable provider.
* **Sentry account** (optional) for error tracking.
## Build
From the repository root, install dependencies and create production builds:
```bash theme={null}
bun install
bun run build
```
This outputs production artifacts to `packages/api/dist/`, `packages/consumer/dist/`, and `packages/auto-claim/dist/`.
## Docker deployment
The repository includes production-ready Dockerfiles for each service:
* `Dockerfile.api`:API service (exposes port 3001)
* `Dockerfile.consumer`:Consumer service
* `Dockerfile.autoclaim`:Auto-Claim service
Each Dockerfile uses `oven/bun:1.2-alpine` with multi-stage builds and runs as a non-root user (`bunuser`).
### Build images
```bash theme={null}
docker build -f Dockerfile.api -t bridge-hub-api:latest .
docker build -f Dockerfile.consumer -t bridge-hub-consumer:latest .
docker build -f Dockerfile.autoclaim -t bridge-hub-autoclaim:latest .
```
### Docker Compose
Create a `docker-compose.yml` in the repository root:
```yaml theme={null}
version: "3.8"
services:
mongodb:
image: mongo:7
volumes:
- mongo-data:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
ports:
- "27017:27017"
restart: unless-stopped
networks:
- bridge-hub
api:
build:
context: .
dockerfile: Dockerfile.api
ports:
- "3001:3001"
environment:
NODE_ENV: production
MONGODB_CONNECTION_URI: ${MONGODB_CONNECTION_URI}
MONGODB_DB_NAME: ${MONGODB_DB_NAME}
PROOF_CONFIG: ${PROOF_CONFIG}
RPC_CONFIG: ${RPC_CONFIG}
SENTRY_DSN: ${SENTRY_DSN}
depends_on:
- mongodb
restart: unless-stopped
networks:
- bridge-hub
consumer-net1:
build:
context: .
dockerfile: Dockerfile.consumer
environment:
NODE_ENV: production
NETWORK_ID: ${CONSUMER_NETWORK_ID}
NETWORK: ${CONSUMER_NETWORK}
BRIDGE_SERVICE_URL: ${BRIDGE_SERVICE_URL}
BRIDGE_CONTRACT_ADDRESS: ${BRIDGE_CONTRACT_ADDRESS}
MONGODB_CONNECTION_URI: ${MONGODB_CONNECTION_URI}
MONGODB_DB_NAME: ${MONGODB_DB_NAME}
SENTRY_DSN: ${SENTRY_DSN}
depends_on:
- mongodb
restart: unless-stopped
networks:
- bridge-hub
autoclaim:
build:
context: .
dockerfile: Dockerfile.autoclaim
environment:
NODE_ENV: production
BRIDGE_HUB_API_URL: http://api:3001
SOURCE_NETWORKS: ${SOURCE_NETWORKS}
DESTINATION_NETWORK: ${DESTINATION_NETWORK}
DESTINATION_NETWORK_CHAINID: ${DESTINATION_NETWORK_CHAINID}
BRIDGE_CONTRACT: ${BRIDGE_CONTRACT}
PRIVATE_KEY: ${PRIVATE_KEY}
RPC_CONFIG: ${RPC_CONFIG}
SENTRY_DSN: ${SENTRY_DSN}
depends_on:
- api
restart: unless-stopped
networks:
- bridge-hub
volumes:
mongo-data:
networks:
bridge-hub:
driver: bridge
```
### Adding consumer instances for additional networks
To index more than one network, duplicate the `consumer-net1` block with a unique service name and network-specific environment variables. For example:
```yaml theme={null}
consumer-net137:
build:
context: .
dockerfile: Dockerfile.consumer
environment:
NETWORK_ID: 137
NETWORK: mainnet
BRIDGE_SERVICE_URL: ${BRIDGE_SERVICE_URL_NET137}
BRIDGE_CONTRACT_ADDRESS: ${BRIDGE_CONTRACT_ADDRESS_NET137}
MONGODB_CONNECTION_URI: ${MONGODB_CONNECTION_URI}
MONGODB_DB_NAME: ${MONGODB_DB_NAME}
depends_on:
- mongodb
restart: unless-stopped
networks:
- bridge-hub
```
### Common Docker Compose commands
```bash theme={null}
# Start all services in the background
docker-compose up -d
# Follow logs from all services
docker-compose logs -f
# Follow logs from a specific service
docker-compose logs -f api
# Check service status
docker-compose ps
# Restart a single service
docker-compose restart api
# Stop all services
docker-compose down
```
## Kubernetes deployment
### Namespace and ConfigMap
```yaml theme={null}
apiVersion: v1
kind: Namespace
metadata:
name: bridge-hub
---
apiVersion: v1
kind: ConfigMap
metadata:
name: bridge-hub-config
namespace: bridge-hub
data:
MONGODB_DB_NAME: "bridge_hub"
NODE_ENV: "production"
```
### API Deployment
```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
name: bridge-hub-api
namespace: bridge-hub
spec:
replicas: 3
selector:
matchLabels:
app: bridge-hub-api
template:
metadata:
labels:
app: bridge-hub-api
spec:
containers:
- name: api
image: bridge-hub-api:latest
ports:
- containerPort: 3000
env:
- name: MONGODB_CONNECTION_URI
valueFrom:
secretKeyRef:
name: bridge-hub-secrets
key: mongodb-uri
- name: MONGODB_DB_NAME
valueFrom:
configMapKeyRef:
name: bridge-hub-config
key: MONGODB_DB_NAME
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: bridge-hub-api
namespace: bridge-hub
spec:
selector:
app: bridge-hub-api
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
```
### Apply and manage resources
```bash theme={null}
# Apply all manifests
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/api-deployment.yaml
kubectl apply -f k8s/consumer-deployment.yaml
kubectl apply -f k8s/auto-claim-deployment.yaml
# Check pod status
kubectl get pods -n bridge-hub
# Follow API logs
kubectl logs -f -n bridge-hub deployment/bridge-hub-api
# Scale the API horizontally
kubectl scale deployment bridge-hub-api --replicas=5 -n bridge-hub
```
## Configuration reference
Never commit secrets to the repository. Use a secret management system such as AWS Secrets Manager, HashiCorp Vault, or Kubernetes Secrets.
### API package
| Variable | Required | Example | Description |
| ------------------------ | -------- | --------------------------------- | -------------------------- |
| `MONGODB_CONNECTION_URI` | Yes | `mongodb://user:pass@host:27017` | MongoDB connection string |
| `MONGODB_DB_NAME` | Yes | `bridge_hub` | Database name |
| `RPC_CONFIG` | Yes | `{"mainnet":{"1":"https://..."}}` | RPC endpoints by network |
| `PROOF_CONFIG` | Yes | `{"mainnet":{"1":"https://..."}}` | Proof generation endpoints |
| `PORT` | No | `3000` | HTTP port (default: 3000) |
| `NODE_ENV` | No | `production` | Environment mode |
| `SENTRY_DSN` | No | `https://...@sentry.io/...` | Error tracking DSN |
### Consumer package
| Variable | Required | Example | Description |
| --------------------------- | -------- | --------------------------------------- | ------------------------------------- |
| `NETWORK_ID` | Yes | `1` | Network identifier |
| `NETWORK` | Yes | `mainnet` | Network name (mainnet/testnet/devnet) |
| `BRIDGE_SERVICE_URL` | Yes | `https://bridge-api.polygon.technology` | Bridge Service API URL |
| `BRIDGE_CONTRACT_ADDRESS` | Yes | `0x...` | Bridge contract address |
| `MONGODB_CONNECTION_URI` | Yes | `mongodb://user:pass@host:27017` | MongoDB connection string |
| `MONGODB_DB_NAME` | Yes | `bridge_hub` | Database name |
| `ETROG_UPDATE_BLOCK_NUMBER` | No | `0` | Starting block for indexing |
| `SENTRY_DSN` | No | `https://...@sentry.io/...` | Error tracking DSN |
### Auto-Claim package
| Variable | Required | Example | Description |
| ----------------------------- | -------- | --------------------------- | -------------------------------- |
| `BRIDGE_HUB_API_URL` | Yes | `http://api:3000` | Bridge Hub API URL |
| `SOURCE_NETWORKS` | Yes | `[1,137]` | Source network IDs (JSON array) |
| `DESTINATION_NETWORK` | Yes | `2442` | Destination network ID |
| `DESTINATION_NETWORK_CHAINID` | Yes | `2442` | Destination chain ID |
| `BRIDGE_CONTRACT` | Yes | `0x...` | Bridge contract address |
| `PRIVATE_KEY` | Yes | `0x...` | Wallet private key for gas funds |
| `RPC_CONFIG` | Yes | `{"2442":"https://..."}` | RPC endpoints (JSON) |
| `SENTRY_DSN` | No | `https://...@sentry.io/...` | Error tracking DSN |
## Adding a new network
Start a new consumer with the network's configuration:
```bash theme={null}
NETWORK_ID=42161 \
NETWORK=mainnet \
BRIDGE_CONTRACT_ADDRESS=0x... \
BRIDGE_SERVICE_URL=https://aggkit-42161.example.com \
MONGODB_CONNECTION_URI=mongodb://... \
bun start
```
In Docker Compose, add a new `consumer-net` service block. In Kubernetes, create a new Consumer Deployment manifest.
Add the new network's RPC endpoint to `RPC_CONFIG` and its proof endpoint to `PROOF_CONFIG`, then restart the API instances so they pick up the change.
Append the new network ID to the `SOURCE_NETWORKS` JSON array, then restart Auto-Claim instances.
Confirm the pipeline is working end to end:
* **Consumer indexing**: Query the MongoDB metadata collection for the new network's checkpoint.
* **API serving data**: Call `GET /transactions?sourceNetworkIds=42161` and confirm results appear.
* **Auto-Claim detection**: Monitor Auto-Claim logs to verify it picks up new transactions.
## Troubleshooting
| Symptom | Likely cause | Solution |
| --------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Services crash with MongoDB connection errors | MongoDB is unreachable or credentials are wrong | Verify MongoDB is running (`mongosh $MONGODB_CONNECTION_URI`). Check network connectivity with `telnet mongo-host 27017`. Review credentials in your environment variables. |
| API queries return no data | Consumer is not running or has not indexed yet | Confirm the Consumer process is active. Check that MongoDB contains documents (`db.transactions.count()`). Review API logs for errors and verify your query parameters. |
| Transactions stuck in `READY_TO_CLAIM` | Auto-Claim cannot submit transactions | Check that the wallet has sufficient gas on the destination chain. Verify `BRIDGE_HUB_API_URL` is reachable from the Auto-Claim service. Test RPC endpoint connectivity. Confirm the private key is correct. |
| Excessive memory usage | Unbounded batch sizes or tight poll intervals | Review Consumer batch size settings and increase poll intervals. Add memory limits in Docker or Kubernetes resource specs. Check logs for memory leak indicators. |
# Bridge Hub
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/bridge-hub/index
Multi-chain bridge transaction indexing, API, and auto-claim system for AggLayer integrators.
## Overview
Bridge Hub is a multi-chain indexing and claiming system for AggLayer bridge transactions. It polls each chain's AggKit Bridge Service, indexes transaction data into MongoDB, and exposes the results through a unified REST API. Bridge Hub is designed for integrators who need a single interface to query and manage bridge transactions across all chains connected to the AggLayer.
## Bridge Hub vs. AggKit Bridge Service
| | AggKit Bridge Service | Bridge Hub |
| --------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **Scope** | Single chain | Multi-chain |
| **Runs on** | Chain operator infrastructure | Integrator infrastructure |
| **Persistence** | In-memory | MongoDB |
| **Exposes** | Raw bridge data | Indexed data + auto-claim |
| **Components** | 1 service | 4 packages |
| **Repo** | [agglayer/agglayer-rs](https://github.com/agglayer/agglayer-rs) | [0xPolygon/agglayer-bridge-hub-api](https://github.com/0xPolygon/agglayer-bridge-hub-api) |
| **Audience** | Chain operators | dApp developers, bridge UIs, integrators |
## Data flow
CDK chain → AggKit Bridge Service → Bridge Hub Consumer → Bridge Hub API → Bridge UI / Auto-Claim
## Packages
| Package | Layer | Description |
| -------------- | -------------- | ----------------------------------------------------------------------------------------- |
| **Commons** | Foundation | Shared TypeScript types, interfaces, and schema definitions used across all packages |
| **Consumer** | Data ingestion | Polls AggKit Bridge Service APIs per chain and writes transaction data to MongoDB |
| **API** | Service | REST API with OpenAPI documentation for querying transactions and generating claim proofs |
| **Auto-Claim** | Automation | Polls the API for claimable transactions and submits claim transactions on-chain |
## Transaction lifecycle
A user initiates a bridge transaction on the source chain. The Consumer detects the deposit through the AggKit Bridge Service and saves the transaction to MongoDB with status `BRIDGED`.
The Consumer monitors L1 info tree updates and determines when the transaction becomes claimable. It updates the transaction status to `READY_TO_CLAIM` and sets the leaf index needed for proof generation.
The Auto-Claim service (or an external caller) fetches a merkle proof from the API and submits a claim transaction on the destination chain. Once confirmed, the Consumer detects the claim event and updates the status to `CLAIMED`.
## Technology stack
* [Bun](https://bun.sh): JavaScript runtime
* [Hono](https://hono.dev): Web framework for the REST API
* [viem](https://viem.sh): TypeScript Ethereum library for blockchain interactions
* [MongoDB](https://www.mongodb.com/): Document database for transaction storage
* [Lerna](https://lerna.js.org/): Monorepo package management
## Next steps
System design, consumer internals, and database schema.
Configuration, environment variables, and production setup.
REST endpoints, request parameters, and response schemas.
Automated claiming service setup and operation.
# Integrations
Source: https://docs.polygon.technology/interoperability/agglayer/integrations/index
Tools and APIs for integrating with AggLayer bridge infrastructure.
## Integrations
Agglayer provides APIs and services for integrating cross-chain bridge functionality into your applications.
Multi-chain bridge transaction indexer and API. Aggregates data from all connected chains into a single queryable interface with auto-claim support.
REST API for accessing bridge data, transaction status, and proof generation without running infrastructure.
# Supported Chains
Source: https://docs.polygon.technology/interoperability/agglayer/supported-chains
Chains connected to AggLayer: chain IDs, rollup IDs, RPC endpoints, block explorers, and gas tokens.
## Overview
The following chains are connected to AggLayer. Each chain has a unique rollup ID assigned during registration.
Chain types:
* **PP (Pessimistic Proof)**: Sovereign mode secured by pessimistic proofs. No ZK prover required.
* **Validium**: ZK-secured execution with offchain data availability.
* **zkEVM**: Fully onchain ZK rollup.
* **ALGateway**: Connected through the AggLayer Gateway.
* **Outpost**: Non-CDK chain connected to AggLayer.
## Mainnets
| Chain | Chain ID | Rollup ID | Type | Gas Token | RPC | Explorer |
| ---------------------------------------------------------------------------------- | -------- | --------- | --------- | --------- | ------------------------------------------ | --------------------------------------------------------------------- |
| [Ethereum](https://ethereum.org) | 1 | 0 | L1 | ETH | `https://eth.llamarpc.com` | [etherscan.io](https://etherscan.io) |
| [Katana](https://docs.katana.network/) | 747474 | 20 | ALGateway | ETH | `https://rpc.katanarpc.com` | [katanascan.com](https://katanascan.com/) |
| [Pentagon Games](https://pentagon.games/) | 3344 | 16 | Validium | PC | `https://rpc.pentagon.games` | [explorer.pentagon.games](https://explorer.pentagon.games/) |
| [Silicon.network](https://docs.silicon.network) | 2355 | 10 | Validium | ETH | `https://rpc.silicon.network` | [scope.silicon.network](https://scope.silicon.network) |
| [X Layer](https://web3.okx.com/xlayer/docs/developer/build-on-xlayer/about-xlayer) | 196 | 3 | PP | OKB | `https://rpc.xlayer.tech` | [oklink.com/x-layer](https://www.oklink.com/x-layer) |
| [Ternoa](https://docs.ternoa.network) | 752025 | 13 | zkEVM | CAPS | `https://rpc-mainnet.zkevm.ternoa.network` | [explorer.ternoa.com](https://explorer.ternoa.com/) |
| [Wirex Pay](https://partner.wirexpaychain.com/docs/getting-started) | 31415 | 8 | zkEVM | ETH | `https://rpc.wirexpaychain.com` | [blockscout.wirexpaychain.com](https://blockscout.wirexpaychain.com/) |
## Testnets
| Chain | Chain ID | Rollup ID | Type | Gas Token | RPC | Explorer |
| ------------------------------------- | ---------- | --------- | --------- | --------- | ---------------------------------- | ------------------------------------------------------------------------------------------ |
| [Bokuto](https://docs.katana.network) | 737373 | 37 | ALGateway | ETH | `https://rpc-bokuto.katanarpc.com` | [tenderly.co/explorer/katana-bokuto](https://dashboard.tenderly.co/explorer/katana-bokuto) |
| [Lumia](https://docs.lumia.org/) | 2030232745 | 35 | zkEVM | LUMIA | `https://beam-rpc.lumia.org` | [beam-explorer.lumia.org](https://beam-explorer.lumia.org/) |
# What is the Open Money Stack?
Source: https://docs.polygon.technology/oms/overview
One open stack for global money movement: fiat access, wallets, compliance, routing, and settlement in one vertically integrated, open platform.
The Open Money Stack is Polygon's open, end-to-end infrastructure for global money movement. It connects fiat access, wallets, compliance, routing, and settlement into one integration, designed to plug into existing systems and move money in seconds.
Need regulated rails, custodial infrastructure, compliance services, or a managed chain deployment? [Contact Polygon](https://info.polygon.technology/get-early-access).
## The problem with fragmented stacks
Money should move reliably, at low cost. That requires predictable settlement without surprise deductions or delays.
Most institutions building stablecoin payment flows today take the same path: choose a compliance vendor, a wallet provider, a bridge, an off-ramp, and a chain. When they have what they like, they stitch these solutions together. Each integration requires independent maintenance.
In practice, this works, right up to the moment that volume grows or something goes wrong.
Then teams must debug an outage that lives somewhere between three vendors and a dozen systems.
## A different architecture
The Open Money Stack is **open and vertically integrated**: every layer is built to hand off cleanly to the next, with no lock-in.
And it's composable. If an institution only wants to use one aspect of the Open Money Stack, say Polygon Chain for settlement or just the wallet infrastructure, they can pick and choose.
**Use all of it, or only the components you need.**
## Settlement at the bottom of the stack
Most payments orchestration companies don't own the infrastructure they route on. They aggregate across vendors they don't control, adding margin at every layer. When settlement breaks, they call their vendor.
Polygon owns the settlement layer. The OMS is built on Polygon Chain: not as a dependency, but as infrastructure Polygon operates.
**\$54B in stablecoin transfer volume**, **159M unique wallet addresses**, **6.4B total transactions**, and an average transaction cost of **\$0.002**, with live integrations by Revolut, Stripe, Flutterwave, and more.
The economics compound at scale: costs improve as volume grows, rather than degrading through intermediary margin stacking.
## Unrolling the stack
1-click wallet creation with zero-config auth, passkeys, Smart Sessions, and enterprise-grade security. Custodial and non-custodial options both supported.
Licensed fiat on- and off-ramps covering bank transfer, debit card, and cash at 50,000+ retail locations. KYC, AML, and compliance built in.
1-click transactions on any chain with any token. Deep unified liquidity across every connected network, with routing and bridging handled automatically.
Polygon Chain for public settlement and Polygon Chain Development Kit (CDK) for dedicated rollup rails with 20,000+ TPS, compliance controls, and native Agglayer connectivity.
Enterprise payments infrastructure for stablecoins and tokenized deposits. Native USDC with no wrapping, no bridges, and no hidden deductions.
x402 for pay-per-use APIs. ERC-8004 for onchain agent identity. Infrastructure for autonomous agent commerce without human approval at every step.
## Why integration changes the economics
Historically, each of these layers existed independently:
* Fiat access from one vendor
* Wallet infrastructure from another
* A bridge from a third
* Settlement from whichever chain you prefer
Every seam adds operational surface area. The Open Money Stack reduces that surface area.
With the full OMS, a complete payment flow looks like this:
1. **Funds enter** through regulated fiat rails
2. **They settle** into a smart contract wallet instantly
3. **Orchestration** routes across borders and networks as needed
4. **Polygon finalizes** the transfer in under two seconds
5. **The recipient off-ramps** into local currency through compliant infrastructure
Integration does not eliminate flexibility. Institutions can still extend, customize, and interoperate. They can pick and choose what they need. But they are not required to assemble foundational plumbing themselves.
**Integrate once. Customize as desired. Move money end-to-end, globally, 24/7.**
## Who it's for
* **Payment platforms and fintechs**: replace the patchwork of wallets, ramps, compliance, routing, and settlement vendors with one open stack at better economics
* **Fintechs and neobanks**: add stablecoin payment rails with embedded wallets, compliant onramps, and instant settlement in one integration
* **Enterprise payments teams**: reduce cross-border costs, unlock 24/7 settlement, and expand globally without assembling per-corridor bank relationships
* **Banks and financial institutions**: layer stablecoin settlement onto existing infrastructure without rebuilding core systems
* **Enterprises and marketplaces**: automate global payouts with programmable, auditable money flows and pay out counterparties anywhere instantly
## Next steps
See how financial institutions are using the Open Money Stack.
Pick a use case and follow a guided path into the docs.
# Start Building
Source: https://docs.polygon.technology/oms/quickstart
Choose your path into the Open Money Stack based on what you're building.
Pick the starting point that matches your use case. Each path links to the relevant section of these docs.
Need regulated rails, custodial infrastructure, compliance services, or a managed chain deployment? [Contact Polygon](https://info.polygon.technology/get-early-access).
## I want to add wallet infrastructure
Give users an onchain account with 1-click setup.
Social login, passkeys, and smart sessions built into your app. No seed phrases.
Integrate an embedded wallet into a React app in minutes.
Scoped, time-limited permissions so users approve once and your app handles the rest.
Fully managed wallets for regulated products and enterprise deployments.
## I want to add fiat on-/off-ramps
Let users move between fiat and crypto without leaving your app.
Licensed infrastructure for bank accounts, debit cards, and cash with KYC and AML built in.
Buy USDC with a card via Stripe's Crypto Onramp API.
Convert onchain stablecoins back to fiat, compliantly.
## I want to accept stablecoin payments
Integrate native USDC on Polygon for instant, low-fee settlement.
Native USDC integration: instant settlement, low fees, broad wallet support.
All stablecoins available on Polygon, with integration guides for USDC and cross-chain transfers.
## I want to enable cross-chain transactions
Orchestrate 1-click transactions across any chain, with any token, from any wallet.
How Trails handles routing, liquidity aggregation, and gas abstraction across chains.
Integrate the Trails Widget, Headless SDK, or Direct API.
## I want to build agentic payment flows
Embed payments into AI agents and autonomous applications.
CLI toolkit for AI agents: wallets, token operations, x402 micropayments, and onchain identity in a single install.
HTTP-native micropayments for pay-per-use APIs and AI agents.
ERC-8004 reputation standard and LLM wallet MCP.
## I want to use an existing chain
Build on Polygon Chain, the public settlement layer for payments, stablecoins, and enterprise applications.
RPC endpoints, chain configuration, and deployment guides for Polygon's public chain.
Understand the full blockchain layer: Polygon Chain, Chain Development Kit (CDK), and Agglayer.
System requirements, snapshots, and setup guides for running a full node on Polygon Chain.
Requirements, staking, and step-by-step setup for running a Polygon validator node.
## I want to deploy my own chain
Launch a custom ZK-powered chain with dedicated throughput and compliance controls.
Build and launch a ZK-powered chain in days, not months.
Cross-chain interoperability and shared liquidity built into every CDK chain by default.
# Use Cases
Source: https://docs.polygon.technology/oms/use-cases
How financial institutions are using the Open Money Stack to move money globally.
The Open Money Stack is built for fintechs, payment platforms, and financial institutions that need to move money globally at scale. These are the most common scenarios, each enabled by OMS components that can be adopted independently or together.
## Always-On Account Funding
Traditional payment rails run on limited global banking hours. Your users don't.
PSPs and neobanks use Polygon's settlement layer to credit accounts and settle merchant balances at any hour, 24/7 globally: evenings, weekends, holidays. Finality in under two seconds. No more queued ACH batches, no settlement lag on Sunday afternoons. Stablecoin rails run continuously, so funding does too.
**Who this is for:** PSPs, payment platforms, neobanks, brokerages, and merchant acquirers that need settlement outside banking hours.
**OMS components:** Stablecoin Orchestration · Blockchain Rails · Wallet Infrastructure
***
## Cross-Border Business Payments
Correspondent banking corridors are slow, opaque, and expensive. Building alternatives from scratch requires bank relationships, licensing, and compliance infrastructure that takes years to assemble.
The OMS provides the full stack for cross-border B2B payments: licensed fiat access in sender and receiver currencies, stablecoin settlement on Polygon, and cross-chain routing for corridors that span multiple networks. Platforms can launch global payment products on regulated infrastructure, without starting from zero.
**Who this is for:** PSPs, paytechs, payout networks, and fintechs building cross-border B2B payment products, whether serving businesses directly or as a platform layer.
**OMS components:** On-/Off-Ramps · Stablecoin Orchestration · Cross-Chain Interop · Blockchain Rails
***
## Dollar Banking for Emerging Markets
Consumers and businesses in Latin America, Africa, and Southeast Asia increasingly want USD-denominated accounts: a stable store of value, a better way to send and receive internationally, and protection against local currency volatility.
The OMS gives neobanks the building blocks to offer that experience: embedded wallets that look like bank accounts, USDC-backed balances, compliant on- and off-ramp infrastructure, and coverage across 38 countries. Users hold and move dollars without a US bank account; the platform doesn't need one either.
**Who this is for:** Stablecoin-first neobanks and consumer fintechs targeting non-US markets.
**OMS components:** Wallet Infrastructure · On-/Off-Ramps · Stablecoin Orchestration
***
## Global Payouts at Scale
Marketplace platforms, payroll providers, and gig economy companies face the same problem: paying large numbers of workers or sellers in different countries, in different currencies, through different local payment systems.
The OMS handles the routing, compliance, and last-mile delivery. Platforms initiate payouts in USDC; the OMS converts and delivers in local fiat across dozens of countries, with KYC, AML, and reporting built into the infrastructure. No per-corridor integrations, no separate compliance stack.
**Who this is for:** Marketplaces, payroll platforms, gig economy companies, and PSPs running global payout programs.
**OMS components:** On-/Off-Ramps · Stablecoin Orchestration · Blockchain Rails
***
## Stablecoin Payment Acceptance
Merchants want the benefits of stablecoin settlement (instant finality, near-zero fees, global reach) without asking customers to manage crypto wallets. Platforms serving merchants want to offer stablecoin acceptance alongside existing payment methods without rebuilding their compliance stack.
The OMS provides wallet infrastructure to receive stablecoin payments, automatic conversion to fiat, and compliant offramp delivery to the merchant's bank account. Customers pay in stablecoins; merchants settle in the currency they already use.
**Who this is for:** Merchants, merchant payment platforms, and PSPs adding stablecoin acceptance.
**OMS components:** Wallet Infrastructure · Stablecoin Orchestration · On-/Off-Ramps
***
## Consumer Remittance
Remittance platforms face a structural tension: US regulatory coverage is fragmented by state, last-mile delivery in destination countries requires local banking relationships, and customers expect near-instant transfers at competitive rates.
The OMS provides licensed corridor infrastructure with 38-state MTL coverage, stablecoin settlement on Polygon, and offramp delivery in local fiat across international corridors. Platforms can offer debit card, ACH, and cash onramp options without assembling the licensing and bank relationships from scratch.
**Who this is for:** Remittance platforms, consumer fintechs serving immigrant populations, and apps with cross-border transfer products.
**OMS components:** On/Off Ramps · Stablecoin Orchestration · Blockchain Rails
***
## Embedded Fiat Ramps
Crypto wallets, exchanges, and fintech apps need to let users move between fiat and digital assets, but building licensed ramp infrastructure across US states is a multi-year compliance project.
The OMS provides white-label and API-based ramp infrastructure with 48-state MTL coverage, support for debit card, ACH, and cash, and both widget and native API integration paths. Platforms embed it; the OMS handles the licensing, KYC, and compliance.
**Who this is for:** Crypto wallets, exchanges, and fintech apps embedding USD on/off-ramp experiences for end users.
**OMS components:** On-/Off-Ramps · Wallet Infrastructure
# ERC-8004 on Polygon
Source: https://docs.polygon.technology/payment-services/agentic-payments/agent-integration/erc8004
Reference and explanation for ERC-8004: onchain Identity, Reputation, and Validation registries for autonomous agents.
**ERC-8004** defines a lightweight, onchain trust layer for autonomous agents
using three registries: **Identity**, **Reputation**, and **Validation**.
The standard is designed to work alongside existing agent protocols (A2A, MCP).
Payments are out of scope for ERC-8004; however, payment proofs (e.g., from **x402**) can be
referenced in reputation data.
This page covers what ERC-8004 standardizes, how it relates to A2A, MCP, and
x402, and where to find the Polygon mainnet and Amoy deployments.
## Why ERC-8004?
Modern agent stacks focus on communication and capability exposure, not on open
discovery and trust across organizational boundaries. Google's **A2A** provides
agent authentication, capability advertisement via Agent Cards, and
orchestration, but does not standardize reputation or validation.
Anthropic's **MCP** connects LLM apps to external tools and data, and similarly
leaves trust and discovery to individual applications.
ERC-8004 adds the missing piece: standard onchain registries that any chain
can host as singletons. It links agents to MCP/A2A endpoints through an onchain
identity, with optional trust signals.
***
## What ERC-8004 standardizes (at a glance)
* **Identity Registry** - an **ERC-721 + URIStorage** registry that mints an
*Agent ID* (`agentId`) and points its `tokenURI` to a JSON registration file
(e.g., on IPFS/HTTPS) listing A2A/MCP endpoints, DIDs, ENS, wallets, etc.
Ownership of the NFT = ownership of the agent entry.
* **Reputation Registry** - an interface for clients to submit **feedback**
(score `0-100`, optional tags, and an optional off-chain file/URI + hash).
Off-chain files may include **payment proofs** to correlate economics with
feedback.
* **Validation Registry** - a request/response log for **independent
validators** (e.g., stake-based re-execution, zkML verifiers, TEEs) to post
attestations about an agent's work. Results can be queried onchain.
Payments are **orthogonal** to
ERC-8004; the spec shows how **x402** proofs can be referenced in reputation
data but does not dictate any settlement flow.
## Polygon Deployments
ERC-8004 registries are deployed on **Polygon mainnet** and **Polygon Amoy**.
### Polygon Mainnet
* **IdentityRegistry:** [0x8004A169FB4a3325136EB29fA0ceB6D2e539a432](https://polygonscan.com/address/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432)
* **ReputationRegistry:** [0x8004BAa17C55a88189AE136b182e5fdA19dE9b63](https://polygonscan.com/address/0x8004BAa17C55a88189AE136b182e5fdA19dE9b63)
### Amoy
* **IdentityRegistry:** [0x8004ad19E14B9e0654f73353e8a0B600D46C2898](https://amoy.polygonscan.com/address/0x8004ad19E14B9e0654f73353e8a0B600D46C2898)
* **ReputationRegistry:** [0x8004B12F4C2B42d00c46479e859C92e39044C930](https://amoy.polygonscan.com/address/0x8004B12F4C2B42d00c46479e859C92e39044C930)
* **ValidationRegistry:** [0x8004C11C213ff7BaD36489bcBDF947ba5eee289B](https://amoy.polygonscan.com/address/0x8004C11C213ff7BaD36489bcBDF947ba5eee289B)
***
## Read-Only Examples
The snippets below use `viem` to query the Amoy contracts. They are
deterministic and read-only, and work before any agents are minted. Note that
`tokenURI` and `ownerOf` only resolve for existing `agentId` values.
```ts theme={null}
import { createPublicClient, http } from "viem";
import { polygonAmoy } from "viem/chains";
import "dotenv/config";
const IDENTITY = "0x8004ad19E14B9e0654f73353e8a0B600D46C2898" as const;
const REPUTATION = "0x8004B12F4C2B42d00c46479e859C92e39044C930" as const;
const VALIDATION = "0x8004C11C213ff7BaD36489bcBDF947ba5eee289B" as const;
const erc721View = [
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ type:"string" }] },
{ type: "function", name: "symbol", stateMutability: "view", inputs: [], outputs: [{ type:"string" }] },
{ type: "function", name: "tokenURI", stateMutability: "view", inputs: [{ type:"uint256" }], outputs: [{ type:"string" }] },
];
const validationView = [
{ type:"function", name:"getValidationStatus", stateMutability:"view",
inputs:[{type:"bytes32"}],
outputs:[{type:"address"},{type:"uint256"},{type:"uint8"},{type:"bytes32"},{type:"uint256"}] },
];
async function main() {
const pub = createPublicClient({ chain: polygonAmoy, transport: http() });
// Identity registry metadata
const name = await pub.readContract({ address: IDENTITY, abi: erc721View, functionName: "name" });
const symbol = await pub.readContract({ address: IDENTITY, abi: erc721View, functionName: "symbol" });
console.log({ name, symbol }); // e.g., "Agent Identity", "AGNT"
// Sample agentId=1 (will throw if not yet minted)
// const uri = await pub.readContract({ address: IDENTITY, abi: erc721View, functionName: "tokenURI", args:[1n] });
// console.log({ uri });
// Check a validation status by requestHash (bytes32)
// const reqHash = "0x" + "00".repeat(32) as `0x${string}`;
// const status = await pub.readContract({ address: VALIDATION, abi: validationView, functionName: "getValidationStatus", args:[reqHash] });
// console.log({ status });
}
main();
```
### Where these come from in the spec
* Identity uses ERC-721 + URIStorage (hence tokenURI and ownership semantics).
* Validation exposes getValidationStatus/getSummary to query results.
You can use more
[modern interfaces and standards backwards compatible with ERC-721](https://eips.ethereum.org/EIPS/eip-6220)
to access the same values if you wish.
### How ERC-8004 Relates to A2A, MCP, and x402
A2A is the coordination layer (authentication, agent cards, lifecycle). ERC-8004
adds discovery and trust so agents from different organizations can find and
assess each other.
MCP is the tool/data layer. The ERC-8004 Identity record can link to MCP
endpoints, allowing agent wallets or marketplaces to enumerate capabilities
consistently.
x402 is the payment layer. ERC-8004 is payment-agnostic. You can optionally
embed payment proofs (such as x402 transaction hashes) in off-chain feedback
files referenced by the Reputation registry.
## Design Goals and Current Status
ERC-8004 is a Standards Track ERC proposal created August 13, 2025, currently
under public review.
The standard introduces pluggable trust models (reputation, crypto-economic
validation, zk/TEE attestations) with security proportional to the value at
risk. The registries are designed to be per-chain singletons for
straightforward discovery.
Active discussion topics include: handling onchain vs. off-chain data,
encouraging multiple independent reputation providers, and keeping payments
decoupled while allowing payment proofs to be referenced.
Development discussion takes place in the [ERC-8004 Builders](https://t.me/ERC8004)
Telegram group and the
[Ethereum Magicians forum thread](https://ethereum-magicians.org/t/erc-8004-trustless-agents/25098).
# Agentic Payments Introduction
Source: https://docs.polygon.technology/payment-services/agentic-payments/agent-integration/intro
Conceptual overview of agentic payments and how autonomous agents transact on Polygon.
# Agentic Payments Introduction
**Agentic Payments** are payments initiated and completed by autonomous software
entities (agents) without direct human action at every step.
Instead of requiring a person to click "confirm transaction", an agent can
negotiate prices, sign intents, and pay onchain in the background, using
predefined policies or earned balances.
This shifts onchain activity from user-driven to intent-driven. An agent
doesn't just send tokens: it executes a purpose, such as subscribing to data,
paying per API call, or settling micro-invoices in real time.
By encoding payment logic inside agents and standardizing protocols like
[x402](https://x402.dev), Polygon allows any intelligent system to become an
autonomous economic participant.
***
## What is an Agent?
An **agent** is an autonomous program that can perceive, decide, and act on
behalf of a user or a system.
Agents combine reasoning models (LLMs, decision trees) with access to data,
APIs, and onchain actions. They can interpret natural language commands,
interact with contracts, and coordinate with other agents without exposing
private keys or depending on centralized custody.
On Polygon, agents work with a suite of tooling including **AgentKit**, **Model
Context Protocol (MCP)**, and **Unified APIs** to perform secure blockchain
operations. This ecosystem makes it possible for an AI assistant, a trading
bot, or a DAO delegate to act as an onchain entity: context-aware,
policy-bounded, and continuously learning.
***
## How Agentic Payments Work
Instead of traditional wallet interactions, payments are executed via
**intents** or **facilitated flows** such as x402.
An agent can detect that an API call costs \$0.002 in USDC, confirm the
requirement, and complete the payment automatically, all in milliseconds.
Because they are keyless and infrastructure-agnostic, agentic payments work
across environments: from local LLMs to decentralized marketplaces. This makes
microtransactions, dynamic subscriptions, and per-use pricing viable for both
human-facing apps and AI agents.
***
## Standards and Protocols
Polygon supports two complementary standards for agentic payments:
* **[x402](/payment-services/agentic-payments/x402/intro/)**: An HTTP-based protocol that uses the 402 Payment Required status code to gate API access behind onchain payments. Clients pay per request; no subscription or API key required.
* **[ERC-8004](/payment-services/agentic-payments/agent-integration/erc8004/)**: An onchain trust layer for autonomous agents, providing Identity, Reputation, and Validation registries so agents from different organizations can discover and assess each other.
# Polygon Agentic CLI
Source: https://docs.polygon.technology/payment-services/agentic-payments/polygon-agent-cli
A single CLI toolkit for AI agents: smart contract wallets, token operations, x402 micropayments, and onchain identity.
The Polygon Agentic CLI is a command-line toolkit that gives AI agents and autonomous applications full access to the Polygon payments stack in a single install. It handles wallet creation, token transfers, cross-chain bridging, x402 micropayments, and ERC-8004 onchain identity without requiring agents to manage multiple SDKs or dependencies.
Source code, releases, and contribution guide.
## What it includes
| Capability | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Smart contract wallets** | Session-based wallets via Sequence. Create and manage wallets with scoped permissions per agent session. |
| **Token operations** | Send, swap, bridge, and deposit tokens via Trails routing. Single command for any cross-chain transfer. |
| **x402 micropayments** | Pay x402-protected API endpoints and register resources as a seller. Built-in buyer and seller flows. |
| **ERC-8004 identity** | Register and update onchain agent identity and reputation. Interact with Identity, Reputation, and Validation registries. |
| **Encrypted storage** | AES-256-GCM encrypted local storage for keys and session state. No plaintext secrets on disk. |
## Install
```bash theme={null}
npm install -g @0xpolygon/agent-cli
```
Or run directly without installing:
```bash theme={null}
npx @0xpolygon/agent-cli
```
## Quick start
**1. Create a wallet**
```bash theme={null}
polygon-agent wallet create
```
This generates a smart contract wallet secured by a session key. The private key is stored locally with AES-256-GCM encryption.
**2. Fund the wallet**
```bash theme={null}
polygon-agent wallet fund --network polygon
```
**3. Send tokens**
```bash theme={null}
polygon-agent token send \
--to 0xRecipient \
--amount 10 \
--token USDC \
--network polygon
```
**4. Pay an x402 endpoint**
```bash theme={null}
polygon-agent x402 pay --url https://api.example.com/resource
```
The CLI detects the 402 response, constructs the payment, and retries the request automatically.
**5. Register agent identity**
```bash theme={null}
polygon-agent identity register --name "my-agent" --description "Trading agent"
```
This writes an ERC-8004 identity record on Polygon mainnet.
## Key commands
### Wallet
| Command | Description |
| ---------------- | ------------------------------------------------ |
| `wallet create` | Create a new session-based smart contract wallet |
| `wallet list` | List all managed wallets |
| `wallet balance` | Show token balances for a wallet |
| `wallet export` | Export wallet credentials |
### Token operations
| Command | Description |
| --------------- | ------------------------------ |
| `token send` | Transfer tokens to an address |
| `token swap` | Swap tokens via Trails routing |
| `token bridge` | Bridge tokens cross-chain |
| `token deposit` | Deposit tokens to a protocol |
### x402
| Command | Description |
| ------------- | ------------------------------------------------- |
| `x402 pay` | Pay an x402-protected endpoint |
| `x402 serve` | Protect a local endpoint with x402 payment gating |
| `x402 status` | Check payment status for a resource |
### Identity (ERC-8004)
| Command | Description |
| --------------------- | ------------------------------------------------------------- |
| `identity register` | Register onchain agent identity |
| `identity update` | Update identity metadata |
| `identity reputation` | Read reputation score for an agent address |
| `identity verify` | Verify an agent's credentials against the Validation registry |
## Configuration
The CLI reads from `~/.polygon-agent/config.json` by default. You can override with `--config`:
```bash theme={null}
polygon-agent --config ./my-config.json wallet list
```
Key configuration options:
```json theme={null}
{
"network": "polygon",
"rpc": "https://polygon-rpc.com",
"keystore": "~/.polygon-agent/keys",
"x402": {
"facilitator": "https://facilitator.polygon.technology"
}
}
```
## Security
* Private keys are encrypted at rest with AES-256-GCM
* Session keys are scoped per operation with configurable spending limits
* No keys are transmitted to external services
## Related
HTTP-native micropayments that the CLI uses under the hood.
Onchain identity and reputation standard for agents.
MCP server for wallet operations inside Claude and Cursor.
Wallet infrastructure designed for autonomous agent use.
# Facilitator addresses
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/analytics
Reference list of x402 Polygon mainnet and Amoy facilitator addresses for analytics and payment verification.
You can also track an up-to-date list of facilitators and their addresses at
[facilitators.x402.watch](https://facilitators.x402.watch).
## Polygon Amoy
The Amoy testnet facilitator ([x402-amoy.polygon.technology](https://x402-amoy.polygon.technology)) runs **x402 v2**. Signer addresses:
```text theme={null}
Address: 0x5a30808427e4C50Abe7430EEA7722CA88379A6a1
Address: 0x7155A302A792C2aD555F2B66E5bC2709044D12dF
Address: 0x17070da58e348b27098e1a04e848AC8faA66DA62
Address: 0x812ab2C611D5103a0907028226B756466017FcF8
Address: 0x863f5153F416f0186C762Eeb5Ce7721c694f344e
Address: 0x680B2227bde0858A269B4a3FE24eaaFC3C3E1e96
Address: 0x8D7e970Cb3c0865d46C0C5bd3a1c1203a9d12aBc
Address: 0x06853A2390c500D351d470d5c191794404AC6FA4
```
## Polygon (mainnet)
Polygon maintains a rotation of three sets of eight addresses:
```
x402_1.keys:Address: 0x29df60c005506AA325d7179F6e09eB4b4875dAde
x402_1.keys:Address: 0xF09A94831C18566781f70937f0996B96EfE691C8
x402_1.keys:Address: 0x42618f623Ec19beFf78dE9DbBFB653BfEaC05D09
x402_1.keys:Address: 0x3202643514D128FF0B4625D2682c0244CF58131c
x402_1.keys:Address: 0x11DA3fe5ADA6f5382Ebe972f14C3585DA4E65AeA
x402_1.keys:Address: 0x135DfE729F9bbd7F88181E1B708d7506fd499140
x402_1.keys:Address: 0xDcb0Ac359025dC0DB1e22e6d33F404e5c92A1564
x402_1.keys:Address: 0x99EFc08BB42282716fB59D221792f5207f714C9d
```
```
x402_2.keys:Address: 0xbE5115800247405f020197BF473eBFd085a2C635
x402_2.keys:Address: 0x5eAb3D78264Dab340340d6a37Ff0836464Ae5773
x402_2.keys:Address: 0xE5D4197eFd5D03E3f30cBf11C0fF63Eb95a0A656
x402_2.keys:Address: 0xfac8Edb989f1ba7F9dBb7A1233542D4e1fD6144F
x402_2.keys:Address: 0xaFdbfaCb5ed691bf0bCFA660901f299ce9775489
x402_2.keys:Address: 0x1e48Ed59a502D0B324CdAf83362865b3ff49ABa2
x402_2.keys:Address: 0xA1dcBDC2C34577ACD4A1152A98807B2f281A112e
x402_2.keys:Address: 0x9e281D4e26E1a4e7C27014E2ca8Cee7F2D44fa52
```
```
x402_3.keys:Address: 0x76FCb8ae3365A487E6EA235386C1cf3AbADeDA60
x402_3.keys:Address: 0x9523B120C75640469f1D16490Da0388928229452
x402_3.keys:Address: 0x153F3A70e4400c211d9B482b62aD721Bb02F96F6
x402_3.keys:Address: 0xd5dD012019C58882Dd507A8b3fCBB7b62e9a24c3
x402_3.keys:Address: 0xfff23108338C218F895d75980E14688218D4E92a
x402_3.keys:Address: 0xF744e153Ef63f7EEe4a58e0F13761D16C2125EE3
x402_3.keys:Address: 0x0a8B10FE8Bd3072351600Adef4796F3F7aF72Ab0
x402_3.keys:Address: 0x971b4079A618F72Fa0F1792b07ed5923dfBF3500
```
## ThirdWeb
```
0x80c08de1a05Df2bD633CF520754e40fdE3C794d3
```
## Questflow
```
0x4a288FA07fC40F701e4fd2620F0a14338e12a4D7
```
## PayAI
```
0xc6699d2aadA6c36Dfea5C248DD70f9CB0235cB63
```
## x402.rs
```
0xD8Dfc729cBd05381647EB5540D756f4f8Ad63eec
```
## Corbits
```
0x06F0BfD2C8f36674DF5cdE852c1eeD8025C268C9
```
# x402 Directory
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/directory
Directory of x402-gated APIs that settle on Polygon.
This page lists x402 APIs that settle on Polygon. To add your API, contact the
Polygon team.
Directory listing is available in the full docs.
# x402 How It Works
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/guides/how-it-works
Conceptual explanation of the x402 protocol: components, payment flow, design goals, and known limitations.
The x402 protocol lets clients pay for HTTP resources (APIs, endpoints, content)
via blockchain transactions (with [fiat support in progress](https://github.com/coinbase/x402/pull/446)).
A server responds with HTTP 402 ("Payment Required"), the client pays, and
the server grants access. No subscription, API key, or manual onboarding
is required.
***
## 1. Components
| Component | Role | Example / note |
| ------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Client** | Requests a resource & pays | Browser, AI agent, mobile SDK |
| **Resource Server** | Protects endpoint, issues-402, verifies payment | API or web service |
| **Facilitator** | Optional verification + settlement layer | Offloads blockchain logic [oai\_citation:1‡QuickNode](https://www.quicknode.com/guides/infrastructure/how-to-use-x402-payment-required?utm_source=chatgpt.com) |
| **Payment Scheme** | Defines chain, token, network, and format | e.g., USDC on Polygon, ERC-3009 scheme |
***
## 2. Payment Flow Sequence
The typical sequence for one request:
1. Client → Resource Server: **HTTP GET /endpoint**
2. Resource Server → Client: **HTTP 402 Payment Required**, body includes
`PaymentRequirements` describing required payment.
3. Client selects a requirement, builds a `PaymentPayload`, encodes it (e.g.,
Base64) in header `X-PAYMENT` and retries request.
4. Resource Server verifies the payload (either locally or via the Facilitator).
5. Facilitator / Resource Server settles payment onchain (if not already).
6. Resource Server → Client: **HTTP 200 OK**, body includes the requested
resource, header `X-PAYMENT-RESPONSE` contains receipt details.
Steps 2-3 can be skipped if the client already knows the payment
requirement ahead of time.
***
## 3. Implementation Snapshot
### Server (Seller Side)
* Protect endpoint with middleware (e.g.,\
`paymentMiddleware("0xYourAddress", { "/path": { price:"$0.01", network:"polygon" } })`).
* On first unauthorized request: respond `402` with JSON:
```json theme={null}
{
"x402Version": 1,
"accepts": [
{
"scheme": "erc-3009",
"network": "polygon",
"token": "USDC_address",
"maxAmountRequired": "1000000", // atomic units
"description": "Access to premium API"
}
],
"error": null
}
```
````
On retry with X-PAYMENT header: verify using facilitator or your logic, then return 200 OK and include:
```http
X-PAYMENT-RESPONSE:
````
Full tutorial: [x402 Quickstart for Sellers](./quickstart-sellers.mdx)
### Client (Buyer Side)
* Send initial request → get 402 + payment info.
* Build payment per the scheme (client signs, selects token/chain).
* Retry request adding header:
```http theme={null}
X-PAYMENT:
```
* Receive 200 OK and decode header X-PAYMENT-RESPONSE to confirm payment details.
Full tutorial: [x402 Quickstart for Buyers](./quickstart-buyers.mdx)
### Facilitator (optional)
Provides endpoints such as `/verify` and `/settle` for payment payloads.
```http theme={null}
POST /verify
{
"x402Version":1,
"paymentHeader": "",
"paymentRequirements": { … }
}
```
Facilitators can implement their own logic to verify the payment payload and
settle the payment onchain, and they can also choose how and when to settle -
some will compile queues of settlements to do later, while others will settle
instantly.
## Key Design Goals and Benefits
* **HTTP-native**: Uses standard HTTP codes and headers. No additional protocol layer.
* **Chain and token agnostic**: Works across any chain or stablecoin (e.g., USDC).
* **Minimal integration**: One-line middleware on the server side, or a few client calls on the buyer side.
* **Micropayments**: Low friction and low cost, suitable for per-request pricing.
* **Autonomous agents**: AI systems can transact without per-transaction human approval.
## Known Limitations and Future Directions
* Use the correct network label (e.g., `"polygon-amoy"` vs. `"polygon"`). Mismatches cause payment failures.
* The protocol is still evolving. Some paid endpoints may require settlement delays.
* For high-volume usage, consider deferred payment flows: aggregate many calls, then settle in batch.
* Protocol governance is managed by an open community to prevent vendor lock-in.
# x402 Quickstart for Buyers
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/guides/quickstart-buyers
Tutorial: set up an x402 buyer client on Polygon to automatically pay for and access paywalled API endpoints.
This tutorial walks through setting up an x402 buyer client on Polygon. By the
end, your client will automatically detect 402 Payment Required responses,
pay in USDC, and retrieve the unlocked resource.
This tutorial uses test credentials and local endpoints for clarity.
In production, never expose private keys or facilitator URLs publicly.
## Prerequisites
| Requirement | Example / Notes |
| --------------- | -------------------------------------------------------------------------------- |
| Wallet | Metamask, Rabby, or any Polygon-compatible wallet with USDC |
| JS env | Node.js 18+ with npm or something of better quality, like [Bun](https://bun.sh/) |
| Polygon testnet | Amoy RPC |
| .env | Must include PRIVATE\_KEY, .KEY; optional FACILITATOR\_URL, RESOURCE\_URL |
## Install dependencies
Install one of the official HTTP clients:
> Wherever `bun` is used, replace it with `npm` or your preferred package manager.
```bash theme={null}
bun install x402-fetch
# or
bun install x402-axios
```
We will use x402-fetch in this tutorial.
Then install viem + dotenv:
```bash theme={null}
bun install viem dotenv
```
## Create a wallet client
Set up a Polygon wallet using [viem](https://viem.sh/).
This wallet signs and sends the USDC payment when a 402 challenge is detected.
```ts theme={null}
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { polygonAmoy } from "viem/chains";
import "dotenv/config";
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) throw new Error("PRIVATE_KEY not set in .env");
const account = privateKeyToAccount(`0x${privateKey}`);
const client = createWalletClient({
account,
chain: polygonAmoy,
transport: http(),
});
console.log("Wallet address:", account.address);
```
Expected output:
```bash theme={null}
Wallet address: 0x1234abcd...
```
## Make paid requests automatically
Use x402-fetch or x402-axios to intercept 402 responses and complete the payment
automatically.
```ts theme={null}
import { wrapFetchWithPayment, decodeXPaymentResponse } from "x402-fetch";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { polygonAmoy } from "viem/chains";
import "dotenv/config";
const account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`);
const client = createWalletClient({ account, chain: polygonAmoy, transport: http() });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const FACILITATOR_URL = process.env.FACILITATOR_URL || "https://x402-amoy.polygon.technology";
const RESOURCE_URL = process.env.RESOURCE_URL || "http://127.0.0.1:4021/weather";
(async () => {
const response = await fetchWithPayment(RESOURCE_URL, { method: "GET" });
const body = await response.json();
console.log("Response body:", body);
if (body.report) {
const rawPaymentResponse = response.headers.get("x-payment-response");
console.log("Raw payment header:", rawPaymentResponse);
const decoded = decodeXPaymentResponse(rawPaymentResponse);
console.log("Decoded payment:", decoded);
}
})();
```
The client:
* Sends the request
* Receives 402 Payment Required
* Pays in USDC via facilitator
* Retries automatically
* Returns the unlocked JSON data
## Reference
Configuration, error codes, and guardrails.
### Schema
| name | type | required | example | description |
| ---------------- | ------ | -------- | ----------------------------------------- | ---------------------- |
| PRIVATE\_KEY | string | ✅ | `"abcd1..."` | Wallet key for Polygon |
| FACILITATOR\_URL | string | optional | `"https://x402-amoy.polygon.technology"` | Payment relay endpoint |
| RESOURCE\_URL | string | ✅ | `"https://api.example.com/paid-endpoint"` | Target API URL |
| USDC | token | implied | USDC on Polygon | Payment asset |
### Errors
Common failure modes and fixes:
| code / case | meaning | fix |
| -------------------- | --------------------------- | ------------------------------------------- |
| MISSING\_CONFIG | Wallet or URL not set | Verify .env keys |
| DUPLICATE\_PAYMENT | Payment replay detected | Retry with new request ID |
| BAD\_PAYMENT\_HEADER | Invalid signature or amount | Ensure client and facilitator are in sync |
| 402\_LOOP | API keeps returning 402 | Check facilitator health or endpoint config |
### Do / Don't Do Guardrails
| ✅ Do | ❌ Don't |
| ---------------------------------------- | ------------------------------------- |
| Use a sandbox facilitator for testing | Hardcode private keys in your scripts |
| Log decoded payment responses | Parse payment headers manually |
| Use `wrapFetchWithPayment` or x402-axios | Re-implement 402 logic yourself |
### References
* [x402-fetch npm docs](https://www.npmjs.com/package/x402-fetch)
* [x402-axios npm docs](https://www.npmjs.com/package/x402-axios)
* [x402 Developer Docs](https://x402.dev)
# x402 Quickstart for Sellers
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/guides/quickstart-sellers
Tutorial: protect API endpoints with x402 payment middleware to accept USDC payments on Polygon.
This tutorial shows how to add x402 payment middleware to your API or service.
By the end, buyers and AI agents will automatically pay in USDC when accessing
your protected endpoints.
These snippets are for demonstration only.
Store private keys and facilitator URLs in a secure vault. Never hardcode secrets.
## Prerequisites
| Requirement | Example / Notes |
| ---------------------- | ---------------------------------------------------------------------------------------------------------- |
| Wallet to receive USDC | Any EVM-compatible wallet (Metamask, Rabby, Safe, etc.) |
| JS end | [Node.js ≥18](https://nodejs.org/en) with npm, or something of better quality, like [Bun](https://bun.sh/) |
| Existing API / server | Express, Next.js, Hono, or any web framework |
| Polygon network | [Amoy testnet](https://wiki.polygon.technology/docs/pos/polygon-bridge/amoy-bridge-guide/) or mainnet |
## Install dependencies
> Wherever `bun` is used, replace it with `npm` or your preferred package manager.
Pick your middleware:
```bash theme={null}
bun install x402-express
```
or
```bash theme={null}
bun install x402-next
```
or
```bash theme={null}
bun install x402-hono
```
## Configure the payment middleware
Each middleware protects your API routes and routes payments to your receiving
wallet.
You must specify:
* Wallet address: where you receive USDC
* Network: e.g. "polygon-amoy" or "polygon"
* Facilitator URL: default for Polygon is [https://x402.polygon.technology](https://x402.polygon.technology)
* Route config: endpoint price and optional metadata
### Example - Express
```ts theme={null}
import express from "express";
import { paymentMiddleware } from "x402-express";
const app = express();
app.use(paymentMiddleware(
"0xCA3953e536bDA86D1F152eEfA8aC7b0C82b6eC00", // receiver wallet
{
"GET /weather": {
price: "$0.001",
network: "polygon",
config: {
description: "Get current weather data for any location",
inputSchema: {
type: "object",
properties: { location: { type: "string" } }
},
outputSchema: {
type: "object",
properties: {
weather: { type: "string" },
temperature: { type: "number" }
}
}
}
}
},
{
url: process.env.FACILITATOR_URL || "https://x402.polygon.technology"
}
));
app.get("/weather", (req, res) => {
res.send({
report: { weather: "sunny", temperature: 70 }
});
});
app.listen(4021, () => {
console.log("Server running at http://localhost:4021");
});
```
Buyers calling `/weather` will automatically receive a 402 challenge, pay, and then receive the weather data.
### Example - Next.js
```ts theme={null}
import { paymentMiddleware } from "x402-next";
export const middleware = paymentMiddleware(
"0xYourAddress", // receiving wallet
{
"/protected": {
price: "$0.01",
network: "polygon-amoy",
config: { description: "Access to protected content" }
}
},
{
url: "https://x402.polygon.technology"
}
);
export const config = {
matcher: ["/protected/:path*"]
};
```
Buyers calling `/protected` will automatically receive a 402 challenge, pay, and then receive the protected content.
### Example - Hono
```ts theme={null}
import { paymentMiddleware } from "x402-next";
export const middleware = paymentMiddleware(
"0xYourAddress", // receiving wallet
{
"/protected": {
price: "$0.01",
network: "polygon-amoy",
config: { description: "Access to protected content" }
}
},
{
url: "https://x402.polygon.technology"
}
);
export const config = {
matcher: ["/protected/:path*"]
};
```
Buyers calling `/protected` will automatically receive a 402 challenge, pay, and then receive the protected content.
## Reference
Configuration parameters, error codes, and guardrails.
### Schema
| name | type | required | example | description |
| ---------------- | ------ | -------- | ----------------------------------- | --------------------------------------- |
| walletAddress | string | ✅ | `0xYourAddress` | Address that receives USDC payments |
| network | string | ✅ | `polygon`. | Network Identifier |
| price | string | ✅ | `"$0.001"` | Cost per request in USDC |
| FACILITATOR\_URL | string | optional | `"https://x402.polygon.technology"` | Payment facilitator endpoint |
| config | object | optional | JSON schema | Used for discoverability in x402 Bazaar |
### Do / Don't Do Guardrails
| ✅ Do | ❌ Don't |
| ------------------------------------------------- | ------------------------------------------------------ |
| Use `https://x402.polygon.technology` for Polygon | Expose private keys in middleware |
| Include schema metadata for AI discoverability | Hardcode facilitator URLs in code |
| Test with Amoy before mainnet deployment | Skip setting network/price, as buyers will fail to pay |
### References
* [x402-express npm docs](https://www.npmjs.com/package/x402-express)
* [x402-next npm docs](https://www.npmjs.com/package/x402-next)
* [x402-hono npm docs](https://www.npmjs.com/package/x402-hono)
* [x402 Developer Docs](https://x402.dev)
* [Coinbase x402 examples](https://github.com/coinbase/x402/tree/main/examples/typescript/servers)
* [Polygon quickstart repo](https://github.com/AkshatGada/x402_Polygon/tree/feature/facilitator-amoy/demo/quickstart-local)
### Errors
| case / code | meaning | fix |
| ---------------- | ---------------------------- | ---------------------------------- |
| 402\_LOOP | Client can't fulfill payment | Check facilitator URL and wallet |
| INVALID\_NETWORK | Wrong network label | Use "polygon-amoy" or "polygon" |
| BAD\_CONFIG | Missing route schema | Define price and network per route |
# Using the Polygon Facilitator
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/guides/using-polygon-facilitator
Reference for the Polygon x402 facilitator: endpoint URLs and signing addresses.
## Polygon mainnet
The Polygon mainnet facilitator is available at
[x402.polygon.technology](https://x402.polygon.technology). Health status is
available at
[https://x402.polygon.technology/health](https://x402.polygon.technology/health).
To add the Polygon Facilitator to your API, follow the
[x402 Quickstart for Sellers](./quickstart-sellers.mdx). To use it as part of
a multi-facilitator setup, see
[Using the x402facilitators package](/payment-services/agentic-payments/x402/guides/using-x402facilitators/).
### Mainnet signer addresses
Polygon maintains a rotation of three sets of eight addresses:
```text theme={null}
x402_1.keys:Address: 0x29df60c005506AA325d7179F6e09eB4b4875dAde
x402_1.keys:Address: 0xF09A94831C18566781f70937f0996B96EfE691C8
x402_1.keys:Address: 0x42618f623Ec19beFf78dE9DbBFB653BfEaC05D09
x402_1.keys:Address: 0x3202643514D128FF0B4625D2682c0244CF58131c
x402_1.keys:Address: 0x11DA3fe5ADA6f5382Ebe972f14C3585DA4E65AeA
x402_1.keys:Address: 0x135DfE729F9bbd7F88181E1B708d7506fd499140
x402_1.keys:Address: 0xDcb0Ac359025dC0DB1e22e6d33F404e5c92A1564
x402_1.keys:Address: 0x99EFc08BB42282716fB59D221792f5207f714C9d
```
```text theme={null}
x402_2.keys:Address: 0xbE5115800247405f020197BF473eBFd085a2C635
x402_2.keys:Address: 0x5eAb3D78264Dab340340d6a37Ff0836464Ae5773
x402_2.keys:Address: 0xE5D4197eFd5D03E3f30cBf11C0fF63Eb95a0A656
x402_2.keys:Address: 0xfac8Edb989f1ba7F9dBb7A1233542D4e1fD6144F
x402_2.keys:Address: 0xaFdbfaCb5ed691bf0bCFA660901f299ce9775489
x402_2.keys:Address: 0x1e48Ed59a502D0B324CdAf83362865b3ff49ABa2
x402_2.keys:Address: 0xA1dcBDC2C34577ACD4A1152A98807B2f281A112e
x402_2.keys:Address: 0x9e281D4e26E1a4e7C27014E2ca8Cee7F2D44fa52
```
```text theme={null}
x402_3.keys:Address: 0x76FCb8ae3365A487E6EA235386C1cf3AbADeDA60
x402_3.keys:Address: 0x9523B120C75640469f1D16490Da0388928229452
x402_3.keys:Address: 0x153F3A70e4400c211d9B482b62aD721Bb02F96F6
x402_3.keys:Address: 0xd5dD012019C58882Dd507A8b3fCBB7b62e9a24c3
x402_3.keys:Address: 0xfff23108338C218F895d75980E14688218D4E92a
x402_3.keys:Address: 0xF744e153Ef63f7EEe4a58e0F13761D16C2125EE3
x402_3.keys:Address: 0x0a8B10FE8Bd3072351600Adef4796F3F7aF72Ab0
x402_3.keys:Address: 0x971b4079A618F72Fa0F1792b07ed5923dfBF3500
```
## Polygon Amoy
The Polygon Amoy testnet facilitator is available at
[x402-amoy.polygon.technology](https://x402-amoy.polygon.technology). Health
status is available at
[https://x402-amoy.polygon.technology/health](https://x402-amoy.polygon.technology/health).
**The Amoy facilitator runs x402 v2.** Use a v2-compatible client when
targeting this endpoint (see the [x402 Quickstart for Buyers](./quickstart-buyers.mdx) for Amoy examples).
### Amoy signer addresses
```text theme={null}
Address: 0x5a30808427e4C50Abe7430EEA7722CA88379A6a1
Address: 0x7155A302A792C2aD555F2B66E5bC2709044D12dF
Address: 0x17070da58e348b27098e1a04e848AC8faA66DA62
Address: 0x812ab2C611D5103a0907028226B756466017FcF8
Address: 0x863f5153F416f0186C762Eeb5Ce7721c694f344e
Address: 0x680B2227bde0858A269B4a3FE24eaaFC3C3E1e96
Address: 0x8D7e970Cb3c0865d46C0C5bd3a1c1203a9d12aBc
Address: 0x06853A2390c500D351d470d5c191794404AC6FA4
```
# Using x402facilitators package
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/guides/using-x402facilitators
How to use the @swader/x402facilitators package to add multi-facilitator routing to x402 middleware.
`@swader/x402facilitators` bundles every public x402 facilitator (Polygon,
Coinbase, Kamiyo, Questflow, and others) into a single package so your
middleware can swap or load-balance providers with minimal code.
* Import `auto` for automatic routing across healthy facilitators.
* Import named facilitators (e.g., `polygon`, `coinbase`, `thirdweb`) for
explicit routing or when you need to supply custom credentials.
* Use discovery helpers to enumerate every resource a facilitator can serve,
useful for LLM agents that need to list tools before calling them.
The package is pure TypeScript and mirrors the data shown on
[facilitators.x402.watch](https://facilitators.x402.watch).
## When to Use This Package
* You are a seller and want your API to accept requests from multiple
facilitators without hardcoding URLs.
* You are a buyer or agent who needs the same code to work on Polygon Amoy
today and on Base or Solana in the future.
* You maintain an orchestrator or LLM agent registry and need to query
facilitator metadata and discovery endpoints programmatically.
* You have already completed the
[x402 Quickstart for Buyers](./quickstart-buyers.mdx) or Sellers and now need
production-ready facilitator management.
## Install
```bash theme={null}
bun add @swader/x402facilitators
# npm install @swader/x402facilitators
```
> Tip for LLM agents: store the package name as `x402facilitators` and check
> `package.json` before reinstalling.
## Multi-Facilitator Routing
```ts theme={null}
import { paymentMiddleware } from "x402"; // pseudocode - use your client
import { auto } from "@swader/x402facilitators";
paymentMiddleware(address, resources, auto);
```
* `auto` distributes requests across healthy facilitators, keeping your service
online if one provider has downtime.
* Agents can use this by routing all payment middleware through `auto` without
any additional configuration.
## Choose Explicit Facilitators
```ts theme={null}
import {
coinbase,
questflow,
polygon,
thirdweb,
} from "@swader/x402facilitators";
paymentMiddleware(address, resources, polygon);
paymentMiddleware(address, resources, coinbase({
apiKey: process.env.CDP_API_KEY!,
}));
paymentMiddleware(address, resources, thirdweb({
secretKey: process.env.THIRDWEB_SECRET_KEY!,
}));
```
* Each facilitator exports both the config function and a metadata object (e.g.,
`coinbaseFacilitator`) for use in dashboards.
* Simple facilitators (`polygon`, `payai`, `x402rs`, etc.) return a plain
`{ url }` config and require no additional props.
## Discover Facilitator Resources Programmatically
```ts theme={null}
import {
discoverableFacilitators,
listAllFacilitatorResources,
kamiyoDiscovery,
} from "@swader/x402facilitators";
const kamiyoResources = await listAllFacilitatorResources(kamiyoDiscovery);
const everything = await Promise.all(
discoverableFacilitators.map(listAllFacilitatorResources)
);
console.log(everything.flat());
```
* Call this before planning an autonomous workflow to enumerate every tool
guarded by Kamiyo, Coinbase, Questflow, and others.
* Cache the results. Discovery endpoints may rate-limit anonymous callers.
## Next Steps
* To use the Polygon-hosted facilitator directly, see
[Using the Polygon Facilitator](./using-polygon-facilitator.mdx).
* If you have not yet wired up payments, start with the
[x402 Quickstart for Buyers](./quickstart-buyers.mdx), then add
`@swader/x402facilitators` on top.
* To contribute a new facilitator: fork the upstream repo, add
`src/facilitators/.ts`, export it from `src/lists/all.ts`, and run
`bun run build`. The PR will publish to npm and appear on
[facilitators.x402.watch](https://facilitators.x402.watch).
Reference: [`Swader/x402facilitators`](https://github.com/Swader/x402facilitators/).
# x402 Introduction
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/intro
What x402 is and how Polygon supports it: available networks, facilitators, and where to start.
x402 is an open payment protocol that brings blockchain payments into the HTTP
standard. By reusing the HTTP 402 Payment Required status code, it lets
developers handle onchain and agentic payments with the same tools they already
use for APIs and web services.
Instead of building complex wallet integrations or subscription systems,
developers can treat payments like any other part of the HTTP request/response
cycle. This approach works for web2 developers experimenting with paid APIs and
for web3 builders who need a lightweight way to support pay-per-use APIs,
agent-to-agent transactions, and micropayments.
[Read more about x402 here](https://x402.gitbook.io/x402).
## Access on Polygon
Polygon supports x402 on Mainnet and Amoy through the following facilitators:
1. Polygon Mainnet Facilitator: [https://x402.polygon.technology](https://x402.polygon.technology)
2. Polygon Testnet Facilitator: [https://x402-amoy.polygon.technology](https://x402-amoy.polygon.technology)
3. [ThirdWeb](https://playground.thirdweb.com/payments/x402)
4. [x402.rs Facilitator Endpoint](https://facilitator.x402.rs/)
5. [Pay.AI](https://payai.network/)
6. [Corbits](https://docs.corbits.dev/about-x402/facilitators)
7. [Questflow](https://facilitator.questflow.ai/)
To implement x402 with Polygon and your applications or agents, follow the
tutorials and guides in these docs. Alternatively, the x402 community has
created
[multiple examples](https://github.com/coinbase/x402/tree/main/examples/typescript)
which can be adapted for Polygon.
# x402 Autopay
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/tools/autopay
How to install and use the AutoPay Chromium extension for x402 micropayments in the browser.
AutoPay is a Chromium extension that lets you fund a wallet for browser-based
micropayments and automatically pays for any x402 API endpoint it encounters.
While x402 is primarily designed for agent-to-agent payments, AutoPay extends
that model to human users browsing the web.
A basic demo video of how it works is here:
## Install AutoPay
The extension is not yet available on the Chrome Web Store. Install it manually
using one of two approaches.
### Option A: Download zip
Download the latest release zip from [https://github.com/Swader/x402autopay/releases](https://github.com/Swader/x402autopay/releases) and unpack it.
### Option B: Clone and build
1. Clone the repository:
```bash theme={null}
git clone https://github.com/Swader/x402autopay
```
2. Install the dependencies:
```bash theme={null}
bun install
```
3. Build the extension:
```bash theme={null}
bun run build
```
### Load the extension in Chrome
Whichever approach you used, load the extension in Chrome:
1. Open the Extensions page in your browser.
2. Enable **Developer mode**.
3. Click **Load unpacked**.
4. Select the `dist` folder: either the one created by `bun run build`, or the
folder you unpacked from the zip file.
## Use AutoPay
A video walkthrough is also available:
On first use, AutoPay prompts you to import or create a wallet and fund it.
Screenshot placeholder: wallet setup screen.
Set a passphrase to encrypt the wallet. The wallet stays unlocked for 15 minutes
by default.
To back up the wallet, click **Back Up**, enter your passphrase, and store the
displayed private key in a secure location.
Fund the wallet by sending USDC on Polygon to the displayed address. The balance
refreshes automatically, or you can click **Refresh** to update it manually.
Screenshot placeholder: wallet funding screen.
When the extension detects a 402 Payment Required response, it shows an approval
popup. Approve the payment and optionally set a "no ask" threshold for that site
so future payments below that amount are approved automatically.
Policies are configured per site and per currency.
Screenshot placeholder: payment approval screen.
View all site policies and payment history in the extension popup.
Screenshot placeholder: policies and history screen.
## Planned Features
* In-extension on-ramps to buy USDC without leaving the popup
* Pre-emptive site policy configuration before encountering a paywall
* [x402 V2](https://github.com/coinbase/x402/pull/446) support for fiat payments
To suggest improvements, open an issue or PR on the
[source repo](https://github.com/swader/autopay).
# x402 CLI
Source: https://docs.polygon.technology/payment-services/agentic-payments/x402/tools/x402-cli
Reference for the x402 CLI tool: usage and prerequisites for testing x402-gated API endpoints.
# x402 CLI
A command-line tool for testing x402-gated API endpoints.
[GitHub Repository](https://github.com/Swader/x402-cli)
## Prerequisites
Ensure you have funds on the chain you are targeting. Create a `.env` file in
the directory where you run the CLI, containing the `KEY` environment variable
set to a wallet private key with sufficient balance. Alternatively, set `KEY`
as an environment variable in your shell.
## Usage
```bash theme={null}
bunx @swader/x402-cli
```
Example:
```bash theme={null}
bunx @swader/x402-cli https://fs9cqhm1.nx.link/random_joke
```
# Stripe On-ramp Tutorial
Source: https://docs.polygon.technology/payment-services/stablecoins/onramps-stripe
Tutorial: use Stripe's Crypto On-ramp API to let users buy USDC on Polygon inside your app.
This tutorial shows how to use Stripe's Crypto On-ramp API to let users buy USDC
on Polygon directly inside your application. You will create an On-ramp session
on the backend and render Stripe's hosted widget on the frontend.
All examples below are demonstrations only.
In production, never expose `STRIPE_SECRET_KEY` or private wallet keys in client code. Store them in a secure vault or secrets manager.
## Prerequisites
* A US-based business entity (excluding Hawaii)
* [Stripe account](https://dashboard.stripe.com/) with the On-ramp feature enabled (appears automatically after compliance verification)
* Stripe API key (`sk_test_...` or `sk_live_...`)
### Reference docs
* [Stripe API - Crypto On-ramp Sessions](https://docs.stripe.com/crypto/onramp_sessions)
* [Stripe Node SDK](https://github.com/stripe/stripe-node)
## Schema
| name | type | required | example | description |
| ----------------------------- | ------ | -------- | ------------------------ | -------------------------------------- |
| destination\_currency | string | ✅ | `"usdc"` | The target crypto asset |
| destination\_network | string | ✅ | `"polygon"` | Blockchain network |
| wallet\_address\[0]\[type] | string | ✅ | `"self_custody"` | Type of wallet |
| wallet\_address\[0]\[address] | string | ✅ | `"0xYourPolygonAddress"` | Destination wallet address |
| STRIPE\_SECRET\_KEY | string | ✅ | `"sk_test_..."` | Your Stripe API key (server-side only) |
## Create Session cURL
Run this on your backend server only (never in a browser):
```bash theme={null}
curl https://api.stripe.com/v1/crypto/onramp_sessions \
-u sk_test_your_secret_key: \
-d destination_currency=usdc \
-d destination_network=polygon \
-d wallet_addresses[0][type]=self_custody \
-d wallet_addresses[0][address]=0xYOUR_POLYGON_ADDRESS
```
If successful, Stripe returns a JSON response containing a client\_secret:
```json theme={null}
{
"id": "cos_0MYvmj589O8KAxCGp14dTjiw",
"object": "crypto.onramp_session",
"client_secret": "cos_0MYvmj589O8KAxCGp14dTjiw_secret_BsxEqQLiYKANcTAoVnJ2ikH5q002b9xzouk",
"created": 1675794053,
"livemode": false
}
```
## Bun / NodeJS backend
```ts theme={null}
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function createOnrampSession(req, res) {
const session = await stripe.crypto.onramps.sessions.create({
destination_currency: "usdc",
destination_network: "polygon",
wallet_addresses: [
{ type: "self_custody", address: "0xYOUR_POLYGON_ADDRESS" }
],
// optional: customer, email, reference
});
res.json({ client_secret: session.client_secret });
}
```
## Serve to Frontend
Pass only the client\_secret to your client app. Never pass your Stripe secret key.
```html theme={null}
```
Users can now buy USDC on Polygon directly from your site.
The funds are sent to the wallet specified in your `onramp_session`.
## Reference
Configuration parameters, error codes, and guardrails.
### Do / Don't Do Guardrails
| ✅ Do | ❌ Don't |
| -------------------------------------- | ----------------------------------------------- |
| Store API keys in a vault, not in code | Hardcode `STRIPE_SECRET_KEY` in frontend |
| Use Stripe Sandbox first | Test with live key before compliance is cleared |
| Log only non-sensitive session info | Log full `client_secret` values |
### Errors
| code | meaning | fix |
| ----------------------------- | ------------------------- | ---------------------------- |
| 403\_access\_denied | Onramp not enabled | Wait for compliance review |
| 400\_invalid\_wallet\_address | Bad or unsupported wallet | Verify address format |
| 401\_unauthorized | Wrong API key | Use correct/test or live key |
### Quick Checklist
* Backend has `STRIPE_SECRET_KEY` set
* Frontend only receives `client_secret`
* Wallet address is valid on Polygon
* Tested in Sandbox before production
# USDC Gateway Integration
Source: https://docs.polygon.technology/payment-services/stablecoins/usdc-gateway-integration
How to integrate USDC on Polygon using Circle's Gateway (CCTP) for cross-chain transfers.
The following examples are demonstrations of integrations and should not be
used in production. In production, store sensitive information such as API keys
and private keys in a secrets manager or vault.
This guide covers the Gateway approach to USDC on Polygon. Gateway USDC uses
Circle's Cross-Chain Transfer Protocol (CCTP), which lets users hold a single
omnichain balance and move it across chains without separate per-chain contracts.
For payments that stay within Polygon, see
[USDC Native Integration](/payment-services/stablecoins/usdc-native-integration/).
| Approach | Description | When to use |
| ------------ | ---------------------------------------------------------------------- | ---------------------------------------------------------- |
| Native USDC | Standard ERC-20 contract directly on Polygon PoS | When you only need payments within Polygon |
| Gateway USDC | Circle's cross-chain system that moves USDC between blockchains (CCTP) | When your users need to send or receive USDC across chains |
## How Gateway USDC Works
The Circle Gateway is a cross-chain USDC liquidity layer:
* **Deposits**: Users send USDC to a Gateway Wallet contract.
* **Attestations**: Circle verifies the burn event on the source chain.
* **Mints**: A Gateway Minter contract releases the same amount on the destination chain.
This design allows trust-minimized USDC movement between chains.
## Example: Deposit, Attest, and Mint
The following example:
1. Deposits USDC into the Gateway wallet on Polygon
2. Requests an attestation from Circle's API
3. Mints the funds back via the Gateway Minter
```ts theme={null}
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { polygon } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { fetch } from "undici";
const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
const GATEWAY_WALLET = "0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE";
const GATEWAY_MINTER = "0x2222222d7164433c4C09B0b0D809a9b52C04C205";
const erc20 = [
{ type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
{ type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] },
];
const gatewayWalletAbi = [
{ type: "function", name: "deposit", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [] },
];
const gatewayMinterAbi = [
{ type: "function", name: "gatewayMint", stateMutability: "nonpayable", inputs: [{ type: "bytes" }, { type: "bytes" }], outputs: [] },
];
const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`);
const rpc = http(process.env.POLYGON_RPC_URL);
const pub = createPublicClient({ chain: polygon, transport: rpc });
const wallet = createWalletClient({ chain: polygon, transport: rpc, account });
async function gatewayTransfer() {
// 1. Approve + deposit
const decimals = await pub.readContract({ address: USDC, abi: erc20, functionName: "decimals" });
const amount = parseUnits("25", Number(decimals));
await wallet.writeContract({ address: USDC, abi: erc20, functionName: "approve", args: [GATEWAY_WALLET, amount] });
await wallet.writeContract({ address: GATEWAY_WALLET, abi: gatewayWalletAbi, functionName: "deposit", args: [USDC, amount] });
// 2. Request attestation from Circle Gateway API
const res = await fetch("https://gateway-api.circle.com/v1/transfer", {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${process.env.CIRCLE_API_KEY}` },
body: JSON.stringify({
burnIntent: {
spec: {
version: 1,
sourceDomain: 7, // Polygon domain ID
destinationDomain: 7, // same-chain "withdraw", or change for cross-chain
sourceContract: GATEWAY_WALLET,
destinationContract: GATEWAY_MINTER,
sourceToken: USDC,
destinationToken: USDC,
sourceDepositor: account.address,
destinationRecipient: "0xRecipient...",
value: amount.toString(),
},
},
}),
});
const { attestationPayload, signature } = await res.json();
// 3. Submit attestation to GatewayMinter
const tx = await wallet.writeContract({
address: GATEWAY_MINTER,
abi: gatewayMinterAbi,
functionName: "gatewayMint",
args: [attestationPayload as `0x${string}`, signature as `0x${string}`],
});
console.log("gatewayMint tx:", tx);
}
gatewayTransfer();
```
# USDC Native Integration
Source: https://docs.polygon.technology/payment-services/stablecoins/usdc-native-integration
How to read balances and transfer native USDC on Polygon PoS using viem.
The following examples are demonstrations of integrations and should not be
used in production. In production, store sensitive information such as API keys
and private keys in a secrets manager or vault.
This guide covers native USDC on Polygon PoS. Native USDC behaves like any
other ERC-20 token: you can read balances, approve spenders, and transfer
tokens directly onchain.
For cross-chain USDC transfers using Circle's CCTP, see
[USDC Gateway Integration](/payment-services/stablecoins/usdc-gateway-integration/).
| Approach | Description | When to use |
| ------------ | ---------------------------------------------------------------------- | ---------------------------------------------------------- |
| Native USDC | Standard ERC-20 contract directly on Polygon PoS | When you only need payments within Polygon |
| Gateway USDC | Circle's cross-chain system that moves USDC between blockchains (CCTP) | When your users need to send or receive USDC across chains |
## Example: Read Balance and Transfer
The following example uses [viem](https://viem.sh/) to check the USDC balance
and send 1 USDC on Polygon.
```ts theme={null}
// pnpm add viem
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { polygon } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
// Native USDC contract on Polygon PoS (not bridged USDC.e)
const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
const erc20 = [
{ type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
{ type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] },
{ type: "function", name: "transfer", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] },
];
const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`);
const rpc = http(process.env.POLYGON_RPC_URL);
const pub = createPublicClient({ chain: polygon, transport: rpc });
const wallet = createWalletClient({ chain: polygon, transport: rpc, account });
async function main() {
const me = account.address;
const decimals = await pub.readContract({ address: USDC, abi: erc20, functionName: "decimals" });
const bal = await pub.readContract({ address: USDC, abi: erc20, functionName: "balanceOf", args: [me] });
console.log("Balance:", Number(bal) / 10 ** Number(decimals), "USDC");
// Send 1 USDC
const amount = parseUnits("1", Number(decimals));
const hash = await wallet.writeContract({ address: USDC, abi: erc20, functionName: "transfer", args: ["0xRecipient...", amount] });
console.log("tx:", hash);
}
main();
```
# Introduction
Source: https://docs.polygon.technology/pos/architecture/bor/introduction
How Bor, Polygon PoS's block production layer, works: proposer and producer selection, spans, sprints, fork resolution, and state synchronization with Ethereum.
Bor is the block production layer of Polygon PoS. It is based on the Clique consensus protocol, described in [EIP-225](https://eips.ethereum.org/EIPS/eip-225). Block producers take turns generating blocks within defined time windows, and sign their blocks using `secp256k1`.
## Proposers and producers selection
Block producers for Bor are a committee selected from the validator pool based on stake. Selection happens at regular intervals, called spans, and is governed by validator governance parameters.
A validator's probability of being selected as a block producer is proportional to their stake.
Selection process:
1. Validators are assigned slots proportionally to their stake.
2. Historical Ethereum block data is used as a seed to shuffle the validator array.
3. Based on the producer count (maintained by validator governance), validators are taken from the top of the shuffled array.
4. CometBFT's proposer selection algorithm is applied to choose a producer for every sprint within a Bor span.
## Spans
A span is a defined set of blocks during which a specific subset of validators is selected from the broader validator pool. Within a span, each validator is assigned voting power. The probability of a validator being chosen as a block producer is proportional to their voting power.
## Sprints
Within a span, a sprint is a smaller subset of blocks. For each sprint, a single block producer is selected to generate blocks. The sprint size is a fraction of the overall span size. Bor also designates backup producers, ready to step in if the primary producer is unable to fulfill its role.
## Block authorization
Block producers sign the hash of the block header (excluding the signature itself) using `secp256k1`. The signature is appended to the `extraData` field of the block header.
Each block is assigned a difficulty level:
* Blocks signed in-turn by the designated producer receive `DIFF_INTURN` (higher difficulty).
* Out-of-turn blocks from backup producers receive `DIFF_NOTURN` (lower difficulty).
### Handling out-of-turn signing
When the designated producer fails to generate a block (due to technical issues, intentional withholding, or network disruption), backup producers step in. Activation is based on a sequential order of validators and a predefined delay called "wiggle time."
Wiggle time is the delay a backup producer waits before generating a block. It is calculated based on the last block's production time, a variable `Period` parameter, and the backup producer's position in the validator sequence relative to the designated producer.
### Fork resolution
When backup producers generate blocks, forks can occur. Bor resolves forks by selecting the chain with the highest cumulative difficulty, favoring in-turn block production. A longer sequence of in-turn blocks produces higher cumulative difficulty and thus represents the canonical chain.
## View change and span commitment
At the end of each span, Bor performs a view change: it makes an HTTP call to the Heimdall node to retrieve new span data, then calls `commitSpan` on the `BorValidatorSet` genesis contract. Block headers in Bor include producer bytes to aid fast-syncing.
## State synchronization with Ethereum
Bor synchronizes specific events from Ethereum through a state sync mechanism:
1. Contracts on Ethereum emit the `StateSynced` event via `StateSender.sol`.
2. Heimdall monitors these events and proposes state changes through `StateReceiver.sol`.
3. At the start of every sprint, Bor commits these state changes, keeping Bor state consistent with Ethereum.
This mechanism ensures that deposits from Ethereum are reflected in Bor's state. For the full state sync flow, see [State sync](/pos/architecture/bor/state-sync/).
# Network configuration
Source: https://docs.polygon.technology/pos/architecture/bor/network-config
Default ports used by Bor and Heimdall nodes for peer connections, RPC, WebSocket, monitoring, and discovery.
The tables below list the default ports used by Bor and Heimdall nodes. These ports govern peer connections, RPC access, monitoring endpoints, and peer discovery.
## Bor node
| Name | Port | Tags | Description |
| ---------------------- | ----- | ------------------------- | --------------------------------------------------------------------------------------------------- |
| Network Listening Port | 30303 | Public | Port used by Bor for peer connections and synchronization. |
| RPC Server | 8545 | Can be Public, Internal | RPC port for sending transactions and fetching data. Heimdall uses this port to obtain Bor headers. |
| WebSocket Server | 8546 | Can be Public, Internal | WebSocket port for real-time updates. |
| GraphQL Server | 8547 | Internal | GraphQL port for querying data. |
| Prometheus Server | 9091 | Can be Public, Monitoring | Prometheus APIs for Grafana data source. Can be mapped to ports 80/443 via an Nginx reverse proxy. |
| Grafana Server | 3001 | Can be Public, Monitoring | Grafana web server. Can be mapped to ports 80/443 via an Nginx reverse proxy. |
| Pprof Server | 7071 | Internal, Monitoring | Pprof server for collecting Bor metrics. |
| UDP Discovery | 30301 | Can be Public, Internal | Default port for Bootnode peer discovery. |
## Heimdall node
| Name | Port | Tags | Description |
| ---------------------- | ----- | ------------------------- | --------------------------------------------------------------------------------------------------- |
| Network Listening Port | 30303 | Public | Port used by Heimdall for peer connections and synchronization. |
| RPC Server | 8545 | Can be Public, Internal | RPC port for sending transactions and fetching data. Heimdall uses this port to obtain Bor headers. |
| WebSocket Server | 8546 | Can be Public, Internal | WebSocket port for real-time updates. |
| GraphQL Server | 8547 | Internal | GraphQL port for querying data. |
| Prometheus Server | 9091 | Can be Public, Monitoring | Prometheus APIs for Grafana data source. Can be mapped to ports 80/443 via an Nginx reverse proxy. |
| Grafana Server | 3001 | Can be Public, Monitoring | Grafana web server. Can be mapped to ports 80/443 via an Nginx reverse proxy. |
| Pprof Server | 7071 | Internal, Monitoring | Pprof server for collecting Heimdall metrics. |
| UDP Discovery | 30301 | Can be Public, Internal | Default port for Bootnode peer discovery. |
# State sync
Source: https://docs.polygon.technology/pos/architecture/bor/state-sync
How state sync works in Polygon PoS, allowing Ethereum contract state to be reflected in Bor through the StateSender and StateReceiver contracts.
State sync is the mechanism by which contract state on Ethereum is propagated to the Bor chain. It allows dApps on Polygon PoS to read and react to events that originate on Ethereum.
## How state sync works
State transfer from Ethereum to Bor happens through a system call:
1. A contract on Ethereum calls `syncState` on the `StateSender.sol` contract, emitting a `StateSynced` event.
2. Heimdall validators listen for `StateSynced` events. One validator sends a state-sync transaction to Heimdall.
3. Once the transaction is included in a Heimdall block, it is added to the pending state-sync list.
4. After every sprint on Bor (currently 16 blocks), the Bor node fetches pending state-sync records from Heimdall via API.
5. Bor commits these records at the start of each sprint by calling `onStateReceive` on the target contract. This is a system call from address `2^160-2`.
The receiver contract on Bor must implement `IStateReceiver`. The `onStateReceive` function can only be called by `StateReceiver.sol` at address `0x0000000000000000000000000000000000001001`.
## State sender contract
Source: [StateSender.sol](https://github.com/0xPolygon/pos-contracts/blob/develop/contracts/root/stateSyncer/StateSender.sol)
The `StateSender` contract on Ethereum exposes a `syncState` function:
```jsx theme={null}
contract StateSender {
/**
* Emits `stateSynced` events to start sync process on Ethereum chain
* @param receiver Target contract on Bor chain
* @param data Data to send
*/
function syncState (
address receiver,
bytes calldata data
) external;
}
```
Calling `syncState` emits the following event:
```jsx theme={null}
/**
* Emits `stateSynced` events to start sync process on Ethereum chain
* @param id State id
* @param contractAddress Target contract address on Bor
* @param data Data to send to Bor chain for Target contract address
*/
event StateSynced (
uint256 indexed id,
address indexed contractAddress,
bytes data
);
```
Once the `StateSynced` event is emitted, Heimdall listens for it. After 2/3+ validators agree, the state-sync record is added to Heimdall state. Bor fetches and applies it at the start of the next sprint.
## State receiver interface on Bor
The target contract on Bor must implement the following interface:
```jsx theme={null}
// IStateReceiver represents interface to receive state
interface IStateReceiver {
function onStateReceive(uint256 stateId, bytes calldata data) external;
}
```
Only `StateReceiver.sol` at address `0x0000000000000000000000000000000000001001` is permitted to call `onStateReceive` on target contracts.
## System calls
The state sync commit on Bor uses a system call. Only the system address `2^160-2` can make a system call. Bor calls it internally with the system address as `msg.sender`, which changes contract state and updates the state root for the block, without requiring a user-initiated transaction. This is inspired by [EIP-210](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-210.md).
## State-sync logs and Bor block receipts
Events emitted by system calls are handled differently from normal logs. Bor produces a synthetic transaction and receipt for each state-sync event, so clients can query these events through standard Ethereum JSON-RPC methods. The transaction hash is derived from:
```jsx theme={null}
keccak256("matic-bor-receipt-" + block number + block hash)
```
This synthetic transaction does not affect consensus logic. `eth_getBlockByNumber`, `eth_getTransactionReceipt`, and `eth_getLogs` include state-sync logs from the derived transaction. Note that the block bloom filter does not include state-sync log entries, and the derived transaction is not included in `transactionRoot` or `receiptRoot`.
# Authentication
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/authentication
How the Heimdall auth module handles account types, transaction validation, gas fees, AnteHandlers, and keepers.
## Abstract
This document specifies the auth module of the Polygon's Cosmos SDK fork.
The auth module is responsible for specifying the base transaction and account types
for an application, since the SDK itself is agnostic to these particulars. It contains
the middlewares, where all basic transaction validity checks (signatures, nonces, auxiliary fields)
are performed, and exposes the account keeper, which allows other modules to read, write, and modify accounts.
This module is used in the Cosmos Hub.
## Concepts
**Note:** The auth module is different from the [authz](https://docs.cosmos.network/main/build/modules/authz) module.
The differences are:
* `auth` - authentication of accounts and transactions for Cosmos SDK applications and is responsible for specifying the base transaction and account types.
* `authz` - authorization for accounts to perform actions on behalf of other accounts and enables a granter to grant authorizations to a grantee that allows the grantee to execute messages on behalf of the granter.
### Gas & Fees
Fees serve two purposes for an operator of the network.
Fees limit the growth of the state stored by every full node and allow for
general purpose censorship of transactions of little economic value. Fees
are best suited as an anti-spam mechanism where validators are disinterested in
the use of the network and identities of users.
Fees are determined by the gas limits and gas prices transactions provide, where
`fees = ceil(gasLimit * gasPrices)`. Txs incur gas costs for all state reads/writes,
signature verification, as well as costs proportional to the tx size. Operators
should set minimum gas prices when starting their nodes. They must set the unit
costs of gas in each token denomination they wish to support:
`heimdalld start ... --minimum-gas-prices=0.00001stake;0.05photinos`
When adding transactions to mempool or gossipping transactions, validators check
if the transaction's gas prices, which are determined by the provided fees, meet
any of the validator's minimum gas prices. In other words, a transaction must
provide a fee of at least one denomination that matches a validator's minimum
gas price.
CometBFT does not currently provide fee based mempool prioritization, and fee
based mempool filtering is local to node and not part of consensus. But with
minimum gas prices set, such a mechanism could be implemented by node operators.
Because the market value for tokens will fluctuate, validators are expected to
dynamically adjust their minimum gas prices to a level that would encourage the
use of the network.
In Heimdall, a default fee of 10^15 pol (`DefaultFeeInPol`) is deducted from the tx sender for every tx.
## State
### Accounts
Accounts contain authentication information for a uniquely identified external user of an SDK blockchain,
including public key, address, and account number / sequence number for replay protection. For efficiency,
since account balances must also be fetched to pay fees, account structs also store the balance of a user
as `sdk.Coins`.
Accounts are exposed externally as an interface, and stored internally as
either a base account or vesting account. Module clients wishing to add more
account types may do so.
* `0x01 | Address -> ProtocolBuffer(account)`
#### Account Interface
The account interface exposes methods to read and write standard account information.
Note that all of these methods operate on an account struct conforming to the
interface - in order to write the account to the store, the account keeper will
need to be used.
```go theme={null}
// AccountI is an interface used to store coins at a given address within state.
// It presumes a notion of sequence numbers for replay protection,
// a notion of account numbers for replay protection for previously pruned accounts,
// and a pubkey for authentication purposes.
//
// Many complex conditions can be used in the concrete struct which implements AccountI.
type AccountI interface {
proto.Message
GetAddress() sdk.AccAddress
SetAddress(sdk.AccAddress) error // errors if already set.
GetPubKey() crypto.PubKey // can return nil.
SetPubKey(crypto.PubKey) error
GetAccountNumber() uint64
SetAccountNumber(uint64) error
GetSequence() uint64
SetSequence(uint64) error
// Ensure that account implements stringer
String() string
Validate()
}
```
##### Base Account
A base account is the simplest and most common account type, which just stores all requisite
fields directly in a struct.
```protobuf theme={null}
// BaseAccount defines a base account type. It contains all the necessary fields
// for basic account functionality. Any custom account type should extend this
// type for additional functionality (e.g. vesting).
message BaseAccount {
string address = 1;
google.protobuf.Any pub_key = 2;
uint64 account_number = 3;
uint64 sequence = 4;
}
```
### Vesting Account
See [Vesting](https://docs.cosmos.network/main/modules/auth/vesting/).\
Heimdall does not currently support vesting accounts, so they will be treated as base accounts.
## AnteHandlers
The `x/auth` module presently has no transaction handlers of its own, but does expose the special `AnteHandler`, used for performing basic validity checks on a transaction, such that it could be thrown out of the mempool.
The `AnteHandler` can be seen as a set of decorators that check transactions within the current context, per [ADR 010](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-010-modular-antehandler.md).
Note that the `AnteHandler` is called on both `CheckTx` and `DeliverTx`, as CometBFT proposers presently have the ability to include in their proposed block transactions which fail `CheckTx`.
### Decorators
The auth module provides `AnteDecorator`s that are recursively chained together into a single `AnteHandler` in the following order:
* `SetUpContextDecorator`: Sets the `GasMeter` in the `Context` and wraps the next `AnteHandler` with a defer clause to recover from any downstream `OutOfGas` panics in the `AnteHandler` chain to return an error with information on gas provided and gas used.
* `RejectExtensionOptionsDecorator`: Rejects all extension options which can optionally be included in protobuf transactions.
* `MempoolFeeDecorator`: Checks if the `tx` fee is above local mempool `minFee` parameter during `CheckTx`.
* `ValidateBasicDecorator`: Calls `tx.ValidateBasic` and returns any non-nil error.
* `TxTimeoutHeightDecorator`: Check for a `tx` height timeout.
* `ValidateMemoDecorator`: Validates `tx` memo with application parameters and returns any non-nil error.
* `ConsumeGasTxSizeDecorator`: Consumes gas proportional to the `tx` size based on application parameters.
* `DeductFeeDecorator`: Deducts the `FeeAmount` from first signer of the `tx`. If the `x/feegrant` module is enabled and a fee granter is set, it deducts fees from the fee granter account.
* `SetPubKeyDecorator`: Sets the pubkey from a `tx`'s signers that does not already have its corresponding pubkey saved in the state machine and in the current context.
* `ValidateSigCountDecorator`: Validates the number of signatures in `tx` based on app-parameters.
* `SigGasConsumeDecorator`: Consumes parameter-defined amount of gas for each signature. This requires pubkeys to be set in context for all signers as part of `SetPubKeyDecorator`.
* `SigVerificationDecorator`: Verifies all signatures are valid. This requires pubkeys to be set in context for all signers as part of `SetPubKeyDecorator`.
* `IncrementSequenceDecorator`: Increments the account sequence for each signer to prevent replay attacks.
Some of these decorators may be disabled due to Heimdall's specific requirements.
## Keepers
The auth module only exposes one keeper, the account keeper, which can be used to read and write accounts.
### Account Keeper
Presently only one fully-permissioned account keeper is exposed, which has the ability to both read and write
all fields of all accounts, and to iterate over all stored accounts.
```go theme={null}
// AccountKeeperI is the interface contract that x/auth's keeper implements.
type AccountKeeperI interface {
// Return a new account with the next account number and the specified address. Does not save the new account to the store.
NewAccountWithAddress(sdk.Context, sdk.AccAddress) types.AccountI
// Return a new account with the next account number. Does not save the new account to the store.
NewAccount(sdk.Context, types.AccountI) types.AccountI
// Check if an account exists in the store.
HasAccount(sdk.Context, sdk.AccAddress) bool
// Retrieve an account from the store.
GetAccount(sdk.Context, sdk.AccAddress) types.AccountI
// Set an account in the store.
SetAccount(sdk.Context, types.AccountI)
// Remove an account from the store.
RemoveAccount(sdk.Context, types.AccountI)
// Iterate over all accounts, calling the provided function. Stop iteration when it returns true.
IterateAccounts(sdk.Context, func(types.AccountI) bool)
// Fetch the public key of an account at a specified address
GetPubKey(sdk.Context, sdk.AccAddress) (crypto.PubKey, error)
// Fetch the sequence of an account at a specified address.
GetSequence(sdk.Context, sdk.AccAddress) (uint64, error)
// Fetch the next account number, and increment the internal counter.
NextAccountNumber(sdk.Context) uint64
}
```
## Parameters
The auth module contains the following parameters:
| Key | Type | Example |
| ---------------------- | ------ | ------- |
| MaxMemoCharacters | uint64 | 256 |
| TxSigLimit | uint64 | 7 |
| TxSizeCostPerByte | uint64 | 10 |
| SigVerifyCostED25519 | uint64 | 590 |
| SigVerifyCostSecp256k1 | uint64 | 1000 |
| MaxTxGas | uint64 | 1000 |
| TxFees | string | "1000" |
## Client
### CLI
A user can query and interact with the `auth` module using the CLI.
### Query
The `query` commands allow users to query `auth` state.
```bash theme={null}
heimdalld query auth --help
```
#### account
The `account` command allow users to query for an account by it's address.
```bash theme={null}
heimdalld query auth account [address] [flags]
```
Example:
```bash theme={null}
heimdalld query auth account cosmos1...
```
Example Output:
```bash theme={null}
'@type': /cosmos.auth.v1beta1.BaseAccount
account_number: "0"
address: 0x...
pub_key:
'@type': /cosmos.crypto.secp256k1.PubKey
key: ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD
sequence: "1"
```
#### accounts
The `accounts` command allow users to query all the available accounts.
```bash theme={null}
heimdalld query auth accounts [flags]
```
Example:
```bash theme={null}
heimdalld query auth accounts
```
Example Output:
```bash theme={null}
accounts:
- '@type': /cosmos.auth.v1beta1.BaseAccount
account_number: "0"
address: 0x...
pub_key:
'@type': /cosmos.crypto.secp256k1.PubKey
key: ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD
sequence: "1"
- '@type': /cosmos.auth.v1beta1.ModuleAccount
base_account:
account_number: "8"
address: 0x...
pub_key: null
sequence: "0"
name: transfer
permissions:
- minter
- burner
- '@type': /cosmos.auth.v1beta1.ModuleAccount
base_account:
account_number: "4"
address: 0x...
pub_key: null
sequence: "0"
name: bonded_tokens_pool
permissions:
- burner
- staking
- '@type': /cosmos.auth.v1beta1.ModuleAccount
base_account:
account_number: "5"
address: 0x...
pub_key: null
sequence: "0"
name: not_bonded_tokens_pool
permissions:
- burner
- staking
- '@type': /cosmos.auth.v1beta1.ModuleAccount
base_account:
account_number: "6"
address: 0x...
pub_key: null
sequence: "0"
name: gov
permissions:
- burner
- '@type': /cosmos.auth.v1beta1.ModuleAccount
base_account:
account_number: "3"
address: 0x...
pub_key: null
sequence: "0"
name: distribution
permissions: []
- '@type': /cosmos.auth.v1beta1.BaseAccount
account_number: "1"
address: 0x...
pub_key: null
sequence: "0"
- '@type': /cosmos.auth.v1beta1.ModuleAccount
base_account:
account_number: "7"
address: 0x...
pub_key: null
sequence: "0"
name: mint
permissions:
- minter
- '@type': /cosmos.auth.v1beta1.ModuleAccount
base_account:
account_number: "2"
address: 0x...
pub_key: null
sequence: "0"
name: fee_collector
permissions: []
pagination:
next_key: null
total: "0"
```
#### params
The `params` command allow users to query the current auth parameters.
```bash theme={null}
heimdalld query auth params [flags]
```
Example:
```bash theme={null}
heimdalld query auth params
```
Example Output:
```bash theme={null}
max_memo_characters: "256"
sig_verify_cost_ed25519: "590"
sig_verify_cost_secp256k1: "1000"
tx_sig_limit: "7"
tx_size_cost_per_byte: "10"
max_tx_gas: "1000";
tx_fees: "1000";
```
### Transactions
The `auth` module supports transactions commands to help you with signing and more. Compared to other modules you can access directly the `auth` module transactions commands using the only `tx` command.
Use directly the `--help` flag to get more information about the `tx` command.
```bash theme={null}
heimdalld tx --help
```
#### `sign`
The `sign` command allows users to sign transactions that was generated offline.
```bash theme={null}
heimdalld tx sign tx.json --from $ALICE > tx.signed.json
```
The result is a signed transaction that can be broadcasted to the network thanks to the broadcast command.
More information about the `sign` command can be found running `heimdalld tx sign --help`.
#### `sign-batch`
The `sign-batch` command allows users to sign multiples offline generated transactions.
The transactions can be in one file, with one tx per line, or in multiple files.
```bash theme={null}
heimdalld tx sign txs.json --from $ALICE > tx.signed.json
```
or
```bash theme={null}
heimdalld tx sign tx1.json tx2.json tx3.json --from $ALICE > tx.signed.json
```
The result is multiples signed transactions. For combining the signed transactions into one transactions, use the `--append` flag.
More information about the `sign-batch` command can be found running `heimdalld tx sign-batch --help`.
#### `multi-sign`
The `multi-sign` command allows users to sign transactions that was generated offline by a multisig account.\
The multi signature functionality is not supported by Heimdall.
```bash theme={null}
heimdalld tx multisign transaction.json k1k2k3 k1sig.json k2sig.json k3sig.json
```
Where `k1k2k3` is the multisig account address, `k1sig.json` is the signature of the first signer, `k2sig.json` is the signature of the second signer, and `k3sig.json` is the signature of the third signer.
##### Nested multisig transactions
To allow transactions to be signed by nested multisigs, meaning that a participant of a multisig account can be another multisig account, the `--skip-signature-verification` flag must be used.
```bash theme={null}
# First aggregate signatures of the multisig participant
heimdalld tx multi-sign transaction.json ms1 ms1p1sig.json ms1p2sig.json --signature-only --skip-signature-verification > ms1sig.json
# Then use the aggregated signatures and the other signatures to sign the final transaction
heimdalld tx multi-sign transaction.json k1ms1 k1sig.json ms1sig.json --skip-signature-verification
```
Where `ms1` is the nested multisig account address, `ms1p1sig.json` is the signature of the first participant of the nested multisig account, `ms1p2sig.json` is the signature of the second participant of the nested multisig account, and `ms1sig.json` is the aggregated signature of the nested multisig account.
`k1ms1` is a multisig account comprised of an individual signer and another nested multisig account (`ms1`). `k1sig.json` is the signature of the first signer of the individual member.
More information about the `multi-sign` command can be found running `heimdalld tx multi-sign --help`.
#### `multisign-batch`
The `multisign-batch` works the same way as `sign-batch`, but for multisig accounts.
With the difference that the `multisign-batch` command requires all transactions to be in one file, and the `--append` flag does not exist.\
The multi signature functionality is not supported by Heimdall.
More information about the `multisign-batch` command can be found running `heimdalld tx multisign-batch --help`.
#### `validate-signatures`
The `validate-signatures` command allows users to validate the signatures of a signed transaction.
```bash theme={null}
$ heimdalld tx validate-signatures tx.signed.json
Signers:
0: 0x...
Signatures:
0: 0x... [OK]
```
More information about the `validate-signatures` command can be found running `heimdalld tx validate-signatures --help`.
#### `broadcast`
The `broadcast` command allows users to broadcast a signed transaction to the network.
```bash theme={null}
heimdalld tx broadcast tx.signed.json
```
More information about the `broadcast` command can be found running `heimdalld tx broadcast --help`.
### gRPC
A user can query the `auth` module using gRPC endpoints.
#### Account
The `account` endpoint allow users to query for an account by it's address.
```bash theme={null}
cosmos.auth.v1beta1.Query/Account
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"address":"cosmos1.."}' \
localhost:9090 \
cosmos.auth.v1beta1.Query/Account
```
Example Output:
```bash theme={null}
{
"account":{
"@type":"/cosmos.auth.v1beta1.BaseAccount",
"address":"0x...",
"pubKey":{
"@type":"/cosmos.crypto.secp256k1.PubKey",
"key":"ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD"
},
"sequence":"1"
}
}
```
#### Accounts
The `accounts` endpoint allow users to query all the available accounts.
```bash theme={null}
cosmos.auth.v1beta1.Query/Accounts
```
Example:
```bash theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.auth.v1beta1.Query/Accounts
```
Example Output:
```bash theme={null}
{
"accounts":[
{
"@type":"/cosmos.auth.v1beta1.BaseAccount",
"address":"0x...",
"pubKey":{
"@type":"/cosmos.crypto.secp256k1.PubKey",
"key":"ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD"
},
"sequence":"1"
},
{
"@type":"/cosmos.auth.v1beta1.ModuleAccount",
"baseAccount":{
"address":"0x...",
"accountNumber":"8"
},
"name":"transfer",
"permissions":[
"minter",
"burner"
]
},
{
"@type":"/cosmos.auth.v1beta1.ModuleAccount",
"baseAccount":{
"address":"0x...",
"accountNumber":"4"
},
"name":"bonded_tokens_pool",
"permissions":[
"burner",
"staking"
]
},
{
"@type":"/cosmos.auth.v1beta1.ModuleAccount",
"baseAccount":{
"address":"0x...",
"accountNumber":"5"
},
"name":"not_bonded_tokens_pool",
"permissions":[
"burner",
"staking"
]
},
{
"@type":"/cosmos.auth.v1beta1.ModuleAccount",
"baseAccount":{
"address":"0x...",
"accountNumber":"6"
},
"name":"gov",
"permissions":[
"burner"
]
},
{
"@type":"/cosmos.auth.v1beta1.ModuleAccount",
"baseAccount":{
"address":"0x...",
"accountNumber":"3"
},
"name":"distribution"
},
{
"@type":"/cosmos.auth.v1beta1.BaseAccount",
"accountNumber":"1",
"address":"0x..."
},
{
"@type":"/cosmos.auth.v1beta1.ModuleAccount",
"baseAccount":{
"address":"0x...",
"accountNumber":"7"
},
"name":"mint",
"permissions":[
"minter"
]
},
{
"@type":"/cosmos.auth.v1beta1.ModuleAccount",
"baseAccount":{
"address":"0x...",
"accountNumber":"2"
},
"name":"fee_collector"
}
],
"pagination":{
"total":"9"
}
}
```
#### Params
The `params` endpoint allow users to query the current auth parameters.
```bash theme={null}
cosmos.auth.v1beta1.Query/Params
```
Example:
```bash theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.auth.v1beta1.Query/Params
```
Example Output:
```bash theme={null}
{
"params": {
"maxMemoCharacters": "256",
"txSigLimit": "7",
"txSizeCostPerByte": "10",
"sigVerifyCostEd25519": "590",
"sigVerifyCostSecp256k1": "1000"
"maxTxGas": "1000",
"txFees": "1000"
}
}
```
### REST
A user can query the `auth` module using REST endpoints.
#### Account
The `account` endpoint allow users to query for an account by it's address.
```bash theme={null}
/cosmos/auth/v1beta1/account?address={address}
```
#### Accounts
The `accounts` endpoint allow users to query all the available accounts.
```bash theme={null}
/cosmos/auth/v1beta1/accounts
```
#### Params
The `params` endpoint allow users to query the current auth parameters.
```bash theme={null}
/cosmos/auth/v1beta1/params
```
### Heimdall Notes
Note that in the example provided here, `0x...` is used as a placeholder for an actual ethereum compatible address.
# Balance transfers
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/balance-transfers
How the Heimdall bank module handles multi-asset coin transfers, module accounts, supply tracking, send restrictions, and CLI/gRPC/REST query commands.
Heimdall's `bank` module handles balance transfers between accounts. This module corresponds to the `bank` module from the Cosmos SDK.
## Abstract
The bank module is responsible for handling multi-asset coin transfers between
accounts and tracking special-case pseudo-transfers which must work differently
with particular kinds of accounts (notably delegating/undelegating for vesting
accounts). It exposes several interfaces with varying capabilities for secure
interaction with other modules which must alter user balances.
In addition, the bank module tracks and provides query support for the total
supply of all assets used in the application.
This module is used in the Cosmos Hub and [Heimdall](https://github.com/0xPolygon/heimdall-v2).
**NOTE**: For heimdall, minimal changes have been done in the fork listed [here](#conceptschanges-specific-to-heimdall)
## Supply
The `supply` functionality:
* passively tracks the total supply of coins within a chain,
* provides a pattern for modules to hold/interact with `Coins`, and
* introduces the invariant check to verify a chain's total supply.
### Total Supply
The total `Supply` of the network is equal to the sum of all coins from the
account. The total supply is updated every time a `Coin` is minted (eg: as part
of the inflation mechanism) or burned (eg: due to slashing or if a governance
proposal is vetoed).
## Module Accounts
The supply functionality introduces a new type of `auth.Account` which can be used by
modules to allocate tokens and in special cases mint or burn tokens. At a base
level these module accounts are capable of sending/receiving tokens to and from
`auth.Account` and other module accounts. This design replaces previous
alternative designs where, to hold tokens, modules would burn the incoming
tokens from the sender account, and then track those tokens internally. Later,
in order to send tokens, the module would need to effectively mint tokens
within a destination account. The new design removes duplicate logic between
modules to perform this accounting.
The `ModuleAccount` interface is defined as follows:
```go theme={null}
type ModuleAccount interface {
auth.Account // same methods as the Account interface
GetName() string // name of the module; used to obtain the address
GetPermissions() []string // permissions of module account
HasPermission(string) bool
}
```
> **WARNING!**
> Any module or message handler that allows either direct or indirect sending of funds must explicitly guarantee those funds cannot be sent to module accounts (unless allowed).
The supply `Keeper` also introduces new wrapper functions for the auth `Keeper`
and the bank `Keeper` that are related to `ModuleAccount` in order to be able
to:
* Get and set `ModuleAccount` by providing the `Name`.
* Send coins from and to other `ModuleAccount` or standard `Account`
(`BaseAccount` or `VestingAccount`) by passing only the `Name`.
* `Mint` or `Burn` coins for a `ModuleAccount` (restricted to its permissions).
### Permissions
Each `ModuleAccount` has a different set of permissions that provide different
object capabilities to perform certain actions. Permissions need to be
registered upon the creation of the supply `Keeper` so that every time a
`ModuleAccount` calls the allowed functions, the `Keeper` can lookup the
permissions to that specific account and perform or not perform the action.
The available permissions are:
* `Minter`: allows for a module to mint a specific amount of coins.
* `Burner`: allows for a module to burn a specific amount of coins.
* `Staking`: allows for a module to delegate and undelegate a specific amount of coins.
## State
The `x/bank` module keeps state of the following primary objects:
1. Account balances
2. Denomination metadata
3. The total supply of all balances
4. Information on which denominations are allowed to be sent.
In addition, the `x/bank` module keeps the following indexes to manage the
aforementioned state:
* Supply Index: `0x0 | byte(denom) -> byte(amount)`
* Denom Metadata Index: `0x1 | byte(denom) -> ProtocolBuffer(Metadata)`
* Balances Index: `0x2 | byte(address length) | []byte(address) | []byte(balance.Denom) -> ProtocolBuffer(balance)`
* Reverse Denomination to Address Index: `0x03 | byte(denom) | 0x00 | []byte(address) -> 0`
## Params
The bank module stores it's params in state with the prefix of `0x05`,
it can be updated with governance or the address with authority.
* Params: `0x05 | ProtocolBuffer(Params)`
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/bank.proto#L12-L23](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/bank.proto#L12-L23)
## Keepers
The bank module provides these exported keeper interfaces that can be
passed to other modules that read or update account balances. Modules
should use the least-permissive interface that provides the functionality they
require.
Best practices dictate careful review of `bank` module code to ensure that
permissions are limited in the way that you expect.
### Denied Addresses
The `x/bank` module accepts a map of addresses that are considered blocklisted
from directly and explicitly receiving funds through means such as `MsgSend` and
`MsgMultiSend` and direct API calls like `SendCoinsFromModuleToAccount`.
Typically, these addresses are module accounts. If these addresses receive funds
outside the expected rules of the state machine, invariants are likely to be
broken and could result in a halted network.
By providing the `x/bank` module with a blocklisted set of addresses, an error occurs for the operation if a user or client attempts to directly or indirectly send funds to a blocklisted account, for example, by using [IBC](https://ibc.cosmos.network).
### Common Types
#### Input
An input of a multiparty transfer
```protobuf theme={null}
// Input models transaction input.
message Input {
string address = 1;
repeated cosmos.base.v1beta1.Coin coins = 2;
}
```
#### Output
An output of a multiparty transfer.
```protobuf theme={null}
// Output models transaction outputs.
message Output {
string address = 1;
repeated cosmos.base.v1beta1.Coin coins = 2;
}
```
### BaseKeeper
The base keeper provides full-permission access: the ability to arbitrary modify any account's balance and mint or burn coins.
Restricted permission to mint per module could be achieved by using baseKeeper with `WithMintCoinsRestriction` to give specific restrictions to mint (e.g. only minting certain denom).
```go theme={null}
// Keeper defines a module interface that facilitates the transfer of coins
// between accounts.
type Keeper interface {
SendKeeper
WithMintCoinsRestriction(MintingRestrictionFn) BaseKeeper
InitGenesis(context.Context, *types.GenesisState)
ExportGenesis(context.Context) *types.GenesisState
GetSupply(ctx context.Context, denom string) sdk.Coin
HasSupply(ctx context.Context, denom string) bool
GetPaginatedTotalSupply(ctx context.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error)
IterateTotalSupply(ctx context.Context, cb func(sdk.Coin) bool)
GetDenomMetaData(ctx context.Context, denom string) (types.Metadata, bool)
HasDenomMetaData(ctx context.Context, denom string) bool
SetDenomMetaData(ctx context.Context, denomMetaData types.Metadata)
IterateAllDenomMetaData(ctx context.Context, cb func(types.Metadata) bool)
SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error
SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
DelegateCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
UndelegateCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error
BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error
DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error
UndelegateCoins(ctx context.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error
// GetAuthority gets the address capable of executing governance proposal messages. Usually the gov module account.
GetAuthority() string
types.QueryServer
}
```
### SendKeeper
The send keeper provides access to account balances and the ability to transfer coins between
accounts. The send keeper does not alter the total supply (mint or burn coins).
```go theme={null}
// SendKeeper defines a module interface that facilitates the transfer of coins
// between accounts without the possibility of creating coins.
type SendKeeper interface {
ViewKeeper
AppendSendRestriction(restriction SendRestrictionFn)
PrependSendRestriction(restriction SendRestrictionFn)
ClearSendRestriction()
InputOutputCoins(ctx context.Context, input types.Input, outputs []types.Output) error
SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error
GetParams(ctx context.Context) types.Params
SetParams(ctx context.Context, params types.Params) error
IsSendEnabledDenom(ctx context.Context, denom string) bool
SetSendEnabled(ctx context.Context, denom string, value bool)
SetAllSendEnabled(ctx context.Context, sendEnableds []*types.SendEnabled)
DeleteSendEnabled(ctx context.Context, denom string)
IterateSendEnabledEntries(ctx context.Context, cb func(denom string, sendEnabled bool) (stop bool))
GetAllSendEnabledEntries(ctx context.Context) []types.SendEnabled
IsSendEnabledCoin(ctx context.Context, coin sdk.Coin) bool
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
BlockedAddr(addr sdk.AccAddress) bool
}
```
#### Send Restrictions
The `SendKeeper` applies a `SendRestrictionFn` before each transfer of funds.
```go theme={null}
// A SendRestrictionFn can restrict sends and/or provide a new receiver address.
type SendRestrictionFn func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (newToAddr sdk.AccAddress, err error)
```
After the `SendKeeper` (or `BaseKeeper`) has been created, send restrictions can be added to it using the `AppendSendRestriction` or `PrependSendRestriction` functions.
Both functions compose the provided restriction with any previously provided restrictions.
`AppendSendRestriction` adds the provided restriction to be run after any previously provided send restrictions.
`PrependSendRestriction` adds the restriction to be run before any previously provided send restrictions.
The composition will short-circuit when an error is encountered. I.e. if the first one returns an error, the second is not run.
During `SendCoins`, the send restriction is applied after coins are removed from the from address, but before adding them to the to address.
During `InputOutputCoins`, the send restriction is applied after the input coins are removed and once for each output before the funds are added.
A send restriction function should make use of a custom value in the context to allow bypassing that specific restriction.
Send Restrictions are not placed on `ModuleToAccount` or `ModuleToModule` transfers. This is done due to modules needing to move funds to user accounts and other module accounts. This is a design decision to allow for more flexibility in the state machine. The state machine should be able to move funds between module accounts and user accounts without restrictions.
Secondly this limitation would limit the usage of the state machine even for itself. users would not be able to receive rewards, not be able to move funds between module accounts. In the case that a user sends funds from a user account to the community pool and then a governance proposal is used to get those tokens into the users account this would fall under the discretion of the app chain developer to what they would like to do here. We can not make strong assumptions here.
Thirdly, this issue could lead into a chain halt if a token is disabled and the token is moved in the begin/endblock. This is the last reason we see the current change and more damaging then beneficial for users.
For example, in your module's keeper package, you'd define the send restriction function:
```go theme={null}
var _ banktypes.SendRestrictionFn = Keeper{}.SendRestrictionFn
func (k Keeper) SendRestrictionFn(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) {
// Bypass if the context says to.
if mymodule.HasBypass(ctx) {
return toAddr, nil
}
// Your custom send restriction logic goes here.
return nil, errors.New("not implemented")
}
```
The bank keeper should be provided to your keeper's constructor so the send restriction can be added to it:
```go theme={null}
func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, bankKeeper mymodule.BankKeeper) Keeper {
rv := Keeper{/*...*/}
bankKeeper.AppendSendRestriction(rv.SendRestrictionFn)
return rv
}
```
Then, in the `mymodule` package, define the context helpers:
```go theme={null}
const bypassKey = "bypass-mymodule-restriction"
// WithBypass returns a new context that will cause the mymodule bank send restriction to be skipped.
func WithBypass(ctx context.Context) context.Context {
return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, true)
}
// WithoutBypass returns a new context that will cause the mymodule bank send restriction to not be skipped.
func WithoutBypass(ctx context.Context) context.Context {
return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, false)
}
// HasBypass checks the context to see if the mymodule bank send restriction should be skipped.
func HasBypass(ctx context.Context) bool {
bypassValue := ctx.Value(bypassKey)
if bypassValue == nil {
return false
}
bypass, isBool := bypassValue.(bool)
return isBool && bypass
}
```
Now, anywhere where you want to use `SendCoins` or `InputOutputCoins`, but you don't want your send restriction applied:
```go theme={null}
func (k Keeper) DoThing(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error {
return k.bankKeeper.SendCoins(mymodule.WithBypass(ctx), fromAddr, toAddr, amt)
}
```
### ViewKeeper
The view keeper provides read-only access to account balances. The view keeper does not have balance alteration functionality. All balance lookups are `O(1)`.
```go theme={null}
// ViewKeeper defines a module interface that facilitates read only access to
// account balances.
type ViewKeeper interface {
ValidateBalance(ctx context.Context, addr sdk.AccAddress) error
HasBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin) bool
GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins
GetAccountsBalances(ctx context.Context) []types.Balance
GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
LockedCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins
SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins
SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
IterateAccountBalances(ctx context.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool))
IterateAllBalances(ctx context.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool))
}
```
## Messages
### MsgSend
Send coins from one address to another.
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L38-L53](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L38-L53)
The message will fail under the following conditions:
* The coins do not have sending enabled
* The `to` address is restricted
### MsgMultiSend
Send coins from one sender and to a series of different address. If any of the receiving addresses do not correspond to an existing account, a new account is created.
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L58-L69](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L58-L69)
The message will fail under the following conditions:
* Any of the coins do not have sending enabled
* Any of the `to` addresses are restricted
* Any of the coins are locked
* The inputs and outputs do not correctly correspond to one another
### MsgUpdateParams
The `bank` module params can be updated through `MsgUpdateParams`, which can be done using governance proposal. The signer will always be the `gov` module account address.
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L74-L88](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L74-L88)
The message handling can fail if:
* signer is not the gov module account address.
### MsgSetSendEnabled
**NOTE**: Heimdall only supports denom (pol) and hence this msg type is not supported.
Used with the x/gov module to set create/edit SendEnabled entries.
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L96-L117](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/bank/v1beta1/tx.proto#L96-L117)
The message will fail under the following conditions:
* The authority is not a bech32 address.
* The authority is not x/gov module's address.
* There are multiple SendEnabled entries with the same Denom.
* One or more SendEnabled entries has an invalid Denom.
## Events
The bank module emits the following events:
### Message Events
#### MsgSend
| Type | Attribute Key | Attribute Value |
| -------- | ------------- | -------------------- |
| transfer | recipient | `{recipientAddress}` |
| transfer | amount | `{amount}` |
| message | module | bank |
| message | action | send |
| message | sender | `{senderAddress}` |
#### MsgMultiSend
| Type | Attribute Key | Attribute Value |
| -------- | ------------- | -------------------- |
| transfer | recipient | `{recipientAddress}` |
| transfer | amount | `{amount}` |
| message | module | bank |
| message | action | multisend |
| message | sender | `{senderAddress}` |
### Keeper Events
In addition to message events, the bank keeper will produce events when the following methods are called (or any method which ends up calling them)
#### MintCoins
```json theme={null}
{
"type": "coinbase",
"attributes": [
{
"key": "minter",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being minted}}",
"index": true
}
]
}
```
```json theme={null}
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the module minting coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
```
#### BurnCoins
```json theme={null}
{
"type": "burn",
"attributes": [
{
"key": "burner",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
```
```json theme={null}
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the module burning coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being burned}}",
"index": true
}
]
}
```
#### addCoins
```json theme={null}
{
"type": "coin_received",
"attributes": [
{
"key": "receiver",
"value": "{{sdk.AccAddress of the address beneficiary of the coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being received}}",
"index": true
}
]
}
```
#### subUnlockedCoins/DelegateCoins
```json theme={null}
{
"type": "coin_spent",
"attributes": [
{
"key": "spender",
"value": "{{sdk.AccAddress of the address which is spending coins}}",
"index": true
},
{
"key": "amount",
"value": "{{sdk.Coins being spent}}",
"index": true
}
]
}
```
## Parameters
The bank module contains the following parameters
### SendEnabled
The SendEnabled parameter is now deprecated and not to be use. It is replaced
with state store records.
### DefaultSendEnabled
The default send enabled value controls send transfer capability for all
coin denominations unless specifically included in the array of `SendEnabled`
parameters.
## Client
### CLI
A user can query and interact with the `bank` module using the CLI.
#### Query
The `query` commands allow users to query `bank` state.
```shell theme={null}
heimdalld query bank --help
```
##### balances
The `balances` command allows users to query account balances by address.
```shell theme={null}
heimdalld query bank balances [address] [flags]
```
Example:
```shell theme={null}
heimdalld query bank balances cosmos1..
```
Example Output:
```yml theme={null}
balances:
- amount: "1000000000"
denom: stake
pagination:
next_key: null
total: "0"
```
##### denom-metadata
The `denom-metadata` command allows users to query metadata for coin denominations. A user can query metadata for a single denomination using the `--denom` flag or all denominations without it.
```shell theme={null}
heimdalld query bank denom-metadata [flags]
```
Example:
```shell theme={null}
heimdalld query bank denom-metadata --denom stake
```
Example Output:
```yml theme={null}
metadata:
base: stake
denom_units:
- aliases:
- STAKE
denom: stake
description: native staking token of simulation app
display: stake
name: SimApp Token
symbol: STK
```
##### total
The `total` command allows users to query the total supply of coins. A user can query the total supply for a single coin using the `--denom` flag or all coins without it.
```shell theme={null}
heimdalld query bank total [flags]
```
Example:
```shell theme={null}
heimdalld query bank total --denom stake
```
Example Output:
```yml theme={null}
amount: "10000000000"
denom: stake
```
##### send-enabled
The `send-enabled` command allows users to query for all or some SendEnabled entries.
```shell theme={null}
heimdalld query bank send-enabled [denom1 ...] [flags]
```
Example:
```shell theme={null}
heimdalld query bank send-enabled
```
Example output:
```yml theme={null}
send_enabled:
- denom: foocoin
enabled: true
- denom: barcoin
pagination:
next-key: null
total: 2
```
#### Transactions
The `tx` commands allow users to interact with the `bank` module.
```shell theme={null}
heimdalld tx bank --help
```
##### send
The `send` command allows users to send funds from one account to another.
```shell theme={null}
heimdalld tx bank send [from_key_or_address] [to_address] [amount] [flags]
```
Example:
```shell theme={null}
heimdalld tx bank send cosmos1.. cosmos1.. 100stake
```
## gRPC
A user can query the `bank` module using gRPC endpoints.
### Balance
The `Balance` endpoint allows users to query account balance by address for a given denomination.
```shell theme={null}
cosmos.bank.v1beta1.Query/Balance
```
Example:
```shell theme={null}
grpcurl -plaintext \
-d '{"address":"cosmos1..","denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/Balance
```
Example Output:
```json theme={null}
{
"balance": {
"denom": "stake",
"amount": "1000000000"
}
}
```
### AllBalances
The `AllBalances` endpoint allows users to query account balance by address for all denominations.
```shell theme={null}
cosmos.bank.v1beta1.Query/AllBalances
```
Example:
```shell theme={null}
grpcurl -plaintext \
-d '{"address":"cosmos1.."}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/AllBalances
```
Example Output:
```json theme={null}
{
"balances": [
{
"denom": "stake",
"amount": "1000000000"
}
],
"pagination": {
"total": "1"
}
}
```
### DenomMetadata
The `DenomMetadata` endpoint allows users to query metadata for a single coin denomination.
```shell theme={null}
cosmos.bank.v1beta1.Query/DenomMetadata
```
Example:
```shell theme={null}
grpcurl -plaintext \
-d '{"denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/DenomMetadata
```
Example Output:
```json theme={null}
{
"metadata": {
"description": "native staking token of simulation app",
"denomUnits": [
{
"denom": "stake",
"aliases": [
"STAKE"
]
}
],
"base": "stake",
"display": "stake",
"name": "SimApp Token",
"symbol": "STK"
}
}
```
### DenomsMetadata
The `DenomsMetadata` endpoint allows users to query metadata for all coin denominations.
```shell theme={null}
cosmos.bank.v1beta1.Query/DenomsMetadata
```
Example:
```shell theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/DenomsMetadata
```
Example Output:
```json theme={null}
{
"metadatas": [
{
"description": "native staking token of simulation app",
"denomUnits": [
{
"denom": "stake",
"aliases": [
"STAKE"
]
}
],
"base": "stake",
"display": "stake",
"name": "SimApp Token",
"symbol": "STK"
}
],
"pagination": {
"total": "1"
}
}
```
### DenomOwners
The `DenomOwners` endpoint allows users to query metadata for a single coin denomination.
```shell theme={null}
cosmos.bank.v1beta1.Query/DenomOwners
```
Example:
```shell theme={null}
grpcurl -plaintext \
-d '{"denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/DenomOwners
```
Example Output:
```json theme={null}
{
"denomOwners": [
{
"address": "cosmos1..",
"balance": {
"denom": "stake",
"amount": "5000000000"
}
},
{
"address": "cosmos1..",
"balance": {
"denom": "stake",
"amount": "5000000000"
}
},
],
"pagination": {
"total": "2"
}
}
```
### TotalSupply
The `TotalSupply` endpoint allows users to query the total supply of all coins.
```shell theme={null}
cosmos.bank.v1beta1.Query/TotalSupply
```
Example:
```shell theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/TotalSupply
```
Example Output:
```json theme={null}
{
"supply": [
{
"denom": "stake",
"amount": "10000000000"
}
],
"pagination": {
"total": "1"
}
}
```
### SupplyOf
The `SupplyOf` endpoint allows users to query the total supply of a single coin.
```shell theme={null}
cosmos.bank.v1beta1.Query/SupplyOf
```
Example:
```shell theme={null}
grpcurl -plaintext \
-d '{"denom":"stake"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/SupplyOf
```
Example Output:
```json theme={null}
{
"amount": {
"denom": "stake",
"amount": "10000000000"
}
}
```
### Params
The `Params` endpoint allows users to query the parameters of the `bank` module.
```shell theme={null}
cosmos.bank.v1beta1.Query/Params
```
Example:
```shell theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/Params
```
Example Output:
```json theme={null}
{
"params": {
"defaultSendEnabled": true
}
}
```
### SendEnabled
The `SendEnabled` enpoints allows users to query the SendEnabled entries of the `bank` module.
Any denominations NOT returned, use the `Params.DefaultSendEnabled` value.
```shell theme={null}
cosmos.bank.v1beta1.Query/SendEnabled
```
Example:
```shell theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.bank.v1beta1.Query/SendEnabled
```
Example Output:
```json theme={null}
{
"send_enabled": [
{
"denom": "foocoin",
"enabled": true
},
{
"denom": "barcoin"
}
],
"pagination": {
"next-key": null,
"total": 2
}
}
```
## concepts/changes specific to heimdall
Since heimdall is a unique component of the PoS architecture and not a traditional Cosmos SDK app, some changes had to be made in order to ensure correct functionality, some of which are :
* MsgSetSendEnabled is not supported since POL is the only denom used.
* (Un)delegation is not supported since staking related logic is handled by PoS [core contracts](https://github.com/0xPolygon/core-contracts/tree/main/contracts) deployed on Ethereum.
* A default fee of 10^15 POL (`DefaultFeeInPol`) is deducted from the tx sender.
# Chain management
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/chain-management
How the Heimdall chainmanager module stores and exposes PoS protocol parameters, including contract addresses, chain IDs, and confirmation block counts.
The chainmanager module stores and exposes the PoS protocol parameters.
These params include addresses of contracts deployed on mainchain (Ethereum) and bor chain (Bor),
chain ids, mainchain and bor chain confirmation blocks.
```protobuf theme={null}
message ChainParams {
option (gogoproto.equal) = true;
string bor_chain_id = 1 [ (amino.dont_omitempty) = true ];
string heimdall_chain_id = 2 [ (amino.dont_omitempty) = true ];
string pol_token_address = 3 [ (amino.dont_omitempty) = true ];
string staking_manager_address = 4 [ (amino.dont_omitempty) = true ];
string slash_manager_address = 5 [ (amino.dont_omitempty) = true ];
string root_chain_address = 6 [ (amino.dont_omitempty) = true ];
string staking_info_address = 7 [ (amino.dont_omitempty) = true ];
string state_sender_address = 8 [ (amino.dont_omitempty) = true ];
string state_receiver_address = 9 [ (amino.dont_omitempty) = true ];
string validator_set_address = 10 [ (amino.dont_omitempty) = true ];
}
message Params {
option (gogoproto.equal) = true;
ChainParams chain_params = 1
[ (amino.dont_omitempty) = true, (gogoproto.nullable) = false ];
uint64 main_chain_tx_confirmations = 2 [ (amino.dont_omitempty) = true ];
uint64 bor_chain_tx_confirmations = 3 [ (amino.dont_omitempty) = true ];
}
```
## Query commands
One can run the following query commands from the chainmanager module :
* `params` - Fetch the parameters associated with the chainmanager module.
### CLI commands
```bash theme={null}
heimdalld query chainmanager params
```
### GRPC Endpoints
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.chainmanager.Query/GetChainManagerParams
```
### REST endpoints
```bash theme={null}
curl localhost:1317/heimdallv2/chainmanager/params
```
# Changes from v1
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/changes-from-v1
Key differences between Heimdall v1 and v2, including encoding changes, the keyring requirement for validator signing, and vote extensions.
The main differences between Heimdall v1 and v2 are described below.
## Encoding change to Base64
Data encoding format changed from hex-encoded to Base64-encoded.
Example:
* Hex encoded: `BJSk2KCI4snP2Cw/ntDdgp8R25XJ2xg18KL67fyEAwgtPMpeq5APSUHrkv5wtgrFfpmcDivnP8HPGufyyXnByxo=`
* Base64 encoded: `0x0494a4d8a088e2c9cfd82c3f9ed0dd829f11db95c9db1835f0a2faedfc8403082d3cca5eab900f4941eb92fe70b60ac57e999c0e2be73fc1cf1ae7f2c979c1cb1a`
## Validator signing key and keyring
In Heimdall-v2, validator signing keys must be imported into the keyring before the node can sign transactions. This replaces the v1 approach of reading the key directly from a config file.
From the Cosmos documentation:
> The keyring holds the private/public keypairs used to interact with a node. For instance, a validator key needs to be set up before running the blockchain node, so that blocks can be correctly signed. The private key can be stored in different locations, called "backends", such as a file or the operating system's own key storage.
For details on keyring configuration, see the [Cosmos SDK documentation](https://docs.cosmos.network/v0.46/run-node/keyring.html).
To import your validator private key into the keyring:
1. Get your Base64-encoded private key:
```bash theme={null}
cat /var/lib/heimdall/config/priv_validator_key.json
```
2. Convert the Base64-encoded key to hex:
```bash theme={null}
echo "" | base64 -d | xxd -p -c 256
```
3. Import the hex-encoded key into the keyring:
```bash theme={null}
heimdalld keys import-hex --home
```
When you first import a key, you are prompted for a password. This password is required each time you sign a transaction.
4. When running a transaction command, specify `--from` with the key name:
```bash theme={null}
heimdalld tx gov vote 1 yes --from
```
## Vote extensions in each block
In Heimdall-v2, the first transaction of each block contains encoded vote extensions. To decode these, use the additional decode command provided in `heimdalld`.
# Checkpoints
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/checkpoints
How Heimdall checkpoints work, from proposal through Ethereum submission and acknowledgment, including the ABCI++ processing flow and message types.
Checkpoints are vital components of the Polygon network, representing snapshots of the Bor chain state. These checkpoints are attested by a majority of the validator set before being validated and submitted on Ethereum contracts.
Heimdall, an integral part of this process, manages checkpoint functionalities using the `checkpoint` module. It coordinates with the Bor chain to verify checkpoint root hashes when a new checkpoint is proposed.
## Overview
Heimdall selects the next proposer using CometBFT’s leader selection algorithm.\
The multi-stage checkpoint process is crucial due to potential failures when submitting checkpoints on the Ethereum chain caused by factors like gas limit, network traffic, or high gas fees.
Each checkpoint has a validator as the proposer.\
The outcome of a checkpoint on the Ethereum chain (success or failure) triggers an ack (acknowledgment) or no-ack (no acknowledgment) transaction,\
altering the proposer for the next checkpoint on Heimdall.
## Flow
### Checkpoint Proposal
A checkpoint proposal is initiated by a proposer, a validator with POL tokens staked on the L1 Ethereum root chain.\
The checkpointing process is managed by the `bridge processor` which generates a `MsgCheckpoint` and broadcasts it as a transaction.
* The proposer derives the root hash from the Bor chain contract.
* Due to Bor’s finality time, the root hash may not always reflect the latest Bor tip.
### Checkpoint Processing in Heimdall
Once the checkpoint message is included in a Heimdall block,
it undergoes processing through the message handling system.\
Each validator node independently verifies the checkpoint
by checking the Bor root hash provided in the message against its local Bor chain.
### ABCI++ Processing Flow for the checkpoint submission on Heimdall
* `Prepare Proposal`: During the proposal phase, the checkpoint message `MsgCheckpoint` is included in the proposed block only if dry-running this tx does not return any errors.
* `Process Proposal`: The proposal is validated to ensure correctness.
* `Pre-Commit`: As part of the voting process, validators execute a side transaction to verify the checkpoint against their local Bor data.
If the checkpoint is valid, validators include a vote extension confirming their approval.
* `Verify Vote`: Injected votes are verified.\
• `Next block - Finalize`: In the next block, the finalized votes are processed, and the checkpoint is considered approved if a sufficient majority supports it.\
The `preBlocker` triggers post-tx handlers performing the Heimdall state changes when the checkpoint is finally saved in the checkpoint buffer as the checkpoint that needs to be further bridged to the Ethereum L1 root chain.
### Submission to Ethereum (L1)
Once approved, the checkpoint is added to a checkpoint buffer and an event is emitted. The bridge system, which listens for these events, submits the checkpoint data along with validator signatures to the Ethereum root chain.
### Acknowledgment from Ethereum (L1)
After the checkpoint is successfully included on the Ethereum chain, an acknowledgment `MsgCpAck` is sent back to Heimdall from the bridge processor.\
This acknowledgment, once processed through the ABCI++ flow with side and post-tx handlers: updates the state, flushes processed checkpoints from the buffer, and increments the number of ACK counters to track confirmations of checkpoints.\
Additionally, the selection of the next checkpoint proposer is adjusted based on the updated state.
### Missing Checkpoint Acknowledgment from Ethereum (L1)
The `MsgCpNoAck` message is broadcast by the bridge processor to indicate that a checkpoint was potentially transferred to the Ethereum chain but has not received an acknowledgment.\
A background routine periodically checks for time elapsed and publishes the No-ACK signal. No-ACK is sent if a sufficient amount of time has passed since:
* the last checkpoint was created on the Heimdall-v2 chain and
* the last No-ACK was issued.\
To conclude, the No-ACKs are triggered only when a checkpoint acknowledgment is overdue, ensuring they are not sent too frequently.\
This message is broadcasted only by the proposer. This entire flow ensures that checkpoints are securely proposed, verified, and finalized across the Heimdall and Ethereum chains in a decentralized manner.
### Messages
#### MsgCheckpoint
`MsgCheckpoint` defines a message for creating a checkpoint on the Ethereum chain.
```protobuf theme={null}
message MsgCheckpoint {
option (cosmos.msg.v1.signer) = "proposer";
option (amino.name) = "heimdallv2/checkpoint/MsgCheckpoint";
option (gogoproto.equal) = true;
option (gogoproto.goproto_getters) = true;
string proposer = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 start_block = 2 [ (amino.dont_omitempty) = true ];
uint64 end_block = 3 [ (amino.dont_omitempty) = true ];
bytes root_hash = 4 [ (amino.dont_omitempty) = true ];
bytes account_root_hash = 5 [ (amino.dont_omitempty) = true ];
string bor_chain_id = 6 [ (amino.dont_omitempty) = true ];
}
```
#### MsgCpAck
`MsgCpAck` defines a message for creating the ack tx of a submitted checkpoint.
```protobuf theme={null}
message MsgCpAck {
option (cosmos.msg.v1.signer) = "from";
option (amino.name) = "heimdallv2/checkpoint/MsgCpAck";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = true;
string from = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 number = 2 [ (amino.dont_omitempty) = true ];
string proposer = 3 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 start_block = 4 [ (amino.dont_omitempty) = true ];
uint64 end_block = 5 [ (amino.dont_omitempty) = true ];
bytes root_hash = 6 [ (amino.dont_omitempty) = true ];
bytes tx_hash = 7 [ (amino.dont_omitempty) = true ];
uint64 log_index = 8 [ (amino.dont_omitempty) = true ];
}
```
#### MsgCheckpointNoAck
`MsgCpNoAck` defines a message for creating the no-ack tx of a checkpoint.
```protobuf theme={null}
message MsgCpNoAck {
option (cosmos.msg.v1.signer) = "from";
option (amino.name) = "heimdallv2/checkpoint/MsgCpNoAck";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = true;
string from = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
}
```
## Interact with the Node
### Tx Commands
#### Send checkpoint
```bash theme={null}
heimdalld tx checkpoint send-checkpoint --proposer= --start-block= --end-block= --root-hash= --account-root= --bor-chain-id= --auto-configure=true/false
```
#### Send checkpoint ack
```bash theme={null}
heimdalld tx checkpoint send-ack --tx-hash= --log-index= --header= --proposer= --auto-configure=true/false
```
#### Send checkpoint no-ack
```bash theme={null}
heimdalld tx checkpoint checkpoint-no-ack --from
```
## CLI Query Commands
One can run the following query commands from the checkpoint module:
* `get-params` - Get checkpoint params
* `get-overview` - Get checkpoint overview
* `get-ack-count` - Get checkpoint ack count
* `get-checkpoint` - Get checkpoint based on its number
* `get-checkpoint-latest` - Get the latest checkpoint
* `get-checkpoint-buffer` - Get the checkpoint buffer
* `get-last-no-ack` - Get the last no ack
* `get-next-checkpoint` - Get the next checkpoint
* `get-current-proposer` - Get the current proposer
* `get-proposers` - Get the proposers
* `get-checkpoint-list` - Get the list of checkpoints
```bash theme={null}
heimdalld query checkpoint get-params
```
```bash theme={null}
heimdalld query checkpoint get-overview
```
```bash theme={null}
heimdalld query checkpoint get-ack-count
```
```bash theme={null}
heimdalld query checkpoint get-checkpoint
```
```bash theme={null}
heimdalld query checkpoint get-checkpoint-latest
```
```bash theme={null}
heimdalld query checkpoint get-checkpoint-buffer
```
```bash theme={null}
heimdalld query checkpoint get-last-no-ack
```
```bash theme={null}
heimdalld query checkpoint get-next-checkpoint
```
```bash theme={null}
heimdalld query checkpoint get-current-proposer
```
```bash theme={null}
heimdalld query checkpoint get-proposers
```
```bash theme={null}
heimdalld query checkpoint get-checkpoint-list
```
## GRPC Endpoints
The endpoints and the params are defined in the [checkpoint/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/checkpoint/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetCheckpointParams
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetCheckpointOverview
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetAckCount
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetCheckpointLatest
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetCheckpointBuffer
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetLastNoAck
```
```bash theme={null}
grpcurl -plaintext -d '{"bor_chain_id": <>}' localhost:9090 heimdallv2.checkpoint.Query/GetNextCheckpoint
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetCurrentProposer
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetProposers
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.checkpoint.Query/GetCheckpointList
```
```bash theme={null}
grpcurl -plaintext -d '{"tx_hash": <>}' localhost:9090 heimdallv2.checkpoint.QueryGetCheckpointSignatures
```
```bash theme={null}
grpcurl -plaintext -d '{"number": <>}' localhost:9090 heimdallv2.checkpoint.Query/GetCheckpoint
```
## REST Endpoints
The endpoints and the params are defined in the [checkpoint/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/checkpoint/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
curl localhost:1317/checkpoints/params
```
```bash theme={null}
curl localhost:1317/checkpoints/overview
```
```bash theme={null}
curl localhost:1317/checkpoints/count
```
```bash theme={null}
curl localhost:1317/checkpoints/latest
```
```bash theme={null}
curl localhost:1317/checkpoints/buffer
```
```bash theme={null}
curl localhost:1317/checkpoints/last-no-ack
```
```bash theme={null}
curl localhost:1317/checkpoints/prepare-next
```
```bash theme={null}
curl localhost:1317/checkpoint/proposers/current
```
```bash theme={null}
curl localhost:1317/checkpoint/proposers/{times}
```
```bash theme={null}
curl localhost:1317/checkpoints/list
```
```bash theme={null}
curl localhost:1317/checkpoints/signatures/{tx_hash}
```
```bash theme={null}
curl localhost:1317/checkpoints/{number}
```
# Clerk
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/clerk
How the Heimdall clerk module manages state-sync event records from Ethereum, including the EventRecord structure and the validation flow.
## Preliminary terminology
* A `StateSender` is a contract deployed on L1 (Ethereum) responsible for emitting state-sync events.
* A `StateReceiver` is a contract deployed on L2 (Bor) responsible for receiving state-sync events.
* A `EventRecord` is a record of the state-sync event stored in the heimdall state.
## Overview
Clerk module manages generic event records from the Ethereum blockchain related to state-sync events.\
These are specially designed events that are emitted by the StateSender contract on the L1 chain to notify the L2 nodes
(Bor in case of PoS) about the state changes in the L1.
Once the bridge processes the events,
the clerk module listens to these events and stores them in the database for further processing.
## State-Sync Mechanism
It's a mechanism for state-management between the Ethereum and Bor chain.
The events generated are called state-sync events.
This is a way to move data from the L1 chain to the L2 chain.
## How it works
An `EventRecord` is defined by the data structure :
```protobuf theme={null}
message EventRecord {
option (gogoproto.goproto_getters) = false;
option (gogoproto.equal) = false;
uint64 id = 1 [ (amino.dont_omitempty) = true ];
string contract = 2 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
bytes data = 3 [ (amino.dont_omitempty) = true ];
string tx_hash = 4 [ (amino.dont_omitempty) = true ];
uint64 log_index = 5 [ (amino.dont_omitempty) = true ];
string bor_chain_id = 6 [ (amino.dont_omitempty) = true ];
google.protobuf.Timestamp record_time = 7 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
}
```
* `id` is the unique identifier for the event record, Generated by the `StateSender` contract.
* `contract` is the address of the contract on the L2 chain on which the event will be processed.
* `data` is the data of the event which will be processed by the contract.
* `txHash` is the transaction hash of the event on the L1 chain.
* `logIndex` is the log index of the event on the L1 chain.
* `borChainID` is the chain id of the bor chain.
* `recordTime` is the time at which the event was recorded in heimdall state.
The bridge will listen to the state-sync events from L1 and generate a txn with `MsgEventRecord` which is responsible for validating events from `StateSender` contract and storing the `EventRecord` on the heimdall state for bor to use.
```protobuf theme={null}
message MsgEventRecord {
option (amino.name) = "heimdallv2/clerk/MsgEventRecord";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
option (cosmos.msg.v1.signer) = "from";
string from = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
string tx_hash = 2 [ (amino.dont_omitempty) = true ];
uint64 log_index = 3 [ (amino.dont_omitempty) = true ];
uint64 block_number = 4 [ (amino.dont_omitempty) = true ];
string contract_address = 5 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
bytes data = 6 [ (amino.dont_omitempty) = true ];
uint64 id = 7 [ (amino.dont_omitempty) = true ];
string chain_id = 8 [ (amino.dont_omitempty) = true ];
}
```
[Handler](https://github.com/0xPolygon/heimdall-v2/blob/develop/x/bor/keeper/msg_server.go) for this transaction validates for multiple conditions including `TxHash` and `LogIndex` to ensure that the event exists on L1 and the data is not tampered with, It throws `Older invalid tx found` error if the event is already processed.
Once the event is validated by the Handler,
it will go to `SideHandleMsgEventRecord` in each validator node and after verifying the event,
the validators will vote with either a `YES` return an error for failed verification.
Only when there is a majority of `YES` votes, The event will be processed by `PostHandleMsgEventRecord` which will persist the event in the state via keeper.
## Adding an event manually
A validator can use the CLI to add an event to the state if it is missing and was not processed by the bridge:
```bash theme={null}
heimdalld tx clerk handle-msg-event-record [from] [tx-hash] [log-index] [block-number] [contract-address] [data] [id] [chain-id]
```
## Query commands
One can run the following query commands from the clerk module :
* `record` - Query for a specific event record by its ID.
* `record-list` - Query a list of event records by page and limit.
* `is-old-tx` - Query if the event record is already processed.
* `latest-record-id` - Query the latest record (state-sync) id from L1.
### CLI commands
```bash theme={null}
heimdalld query clerk record [record-id]
```
```bash theme={null}
heimdalld query clerk record-list [page] [limit]
```
```bash theme={null}
heimdalld query clerk record-list-with-time [from-id] [to-time]
```
```bash theme={null}
heimdalld query clerk record-sequence [tx-hash] [log-index]
```
```bash theme={null}
heimdalld query clerk is-old-tx [tx-hash] [log-index]
```
```bash theme={null}
heimdalld query clerk latest-record-id
```
### GRPC Endpoints
The endpoints and the params are defined in the [clerk/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/clerk/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.clerk.Query/GetRecordList
```
```bash theme={null}
grpcurl -plaintext -d '{"record_id": <>}' localhost:9090 heimdallv2.clerk.Query/GetRecordById
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.clerk.Query/GetRecordListWithTime
```
```bash theme={null}
grpcurl -plaintext -d '{"tx_hash": <>, "log_index": <>}' localhost:9090 heimdallv2.clerk.Query/GetRecordSequence
```
```bash theme={null}
grpcurl -plaintext -d '{"tx_hash": <>, "log_index": <>}' localhost:9090 heimdallv2.clerk.Query/IsClerkTxOld
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.clerk.Query/GetLatestRecordId
```
### REST endpoints
The endpoints and the params are defined in the [clerk/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/clerk/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
curl localhost:1317/clerk/event-records/list?page=&limit=
```
```bash theme={null}
curl localhost:1317/clerk/event-records/latest-id
```
```bash theme={null}
curl localhost:1317/clerk/event-records/
```
```bash theme={null}
curl localhost:1317/clerk/time?from_id=&to_time=&page=&limit=
```
```bash theme={null}
curl localhost:1317/clerk/sequence?tx_hash=&log_index=
```
```bash theme={null}
curl localhost:1317/clerk/is-old-tx?tx_hash=&log_index=
```
# Governance
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/governance
How the Heimdall governance module works, including proposal submission, voting, deposit handling, and parameter changes.
## Abstract
This page specifies the Governance module of the Cosmos SDK, which was first
described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in
June 2016.
The module enables Cosmos SDK based blockchain to support an onchain governance
system. In this system, holders of the native staking token of the chain can vote
on proposals on a 1 token 1 vote basis. Next is a list of features the module
currently supports:
* **Proposal submission:** Users can submit proposals with a deposit. Once the
minimum deposit is reached, the proposal enters voting period. The minimum deposit can be reached by collecting deposits from different users (including proposer) within deposit period.
* **Vote:** Participants can vote on proposals that reached MinDeposit and entered voting period.
* **Inheritance and penalties:** Delegators inherit their validator's vote if
they don't vote themselves.
* **Claiming deposit:** Users that deposited on proposals can recover their
deposits if the proposal was accepted or rejected. If the proposal was vetoed, or never entered voting period (minimum deposit not reached within deposit period), the deposit is burned.
The burn functionality is not supported by Heimdall, hence in any case the deposit is refunded to the proposer.\
Also, Heimdall does not support delegation, hence the inheritance is not to be considered.
This module is in use on the Cosmos Hub (a.k.a [gaia](https://github.com/cosmos/gaia)).
Features that may be added in the future are described in [Future Improvements](#future-improvements).
## Contents
The following specification uses *POL* as the native staking token. The module
can be adapted to any Proof-Of-Stake blockchain by replacing *POL* with the native
staking token of the chain.
* [Concepts](#concepts)
* [Proposal submission](#proposal-submission)
* [Deposit](#deposit)
* [Vote](#vote)
* [Software Upgrade](#software-upgrade)
* [State](#state)
* [Proposals](#proposals)
* [Parameters and base types](#parameters-and-base-types)
* [Deposit](#deposit-1)
* [ValidatorGovInfo](#validatorgovinfo)
* [Stores](#stores)
* [Proposal Processing Queue](#proposal-processing-queue)
* [Legacy Proposal](#legacy-proposal)
* [Messages](#messages)
* [Proposal Submission](#proposal-submission-1)
* [Deposit](#deposit-2)
* [Vote](#vote-1)
* [Events](#events)
* [EndBlocker](#endblocker)
* [Handlers](#handlers)
* [Parameters](#parameters)
* [Client](#client)
* [CLI](#cli)
* [gRPC](#grpc)
* [REST](#rest)
* [Metadata](#metadata)
* [Proposal](#proposal-3)
* [Vote](#vote-5)
* [Future Improvements](#future-improvements)
## Concepts
*Disclaimer: This is work in progress. Mechanisms are susceptible to change.*
The governance process is divided in a few steps that are outlined below:
* **Proposal submission:** Proposal is submitted to the blockchain with a
deposit.
* **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is
confirmed and vote opens. Polygon PoS validators can then send `TxGovVote`
transactions to vote on the proposal.
* **Execution** After a period of time, the votes are tallied and depending
on the result, the messages in the proposal will be executed.
### Proposal submission
#### Right to submit a proposal
Every account can submit proposals by sending a `MsgSubmitProposal` transaction.
Once a proposal is submitted, it is identified by its unique `proposalID`.
#### Proposal Messages
A proposal includes an array of `sdk.Msg`s which are executed automatically if the
proposal passes. The messages are executed by the governance `ModuleAccount` itself. Modules
such as `x/upgrade`, that want to allow certain messages to be executed by governance
only should add a whitelist within the respective msg server, granting the governance
module the right to execute the message once a quorum has been reached. The governance
module uses the `MsgServiceRouter` to check that these messages are correctly constructed
and have a respective path to execute on but do not perform a full validity check.
### Deposit
To prevent spam, proposals must be submitted with a deposit in the coins defined by
the `MinDeposit` param.
When a proposal is submitted, it has to be accompanied with a deposit that must be
strictly positive, but can be inferior to `MinDeposit`. The submitter doesn't need
to pay for the entire deposit on their own. The newly created proposal is stored in
an *inactive proposal queue* and stays there until its deposit passes the `MinDeposit`.
Other token holders can increase the proposal's deposit by sending a `Deposit`
transaction. If a proposal doesn't pass the `MinDeposit` before the deposit end time
(the time when deposits are no longer accepted), the proposal will be destroyed: the
proposal will be removed from state and the deposit will be refunded (see x/gov `EndBlocker`).
When a proposal deposit passes the `MinDeposit` threshold (even during the proposal
submission) before the deposit end time, the proposal will be moved into the
*active proposal queue* and the voting period will begin.
The deposit is kept in escrow and held by the governance `ModuleAccount` until the
proposal is finalized (passed or rejected).
#### Deposit refund and burn
When a proposal is finalized, the coins from the deposit are either refunded or burned
according to the final tally of the proposal. In Heimdall, burn is not enabled, hence all the deposits will be refunded.
* If the proposal is approved or rejected but *not* vetoed, each deposit will be
automatically refunded to its respective depositor (transferred from the governance
`ModuleAccount`).
* When the proposal is vetoed with greater than 1/3, deposits will be refunded from the
governance `ModuleAccount` and the proposal information along with its deposit
information will be removed from state.
* All refunded deposits are removed from the state. Events are issued when
refunding a deposit.
### Vote
#### Participants
*Participants* are users that have the right to vote on proposals. On the
Polygon PoS network, participants are validators. Other holders and users do not get the right to participate in governance.
However, they can submit and deposit on proposals.
Note that for *participants*, their voting power is calculated from their L1 POL stakes only.
#### Voting period
Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We
define `Voting period` as the interval between the moment the vote opens and
the moment the vote closes. The initial value of `Voting period` is 2 weeks.
#### Option set
The option set of a proposal refers to the set of choices a participant can
choose from when casting its vote.
The initial option set includes the following options:
* `Yes`
* `No`
* `NoWithVeto`
* `Abstain`
`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option
allows voters to signal that they do not intend to vote in favor or against the
proposal but accept the result of the vote.
*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ option that casts a `NoWithVeto` vote.*
#### Weighted Votes
*Weighted Votes are not supported in Heimdall*.
[ADR-037](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-037-gov-split-vote.md) introduces the weighted vote feature which allows a staker to split their votes into several voting options. For example, it could use 70% of its voting power to vote Yes and 30% of its voting power to vote No.
Often times the entity owning that address might not be a single individual. For example, a company might have different stakeholders who want to vote differently, and so it makes sense to allow them to split their voting power. Currently, it is not possible for them to do "passthrough voting" and giving their users voting rights over their tokens. However, with this system, exchanges can poll their users for voting preferences, and then vote onchain proportionally to the results of the poll.
To represent weighted vote on chain, we use the following Protobuf message.
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L34-L47](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L34-L47)
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L181-L201](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1beta1/gov.proto#L181-L201)
For a weighted vote to be valid, the `options` field must not contain duplicate vote options, and the sum of weights of all options must be equal to 1.
### Quorum
Quorum is defined as the minimum percentage of voting power that needs to be
cast on a proposal for the result to be valid.
### Expedited Proposals
A proposal can be expedited, making the proposal use shorter voting duration and a higher tally threshold by its default. If an expedited proposal fails to meet the threshold within the scope of shorter voting duration, the expedited proposal is then converted to a regular proposal and restarts voting under regular voting conditions.
#### Threshold
Threshold is defined as the minimum proportion of `Yes` votes (excluding
`Abstain` votes) for the proposal to be accepted.
Initially, the threshold is set at 50% of `Yes` votes, excluding `Abstain`
votes. A possibility to veto exists if more than 1/3rd of all votes are
`NoWithVeto` votes. Note, both of these values are derived from the `TallyParams`
onchain parameter, which is modifiable by governance.
This means that proposals are accepted iff:
* There exist staked tokens.
* Quorum has been achieved.
* The proportion of `Abstain` votes is inferior to 1/1.
* The proportion of `NoWithVeto` votes is inferior to 1/3, including
`Abstain` votes.
* The proportion of `Yes` votes, excluding `Abstain` votes, at the end of
the voting period is superior to 1/2.
For expedited proposals, by default, the threshold is higher than with a *normal proposal*, namely, 66.7%.
#### Inheritance
*Inheritance is not supported in Heimdall, as there's no concept of tokens delegation*.
If a delegator does not vote, it will inherit its validator vote.
* If the delegator votes before its validator, it will not inherit from the
validator's vote.
* If the delegator votes after its validator, it will override its validator
vote with its own. If the proposal is urgent, it is possible
that the vote will close before delegators have a chance to react and
override their validator's vote. This is not a problem, as proposals require more than 2/3rd of the total voting power to pass, when tallied at the end of the voting period. Because as little as 1/3 + 1 validation power could collude to censor transactions, non-collusion is already assumed for ranges exceeding this threshold.
#### Validator’s punishment for non-voting
At present, validators are not punished for failing to vote.
#### Governance address
Later, we may add permissioned keys that could only sign txs from certain modules. For the MVP, the `Governance address` will be the main validator address generated at account creation. This address corresponds to a different PrivKey than the CometBFT PrivKey which is responsible for signing consensus messages. Validators thus do not have to sign governance transactions with the sensitive CometBFT PrivKey.
#### Burnable Params
There are three parameters that define if the deposit of a proposal should be burned or returned to the depositors.
* `BurnVoteVeto` burns the proposal deposit if the proposal gets vetoed.
* `BurnVoteQuorum` burns the proposal deposit if the proposal deposit if the vote does not reach quorum.
* `BurnProposalDepositPrevote` burns the proposal deposit if it does not enter the voting phase.
> Note: These parameters are modifiable via governance.
## State
### Constitution
`Constitution` is found in the genesis state. It is a string field intended to be used to descibe the purpose of a particular blockchain, and its expected norms. A few examples of how the constitution field can be used:
* define the purpose of the chain, laying a foundation for its future development
* set expectations for delegators (not supported in Heimdall)
* set expectations for validators
* define the chain's relationship to "meatspace" entities, like a foundation or corporation
Since this is more of a social feature than a technical feature, we'll now get into some items that may have been useful to have in a genesis constitution:
* What limitations on governance exist, if any?
* is it okay for the community to slash the wallet of a whale that they no longer feel that they want around? (viz: Juno Proposal 4 and 16)
* can governance "socially slash" a validator who is using unapproved MEV? (viz: commonwealth.im/osmosis)
* In the event of an economic emergency, what should validators do?
* Terra crash of May, 2022, saw validators choose to run a new binary with code that had not been approved by governance, because the governance token had been inflated to nothing.
* What is the purpose of the chain, specifically?
* best example of this is the Cosmos hub, where different founding groups, have different interpretations of the purpose of the network.
This genesis entry, "constitution" hasn't been designed for existing chains, who should likely just ratify a constitution using their governance system. Instead, this is for new chains. It will allow for validators to have a much clearer idea of purpose and the expectations placed on them while operating their nodes. Likewise, for community members, the constitution will give them some idea of what to expect from both the "chain team" and the validators, respectively.
This constitution is designed to be immutable, and placed only in genesis, though that could change over time by a pull request to the cosmos-sdk that allows for the constitution to be changed by governance. Communities wishing to make amendments to their original constitution should use the governance mechanism and a "signaling proposal" to do exactly that.
**Ideal use scenario for a cosmos chain constitution**
As a chain developer, you decide that you'd like to provide clarity to your key user groups:
* validators
* token holders
* developers (yourself)
You use the constitution to immutably store some Markdown in genesis, so that when difficult questions come up, the constutituon can provide guidance to the community.
### Proposals
`Proposal` objects are used to tally votes and generally track the proposal's state.
They contain an array of arbitrary `sdk.Msg`'s which the governance module will attempt
to resolve and then execute if the proposal passes. `Proposal`'s are identified by a
unique id and contains a series of timestamps: `submit_time`, `deposit_end_time`,
`voting_start_time`, `voting_end_time` which track the lifecycle of a proposal
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L51-L99](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L51-L99)
A proposal will generally require more than just a set of messages to explain its
purpose but need some greater justification and allow a means for interested participants
to discuss and debate the proposal.
In most cases, **it is encouraged to have an off-chain system that supports the onchain governance process**.
To accommodate for this, a proposal contains a special **`metadata`** field, a string,
which can be used to add context to the proposal. The `metadata` field allows custom use for networks,
however, it is expected that the field contains a URL or some form of CID using a system such as
[IPFS](https://docs.ipfs.io/concepts/content-addressing/). To support the case of
interoperability across networks, the SDK recommends that the `metadata` represents
the following `JSON` template:
```json theme={null}
{
"title": "...",
"description": "...",
"forum": "...", // a link to the discussion platform (i.e. Discord)
"other": "..." // any extra data that doesn't correspond to the other fields
}
```
This makes it far easier for clients to support multiple networks.
The metadata has a maximum length that is chosen by the app developer, and
passed into the gov keeper as a config. The default maximum length in the SDK is 255 characters.
#### Writing a module that uses governance
There are many aspects of a chain, or of the individual modules that you may want to
use governance to perform such as changing various parameters. This is very simple
to do. First, write out your message types and `MsgServer` implementation. Add an
`authority` field to the keeper which will be populated in the constructor with the
governance module account: `govKeeper.GetGovernanceAccount().GetAddress()`. Then for
the methods in the `msg_server.go`, perform a check on the message that the signer
matches `authority`. This will prevent any user from executing that message.
### Parameters and base types
`Parameters` define the rules according to which votes are run. There can only
be one active parameter set at any given time. If governance wants to change a
parameter set, either to modify a value or add/remove a parameter field, a new
parameter set has to be created and the previous one rendered inactive.
#### DepositParams
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L152-L162](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L152-L162)
#### VotingParams
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L164-L168](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L164-L168)
#### TallyParams
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L170-L182](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L170-L182)
Parameters are stored in a global `GlobalParams` KVStore.
Additionally, we introduce some basic types:
```go theme={null}
type Vote byte
const (
VoteYes = 0x1
VoteNo = 0x2
VoteNoWithVeto = 0x3
VoteAbstain = 0x4
)
type ProposalType string
const (
ProposalTypePlainText = "Text"
ProposalTypeSoftwareUpgrade = "SoftwareUpgrade" // currently not supported in Heimdall
)
type ProposalStatus byte
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01 // Proposal is submitted. Participants can deposit on it but not vote
StatusVotingPeriod ProposalStatus = 0x02 // MinDeposit is reached, participants can vote
StatusPassed ProposalStatus = 0x03 // Proposal passed and successfully executed
StatusRejected ProposalStatus = 0x04 // Proposal has been rejected
StatusFailed ProposalStatus = 0x05 // Proposal passed but failed execution
)
```
### Deposit
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L38-L49](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/gov.proto#L38-L49)
### ValidatorGovInfo
This type is used in a temp map when tallying
```go theme={null}
type ValidatorGovInfo struct {
Minus sdk.Dec
Vote Vote
}
```
## Stores
:::note
Stores are KVStores in the multi-store. The key to find the store is the first parameter in the list
:::
We will use one KVStore `Governance` to store four mappings:
* A mapping from `proposalID|'proposal'` to `Proposal`.
* A mapping from `proposalID|'addresses'|address` to `Vote`. This mapping allows
us to query all addresses that voted on the proposal along with their vote by
doing a range query on `proposalID:addresses`.
* A mapping from `ParamsKey|'Params'` to `Params`. This map allows to query all
x/gov params.
* A mapping from `VotingPeriodProposalKeyPrefix|proposalID` to a single byte. This allows
us to know if a proposal is in the voting period or not with very low gas cost.
For pseudocode purposes, here are the two function we will use to read or write in stores:
* `load(StoreKey, Key)`: Retrieve item stored at key `Key` in store found at key `StoreKey` in the multistore
* `store(StoreKey, Key, value)`: Write value `Value` at key `Key` in store found at key `StoreKey` in the multistore
### Proposal Processing Queue
**Store:**
* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the
`ProposalIDs` of proposals that reached `MinDeposit`. During each `EndBlock`,
all the proposals that have reached the end of their voting period are processed.
To process a finished proposal, the application tallies the votes, computes the
votes of each validator and checks if every validator in the validator set has
voted. If the proposal is accepted/rejected, deposits are refunded. Finally, the proposal
content `Handler` is executed.
And the pseudocode for the `ProposalProcessingQueue`:
```go theme={null}
in EndBlock do
for finishedProposalID in GetAllFinishedProposalIDs(block.Time)
proposal = load(Governance, ) // proposal is a const key
validators = Keeper.getAllValidators()
tmpValMap := map(sdk.AccAddress)ValidatorGovInfo
// Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes
for each validator in validators
tmpValMap(validator.OperatorAddr).Minus = 0
// Tally
voterIterator = rangeQuery(Governance, ) //return all the addresses that voted on the proposal
for each (voterAddress, vote) in voterIterator
_, isVal = stakingKeeper.getValidator(voterAddress)
if (isVal)
tmpValMap(voterAddress).Vote = vote
tallyingParam = load(GlobalParams, 'TallyingParam')
// Update tally if validator voted
for each validator in validators
if tmpValMap(validator).HasVoted
proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus))
// Check if proposal is accepted or rejected
totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingParam.Veto)
// proposal was accepted at the end of the voting period
// refund deposits (non-voters already punished)
for each (amount, depositor) in proposal.Deposits
depositor.PolBalance += amount
stateWriter, err := proposal.Handler()
if err != nil
// proposal passed but failed during state execution
proposal.CurrentStatus = ProposalStatusFailed
else
// proposal pass and state is persisted
proposal.CurrentStatus = ProposalStatusAccepted
stateWriter.save()
else
// proposal was rejected
proposal.CurrentStatus = ProposalStatusRejected
store(Governance, , proposal)
```
### Legacy Proposal
:::warning
Legacy proposals are deprecated. Use the new proposal flow by granting the governance module the right to execute the message.
:::
A legacy proposal is the old implementation of governance proposal.
Contrary to proposal that can contain any messages, a legacy proposal allows to submit a set of pre-defined proposals.
These proposals are defined by their types and handled by handlers that are registered in the gov v1beta1 router.
More information on how to submit proposals in the [client section](#client).
## Messages
### Proposal Submission
Proposals can be submitted by any account via a `MsgSubmitProposal` transaction.
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L42-L69](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L42-L69)
All `sdk.Msgs` passed into the `messages` field of a `MsgSubmitProposal` message
must be registered in the app's `MsgServiceRouter`. Each of these messages must
have one signer, namely the gov module account. And finally, the metadata length
must not be larger than the `maxMetadataLen` config passed into the gov keeper.
The `initialDeposit` must be strictly positive and conform to the accepted denom of the `MinDeposit` param.
**State modifications:**
* Generate new `proposalID`
* Create new `Proposal`
* Initialise `Proposal`'s attributes
* Decrease balance of sender by `InitialDeposit`
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueue`
* Transfer `InitialDeposit` from the `Proposer` to the governance `ModuleAccount`
### Deposit
Once a proposal is submitted, if `Proposal.TotalDeposit < ActiveParam.MinDeposit`, POL holders can send
`MsgDeposit` transactions to increase the proposal's deposit.
A deposit is accepted iff:
* The proposal exists
* The proposal is not in the voting period
* The deposited coins are conform to the accepted denom from the `MinDeposit` param
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L134-L147](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L134-L147)
**State modifications:**
* Decrease balance of sender by `deposit`
* Add `deposit` of sender in `proposal.Deposits`
* Increase `proposal.TotalDeposit` by sender's `deposit`
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueueEnd`
* Transfer `Deposit` from the `proposer` to the governance `ModuleAccount`
### Vote
Once `ActiveParam.MinDeposit` is reached, voting period starts. From there,
Polygon PoS validators are able to send `MsgVote` transactions to cast their
vote on the proposal.
protobuf reference
[https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L92-L108](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/proto/cosmos/gov/v1/tx.proto#L92-L108)
**State modifications:**
* Record `Vote` of sender
:::note
Gas cost for this message has to take into account the future tallying of the vote in EndBlocker.
:::
## Events
The governance module emits the following events:
### EndBlocker
| Type | Attribute Key | Attribute Value |
| ------------------ | ---------------- | ------------------ |
| inactive\_proposal | proposal\_id | `{proposalID}` |
| inactive\_proposal | proposal\_result | `{proposalResult}` |
| active\_proposal | proposal\_id | `{proposalID}` |
| active\_proposal | proposal\_result | `{proposalResult}` |
### Handlers
#### MsgSubmitProposal
| Type | Attribute Key | Attribute Value |
| --------------------- | --------------------- | ----------------- |
| submit\_proposal | proposal\_id | `{proposalID}` |
| submit\_proposal \[0] | voting\_period\_start | `{proposalID}` |
| proposal\_deposit | amount | `{depositAmount}` |
| proposal\_deposit | proposal\_id | `{proposalID}` |
| message | module | governance |
| message | action | submit\_proposal |
| message | sender | `{senderAddress}` |
* \[0] Event only emitted if the voting period starts during the submission.
#### MsgVote
| Type | Attribute Key | Attribute Value |
| -------------- | ------------- | ----------------- |
| proposal\_vote | option | `{voteOption}` |
| proposal\_vote | proposal\_id | `{proposalID}` |
| message | module | governance |
| message | action | vote |
| message | sender | `{senderAddress}` |
#### MsgVoteWeighted
| Type | Attribute Key | Attribute Value |
| -------------- | ------------- | ----------------------- |
| proposal\_vote | option | `{weightedVoteOptions}` |
| proposal\_vote | proposal\_id | `{proposalID}` |
| message | module | governance |
| message | action | vote |
| message | sender | `{senderAddress}` |
#### MsgDeposit
| Type | Attribute Key | Attribute Value |
| ---------------------- | --------------------- | ----------------- |
| proposal\_deposit | amount | `{depositAmount}` |
| proposal\_deposit | proposal\_id | `{proposalID}` |
| proposal\_deposit \[0] | voting\_period\_start | `{proposalID}` |
| message | module | governance |
| message | action | deposit |
| message | sender | `{senderAddress}` |
* \[0] Event only emitted if the voting period starts during the submission.
## Parameters
The governance module contains the following parameters:
| Key | Type | Example |
| -------------------------------- | ---------------- | --------------------------------------- |
| min\_deposit | array (coins) | `[{"denom":"pol","amount":"10000000"}]` |
| max\_deposit\_period | string (time ns) | "172800000000000" (17280s) |
| voting\_period | string (time ns) | "172800000000000" (17280s) |
| quorum | string (dec) | "0.334000000000000000" |
| threshold | string (dec) | "0.500000000000000000" |
| veto | string (dec) | "0.334000000000000000" |
| expedited\_threshold | string (time ns) | "0.667000000000000000" |
| expedited\_voting\_period | string (time ns) | "86400000000000" (8600s) |
| expedited\_min\_deposit | array (coins) | `[{"denom":"pol","amount":"50000000"}]` |
| burn\_proposal\_deposit\_prevote | bool | false |
| burn\_vote\_quorum | bool | false |
| burn\_vote\_veto | bool | false |
| min\_initial\_deposit\_ratio | string | "0.1" |
**NOTE**: The governance module contains parameters that are objects unlike other
modules. If only a subset of parameters are desired to be changed, only they need
to be included and not the entire parameter object structure.
## Client
### CLI
A user can query and interact with the `gov` module using the CLI.
#### Query
The `query` commands allow users to query `gov` state.
```bash theme={null}
heimdalld query gov --help
```
##### deposit
The `deposit` command allows users to query a deposit for a given proposal from a given depositor.
```bash theme={null}
heimdalld query gov deposit [proposal-id] [depositer-addr] [flags]
```
Example:
```bash theme={null}
heimdalld query gov deposit 1 0x...
```
Example Output:
```bash theme={null}
amount:
- amount: "100"
denom: pol
depositor: 0x...
proposal_id: "1"
```
##### deposits
The `deposits` command allows users to query all deposits for a given proposal.
```bash theme={null}
heimdalld query gov deposits [proposal-id] [flags]
```
Example:
```bash theme={null}
heimdalld query gov deposits 1
```
Example Output:
```bash theme={null}
deposits:
- amount:
- amount: "100"
denom: pol
depositor: 0x...
proposal_id: "1"
pagination:
next_key: null
total: "0"
```
##### param
The `param` command allows users to query a given parameter for the `gov` module.
```bash theme={null}
heimdalld query gov param [param-type] [flags]
```
Example:
```bash theme={null}
heimdalld query gov param voting
```
Example Output:
```bash theme={null}
voting_period: "172800000000000"
```
##### params
The `params` command allows users to query all parameters for the `gov` module.
```bash theme={null}
heimdalld query gov params [flags]
```
Example:
```bash theme={null}
heimdalld query gov params
```
Example Output:
```bash theme={null}
deposit_params:
max_deposit_period: 86400s
min_deposit:
- amount: "10000000"
denom: pol
params:
expedited_min_deposit:
- amount: "50000000"
denom: pol
expedited_threshold: "0.670000000000000000"
expedited_voting_period: 43200s
max_deposit_period: 86400s
min_deposit:
- amount: "10000000"
denom: pol
min_initial_deposit_ratio: "0.000000000000000000"
proposal_cancel_burn_rate: "0.500000000000000000"
quorum: "0.334000000000000000"
threshold: "0.500000000000000000"
veto_threshold: "0.334000000000000000"
voting_period: 86400s
tally_params:
quorum: "0.334000000000000000"
threshold: "0.500000000000000000"
veto_threshold: "0.334000000000000000"
voting_params:
voting_period: 86400s
```
##### proposal
The `proposal` command allows users to query a given proposal.
```bash theme={null}
heimdalld query gov proposal [proposal-id] [flags]
```
Example:
```bash theme={null}
heimdalld query gov proposal 1
```
Example Output:
```bash theme={null}
deposit_end_time: "2022-03-30T11:50:20.819676256Z"
final_tally_result:
abstain_count: "0"
no_count: "0"
no_with_veto_count: "0"
yes_count: "0"
id: "1"
messages:
- '@type': /cosmos.bank.v1beta1.MsgSend
amount:
- amount: "10"
denom: pol
from_address: 0x...
to_address: 0x...
metadata: AQ==
status: PROPOSAL_STATUS_DEPOSIT_PERIOD
submit_time: "2022-03-28T11:50:20.819676256Z"
total_deposit:
- amount: "10"
denom: pol
voting_end_time: null
voting_start_time: null
```
##### proposals
The `proposals` command allows users to query all proposals with optional filters.
```bash theme={null}
heimdalld query gov proposals [flags]
```
Example:
```bash theme={null}
heimdalld query gov proposals
```
Example Output:
```bash theme={null}
pagination:
next_key: null
total: "0"
proposals:
- deposit_end_time: "2022-03-30T11:50:20.819676256Z"
final_tally_result:
abstain_count: "0"
no_count: "0"
no_with_veto_count: "0"
yes_count: "0"
id: "1"
messages:
- '@type': /cosmos.bank.v1beta1.MsgSend
amount:
- amount: "10"
denom: pol
from_address: 0x...
to_address: 0x...
metadata: AQ==
status: PROPOSAL_STATUS_DEPOSIT_PERIOD
submit_time: "2022-03-28T11:50:20.819676256Z"
total_deposit:
- amount: "10"
denom: pol
voting_end_time: null
voting_start_time: null
- deposit_end_time: "2022-03-30T14:02:41.165025015Z"
final_tally_result:
abstain_count: "0"
no_count: "0"
no_with_veto_count: "0"
yes_count: "0"
id: "2"
messages:
- '@type': /cosmos.bank.v1beta1.MsgSend
amount:
- amount: "10"
denom: pol
from_address: 0x...
to_address: 0x...
metadata: AQ==
status: PROPOSAL_STATUS_DEPOSIT_PERIOD
submit_time: "2022-03-28T14:02:41.165025015Z"
total_deposit:
- amount: "10"
denom: pol
voting_end_time: null
voting_start_time: null
```
##### proposer
The `proposer` command allows users to query the proposer for a given proposal.
```bash theme={null}
heimdalld query gov proposer [proposal-id] [flags]
```
Example:
```bash theme={null}
heimdalld query gov proposer 1
```
Example Output:
```bash theme={null}
proposal_id: "1"
proposer: 0x...
```
##### tally
The `tally` command allows users to query the tally of a given proposal vote.
```bash theme={null}
heimdalld query gov tally [proposal-id] [flags]
```
Example:
```bash theme={null}
heimdalld query gov tally 1
```
Example Output:
```bash theme={null}
abstain: "0"
"no": "0"
no_with_veto: "0"
"yes": "1"
```
##### vote
The `vote` command allows users to query a vote for a given proposal.
```bash theme={null}
heimdalld query gov vote [proposal-id] [voter-addr] [flags]
```
Example:
```bash theme={null}
heimdalld query gov vote 1 0x...
```
Example Output:
```bash theme={null}
option: VOTE_OPTION_YES
options:
- option: VOTE_OPTION_YES
weight: "1.000000000000000000"
proposal_id: "1"
voter: 0x...
```
##### votes
The `votes` command allows users to query all votes for a given proposal.
```bash theme={null}
heimdalld query gov votes [proposal-id] [flags]
```
Example:
```bash theme={null}
heimdalld query gov votes 1
```
Example Output:
```bash theme={null}
pagination:
next_key: null
total: "0"
votes:
- option: VOTE_OPTION_YES
options:
- option: VOTE_OPTION_YES
weight: "1.000000000000000000"
proposal_id: "1"
voter: 0x...
```
#### Transactions
The `tx` commands allow users to interact with the `gov` module.
```bash theme={null}
heimdalld tx gov --help
```
##### deposit
The `deposit` command allows users to deposit tokens for a given proposal.
```bash theme={null}
heimdalld tx gov deposit [proposal-id] [deposit] [flags]
```
Example:
```bash theme={null}
heimdalld tx gov deposit 1 10000000pol --from 0x...
```
##### draft-proposal
The `draft-proposal` command allows users to draft any type of proposal.
The command returns a `draft_proposal.json`, to be used by `submit-proposal` after being completed.
The `draft_metadata.json` is meant to be uploaded to [IPFS](#metadata).
```bash theme={null}
heimdalld tx gov draft-proposal
```
##### submit-proposal
The `submit-proposal` command allows users to submit a governance proposal along with some messages and metadata.
Messages, metadata and deposit are defined in a JSON file.
```bash theme={null}
heimdalld tx gov submit-proposal [path-to-proposal-json] [flags]
```
Example:
```bash theme={null}
heimdalld tx gov submit-proposal /path/to/proposal.json --from 0x...
```
where `proposal.json` contains:
```json theme={null}
{
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "0x...", // The gov module module address
"to_address": "0x...",
"amount":[{"denom": "pol","amount": "10"}]
}
],
"metadata": "AQ==",
"deposit": "10pol",
"title": "Proposal Title",
"summary": "Proposal Summary"
}
```
:::note
By default the metadata, summary and title are both limited by 255 characters, this can be overridden by the application developer.
:::
:::tip
When metadata is not specified, the title is limited to 255 characters and the summary 40x the title length.
:::
##### submit-legacy-proposal
The `submit-legacy-proposal` command allows users to submit a governance legacy proposal along with an initial deposit.
```bash theme={null}
heimdalld tx gov submit-legacy-proposal [command] [flags]
```
Example:
```bash theme={null}
heimdalld tx gov submit-legacy-proposal --title="Test Proposal" --description="testing" --type="Text" --deposit="100000000pol" --from 0x...
```
Example (`param-change`):
```bash theme={null}
heimdalld tx gov submit-legacy-proposal param-change proposal.json --from 0x...
```
```json theme={null}
{
"title": "Test Proposal",
"description": "testing, testing, 1, 2, 3",
"changes": [
{
"subspace": "staking",
"key": "MaxValidators",
"value": 100
}
],
"deposit": "10000000pol"
}
```
#### cancel-proposal
Once proposal is canceled, from the deposits of proposal `deposits * proposal_cancel_ratio` will be sent to `ProposalCancelDest` address , if `ProposalCancelDest` is empty then deposits will be refunded. The `remaining deposits` will be sent to depositers.
```bash theme={null}
heimdalld tx gov cancel-proposal [proposal-id] [flags]
```
Example:
```bash theme={null}
heimdalld tx gov cancel-proposal 1 --from 0x...
```
##### vote
The `vote` command allows users to submit a vote for a given governance proposal.
```bash theme={null}
heimdalld tx gov vote [command] [flags]
```
Example:
```bash theme={null}
heimdalld tx gov vote 1 yes --from 0x...
```
##### weighted-vote
*Currently not supported in Heimdall.*
The `weighted-vote` command allows users to submit a weighted vote for a given governance proposal.
```bash theme={null}
heimdalld tx gov weighted-vote [proposal-id] [weighted-options] [flags]
```
Example:
```bash theme={null}
heimdalld tx gov weighted-vote 1 yes=0.5,no=0.5 --from 0x...
```
### gRPC
A user can query the `gov` module using gRPC endpoints.
#### Proposal
The `Proposal` endpoint allows users to query a given proposal.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/Proposal
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1beta1.Query/Proposal
```
Example Output:
```bash theme={null}
{
"proposal": {
"proposalId": "1",
"content": {"@type":"/cosmos.gov.v1beta1.TextProposal","description":"testing, testing, 1, 2, 3","title":"Test Proposal"},
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"finalTallyResult": {
"yes": "0",
"abstain": "0",
"no": "0",
"noWithVeto": "0"
},
"submitTime": "2021-09-16T19:40:08.712440474Z",
"depositEndTime": "2021-09-18T19:40:08.712440474Z",
"totalDeposit": [
{
"denom": "pol",
"amount": "10000000"
}
],
"votingStartTime": "2021-09-16T19:40:08.712440474Z",
"votingEndTime": "2021-09-18T19:40:08.712440474Z",
"title": "Test Proposal",
"summary": "testing, testing, 1, 2, 3"
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/Proposal
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1.Query/Proposal
```
Example Output:
```bash theme={null}
{
"proposal": {
"id": "1",
"messages": [
{"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"pol","amount":"10"}],"fromAddress":"0x...","toAddress":"0x..."}
],
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"finalTallyResult": {
"yesCount": "0",
"abstainCount": "0",
"noCount": "0",
"noWithVetoCount": "0"
},
"submitTime": "2022-03-28T11:50:20.819676256Z",
"depositEndTime": "2022-03-30T11:50:20.819676256Z",
"totalDeposit": [
{
"denom": "pol",
"amount": "10000000"
}
],
"votingStartTime": "2022-03-28T14:25:26.644857113Z",
"votingEndTime": "2022-03-30T14:25:26.644857113Z",
"metadata": "AQ==",
"title": "Test Proposal",
"summary": "testing, testing, 1, 2, 3"
}
}
```
#### Proposals
The `Proposals` endpoint allows users to query all proposals with optional filters.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/Proposals
```
Example:
```bash theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.gov.v1beta1.Query/Proposals
```
Example Output:
```bash theme={null}
{
"proposals": [
{
"proposalId": "1",
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"finalTallyResult": {
"yes": "0",
"abstain": "0",
"no": "0",
"noWithVeto": "0"
},
"submitTime": "2022-03-28T11:50:20.819676256Z",
"depositEndTime": "2022-03-30T11:50:20.819676256Z",
"totalDeposit": [
{
"denom": "pol",
"amount": "10000000010"
}
],
"votingStartTime": "2022-03-28T14:25:26.644857113Z",
"votingEndTime": "2022-03-30T14:25:26.644857113Z"
},
{
"proposalId": "2",
"status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
"finalTallyResult": {
"yes": "0",
"abstain": "0",
"no": "0",
"noWithVeto": "0"
},
"submitTime": "2022-03-28T14:02:41.165025015Z",
"depositEndTime": "2022-03-30T14:02:41.165025015Z",
"totalDeposit": [
{
"denom": "pol",
"amount": "10"
}
],
"votingStartTime": "0001-01-01T00:00:00Z",
"votingEndTime": "0001-01-01T00:00:00Z"
}
],
"pagination": {
"total": "2"
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/Proposals
```
Example:
```bash theme={null}
grpcurl -plaintext \
localhost:9090 \
cosmos.gov.v1.Query/Proposals
```
Example Output:
```bash theme={null}
{
"proposals": [
{
"id": "1",
"messages": [
{"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"pol","amount":"10"}],"fromAddress":"0x...","toAddress":"0x..."}
],
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"finalTallyResult": {
"yesCount": "0",
"abstainCount": "0",
"noCount": "0",
"noWithVetoCount": "0"
},
"submitTime": "2022-03-28T11:50:20.819676256Z",
"depositEndTime": "2022-03-30T11:50:20.819676256Z",
"totalDeposit": [
{
"denom": "pol",
"amount": "10000000010"
}
],
"votingStartTime": "2022-03-28T14:25:26.644857113Z",
"votingEndTime": "2022-03-30T14:25:26.644857113Z",
"metadata": "AQ==",
"title": "Proposal Title",
"summary": "Proposal Summary"
},
{
"id": "2",
"messages": [
{"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"pol","amount":"10"}],"fromAddress":"0x...","toAddress":"0x..."}
],
"status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
"finalTallyResult": {
"yesCount": "0",
"abstainCount": "0",
"noCount": "0",
"noWithVetoCount": "0"
},
"submitTime": "2022-03-28T14:02:41.165025015Z",
"depositEndTime": "2022-03-30T14:02:41.165025015Z",
"totalDeposit": [
{
"denom": "pol",
"amount": "10"
}
],
"metadata": "AQ==",
"title": "Proposal Title",
"summary": "Proposal Summary"
}
],
"pagination": {
"total": "2"
}
}
```
#### Vote
The `Vote` endpoint allows users to query a vote for a given proposal.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/Vote
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1","voter":"0x..."}' \
localhost:9090 \
cosmos.gov.v1beta1.Query/Vote
```
Example Output:
```bash theme={null}
{
"vote": {
"proposalId": "1",
"voter": "0x...",
"option": "VOTE_OPTION_YES",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1000000000000000000"
}
]
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/Vote
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1","voter":"0x..."}' \
localhost:9090 \
cosmos.gov.v1.Query/Vote
```
Example Output:
```bash theme={null}
{
"vote": {
"proposalId": "1",
"voter": "0x...",
"option": "VOTE_OPTION_YES",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1.000000000000000000"
}
]
}
}
```
#### Votes
The `Votes` endpoint allows users to query all votes for a given proposal.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/Votes
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1beta1.Query/Votes
```
Example Output:
```bash theme={null}
{
"votes": [
{
"proposalId": "1",
"voter": "0x...",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1000000000000000000"
}
]
}
],
"pagination": {
"total": "1"
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/Votes
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1.Query/Votes
```
Example Output:
```bash theme={null}
{
"votes": [
{
"proposalId": "1",
"voter": "0x...",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1.000000000000000000"
}
]
}
],
"pagination": {
"total": "1"
}
}
```
#### Params
The `Params` endpoint allows users to query all parameters for the `gov` module.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/Params
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"params_type":"voting"}' \
localhost:9090 \
cosmos.gov.v1beta1.Query/Params
```
Example Output:
```bash theme={null}
{
"votingParams": {
"votingPeriod": "86400s"
},
"depositParams": {
"maxDepositPeriod": "0s"
},
"tallyParams": {
"quorum": "MA==",
"threshold": "MA==",
"vetoThreshold": "MA=="
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/Params
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"params_type":"voting"}' \
localhost:9090 \
cosmos.gov.v1.Query/Params
```
Example Output:
```bash theme={null}
{
"votingParams": {
"votingPeriod": "86400s"
}
}
```
#### Deposit
The `Deposit` endpoint allows users to query a deposit for a given proposal from a given depositor.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/Deposit
```
Example:
```bash theme={null}
grpcurl -plaintext \
'{"proposal_id":"1","depositor":"0x..."}' \
localhost:9090 \
cosmos.gov.v1beta1.Query/Deposit
```
Example Output:
```bash theme={null}
{
"deposit": {
"proposalId": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/Deposit
```
Example:
```bash theme={null}
grpcurl -plaintext \
'{"proposal_id":"1","depositor":"0x..."}' \
localhost:9090 \
cosmos.gov.v1.Query/Deposit
```
Example Output:
```bash theme={null}
{
"deposit": {
"proposalId": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
}
```
#### deposits
The `Deposits` endpoint allows users to query all deposits for a given proposal.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/Deposits
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1beta1.Query/Deposits
```
Example Output:
```bash theme={null}
{
"deposits": [
{
"proposalId": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
],
"pagination": {
"total": "1"
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/Deposits
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1.Query/Deposits
```
Example Output:
```bash theme={null}
{
"deposits": [
{
"proposalId": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
],
"pagination": {
"total": "1"
}
}
```
#### TallyResult
The `TallyResult` endpoint allows users to query the tally of a given proposal.
Using legacy v1beta1:
```bash theme={null}
cosmos.gov.v1beta1.Query/TallyResult
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1beta1.Query/TallyResult
```
Example Output:
```bash theme={null}
{
"tally": {
"yes": "1000000",
"abstain": "0",
"no": "0",
"noWithVeto": "0"
}
}
```
Using v1:
```bash theme={null}
cosmos.gov.v1.Query/TallyResult
```
Example:
```bash theme={null}
grpcurl -plaintext \
-d '{"proposal_id":"1"}' \
localhost:9090 \
cosmos.gov.v1.Query/TallyResult
```
Example Output:
```bash theme={null}
{
"tally": {
"yes": "1000000",
"abstain": "0",
"no": "0",
"noWithVeto": "0"
}
}
```
### REST
A user can query the `gov` module using REST endpoints.
#### proposal
The `proposals` endpoint allows users to query a given proposal.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/proposals/{proposal_id}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/proposals/1
```
Example Output:
```bash theme={null}
{
"proposal": {
"proposal_id": "1",
"content": null,
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"final_tally_result": {
"yes": "0",
"abstain": "0",
"no": "0",
"no_with_veto": "0"
},
"submit_time": "2022-03-28T11:50:20.819676256Z",
"deposit_end_time": "2022-03-30T11:50:20.819676256Z",
"total_deposit": [
{
"denom": "pol",
"amount": "10000000010"
}
],
"voting_start_time": "2022-03-28T14:25:26.644857113Z",
"voting_end_time": "2022-03-30T14:25:26.644857113Z"
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/proposals/{proposal_id}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/proposals/1
```
Example Output:
```bash theme={null}
{
"proposal": {
"id": "1",
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "0x...",
"to_address": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10"
}
]
}
],
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"final_tally_result": {
"yes_count": "0",
"abstain_count": "0",
"no_count": "0",
"no_with_veto_count": "0"
},
"submit_time": "2022-03-28T11:50:20.819676256Z",
"deposit_end_time": "2022-03-30T11:50:20.819676256Z",
"total_deposit": [
{
"denom": "pol",
"amount": "10000000"
}
],
"voting_start_time": "2022-03-28T14:25:26.644857113Z",
"voting_end_time": "2022-03-30T14:25:26.644857113Z",
"metadata": "AQ==",
"title": "Proposal Title",
"summary": "Proposal Summary"
}
}
```
#### proposals
The `proposals` endpoint also allows users to query all proposals with optional filters.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/proposals
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/proposals
```
Example Output:
```bash theme={null}
{
"proposals": [
{
"proposal_id": "1",
"content": null,
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"final_tally_result": {
"yes": "0",
"abstain": "0",
"no": "0",
"no_with_veto": "0"
},
"submit_time": "2022-03-28T11:50:20.819676256Z",
"deposit_end_time": "2022-03-30T11:50:20.819676256Z",
"total_deposit": [
{
"denom": "pol",
"amount": "10000000"
}
],
"voting_start_time": "2022-03-28T14:25:26.644857113Z",
"voting_end_time": "2022-03-30T14:25:26.644857113Z"
},
{
"proposal_id": "2",
"content": null,
"status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
"final_tally_result": {
"yes": "0",
"abstain": "0",
"no": "0",
"no_with_veto": "0"
},
"submit_time": "2022-03-28T14:02:41.165025015Z",
"deposit_end_time": "2022-03-30T14:02:41.165025015Z",
"total_deposit": [
{
"denom": "pol",
"amount": "10"
}
],
"voting_start_time": "0001-01-01T00:00:00Z",
"voting_end_time": "0001-01-01T00:00:00Z"
}
],
"pagination": {
"next_key": null,
"total": "2"
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/proposals
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/proposals
```
Example Output:
```bash theme={null}
{
"proposals": [
{
"id": "1",
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "0x...",
"to_address": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10"
}
]
}
],
"status": "PROPOSAL_STATUS_VOTING_PERIOD",
"final_tally_result": {
"yes_count": "0",
"abstain_count": "0",
"no_count": "0",
"no_with_veto_count": "0"
},
"submit_time": "2022-03-28T11:50:20.819676256Z",
"deposit_end_time": "2022-03-30T11:50:20.819676256Z",
"total_deposit": [
{
"denom": "pol",
"amount": "10000000010"
}
],
"voting_start_time": "2022-03-28T14:25:26.644857113Z",
"voting_end_time": "2022-03-30T14:25:26.644857113Z",
"metadata": "AQ==",
"title": "Proposal Title",
"summary": "Proposal Summary"
},
{
"id": "2",
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "0x...",
"to_address": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10"
}
]
}
],
"status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
"final_tally_result": {
"yes_count": "0",
"abstain_count": "0",
"no_count": "0",
"no_with_veto_count": "0"
},
"submit_time": "2022-03-28T14:02:41.165025015Z",
"deposit_end_time": "2022-03-30T14:02:41.165025015Z",
"total_deposit": [
{
"denom": "pol",
"amount": "10"
}
],
"voting_start_time": null,
"voting_end_time": null,
"metadata": "AQ==",
"title": "Proposal Title",
"summary": "Proposal Summary"
}
],
"pagination": {
"next_key": null,
"total": "2"
}
}
```
#### voter vote
The `votes` endpoint allows users to query a vote for a given proposal.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes/0x...
```
Example Output:
```bash theme={null}
{
"vote": {
"proposal_id": "1",
"voter": "0x...",
"option": "VOTE_OPTION_YES",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1.000000000000000000"
}
]
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/proposals/1/votes/0x...
```
Example Output:
```bash theme={null}
{
"vote": {
"proposal_id": "1",
"voter": "0x...",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1.000000000000000000"
}
],
"metadata": ""
}
}
```
#### votes
The `votes` endpoint allows users to query all votes for a given proposal.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/proposals/{proposal_id}/votes
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes
```
Example Output:
```bash theme={null}
{
"votes": [
{
"proposal_id": "1",
"voter": "0x...",
"option": "VOTE_OPTION_YES",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1.000000000000000000"
}
]
}
],
"pagination": {
"next_key": null,
"total": "1"
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/proposals/{proposal_id}/votes
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/proposals/1/votes
```
Example Output:
```bash theme={null}
{
"votes": [
{
"proposal_id": "1",
"voter": "0x...",
"options": [
{
"option": "VOTE_OPTION_YES",
"weight": "1.000000000000000000"
}
],
"metadata": ""
}
],
"pagination": {
"next_key": null,
"total": "1"
}
}
```
#### params
The `params` endpoint allows users to query all parameters for the `gov` module.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/params/{params_type}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/params/voting
```
Example Output:
```bash theme={null}
{
"voting_params": {
"voting_period": "86400s"
},
"deposit_params": {
"min_deposit": [
],
"max_deposit_period": "0s"
},
"tally_params": {
"quorum": "0.000000000000000000",
"threshold": "0.000000000000000000",
"veto_threshold": "0.000000000000000000"
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/params/{params_type}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/params/voting
```
Example Output:
```bash theme={null}
{
"voting_params": {
"voting_period": "86400s"
},
"deposit_params": {
"min_deposit": [
],
"max_deposit_period": "0s"
},
"tally_params": {
"quorum": "0.000000000000000000",
"threshold": "0.000000000000000000",
"veto_threshold": "0.000000000000000000"
}
}
```
#### deposits
The `deposits` endpoint allows users to query a deposit for a given proposal from a given depositor.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits/{depositor}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits/0x...
```
Example Output:
```bash theme={null}
{
"deposit": {
"proposal_id": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/proposals/{proposal_id}/deposits/{depositor}
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/proposals/1/deposits/0x...
```
Example Output:
```bash theme={null}
{
"deposit": {
"proposal_id": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
}
```
#### proposal deposits
The `deposits` endpoint allows users to query all deposits for a given proposal.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits
```
Example Output:
```bash theme={null}
{
"deposits": [
{
"proposal_id": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
],
"pagination": {
"next_key": null,
"total": "1"
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/proposals/{proposal_id}/deposits
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/proposals/1/deposits
```
Example Output:
```bash theme={null}
{
"deposits": [
{
"proposal_id": "1",
"depositor": "0x...",
"amount": [
{
"denom": "pol",
"amount": "10000000"
}
]
}
],
"pagination": {
"next_key": null,
"total": "1"
}
}
```
#### tally
The `tally` endpoint allows users to query the tally of a given proposal.
Using legacy v1beta1:
```bash theme={null}
/cosmos/gov/v1beta1/proposals/{proposal_id}/tally
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1beta1/proposals/1/tally
```
Example Output:
```bash theme={null}
{
"tally": {
"yes": "1000000",
"abstain": "0",
"no": "0",
"no_with_veto": "0"
}
}
```
Using v1:
```bash theme={null}
/cosmos/gov/v1/proposals/{proposal_id}/tally
```
Example:
```bash theme={null}
curl localhost:1317/cosmos/gov/v1/proposals/1/tally
```
Example Output:
```bash theme={null}
{
"tally": {
"yes": "1000000",
"abstain": "0",
"no": "0",
"no_with_veto": "0"
}
}
```
## Metadata
The gov module has two locations for metadata where users can provide further context about the onchain actions they are taking. By default all metadata fields have a 255 character length field where metadata can be stored in json format, either onchain or off-chain depending on the amount of data required. Here we provide a recommendation for the json structure and where the data should be stored. There are two important factors in making these recommendations. First, that the gov and group modules are consistent with one another, note the number of proposals made by all groups may be quite large. Second, that client applications such as block explorers and governance interfaces have confidence in the consistency of metadata structure accross chains.
### Proposal
Location: off-chain as json object stored on IPFS
```json theme={null}
{
"title": "",
"authors": [""],
"summary": "",
"details": "",
"proposal_forum_url": "",
"vote_option_context": "",
}
```
:::note
The `authors` field is an array of strings, this is to allow for multiple authors to be listed in the metadata.
In v0.46, the `authors` field is a comma-separated string. Frontends are encouraged to support both formats for backwards compatibility.
:::
### Vote
Location: onchain as json within 255 character limit
```json theme={null}
{
"justification": "",
}
```
## Future Improvements
These improvements are being discussed in the upstream cosmos-sdk, hence not really part of any heimdall future plan.
The current documentation only describes the minimum viable product for the
governance module. Future improvements may include:
* **`BountyProposals`:** If accepted, a `BountyProposal` creates an open
bounty. The `BountyProposal` specifies how many tokens will be given upon
completion. These POL will be taken from the `reserve pool`. After a
`BountyProposal` is accepted by governance, anybody can submit a
`SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a
`BountyProposal` is accepted, the corresponding funds in the `reserve pool`
are locked so that payment can always be honored. In order to link a
`SoftwareUpgradeProposal` to an open bounty, the submitter of the
`SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute.
If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by
governance, the funds that were reserved are automatically transferred to the
submitter.
* **Complex delegation:** Delegators could choose other representatives than
their validators. Ultimately, the chain of representatives would always end
up to a validator, but delegators could inherit the vote of their chosen
representative before they inherit the vote of their validator. In other
words, they would only inherit the vote of their validator if their other
appointed representative did not vote.
* **Better process for proposal review:** There would be two parts to
`proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to
reward third party auditors.
# Heimdall Bor module
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/heimdall-bor-module
How the Heimdall bor module manages span intervals, selects block producers for the Bor chain, and coordinates span proposals through the ABCI++ side-transaction flow.
Heimdall's `bor` module is responsible for managing span intervals and coordinating interactions with the Bor chain. Specifically, it determines when a new span can be proposed on Heimdall based on the current block number and the current span.
## Preliminary terminology
* A `side-transaction` is a normal heimdall transaction but the data with which the message is composed needs to be voted on by the validators since the data is obscure to the consensus protocol itself, and it has no way of validating the data's correctness.
* A `sprint` comprises of 16 bor blocks (configured in [bor](https://github.com/0xPolygon/launch/blob/fe86ba6cd16e5c36067a5ae49c0bad62ce8b1c3f/mainnet-v1/sentry/validator/bor/genesis.json#L26C18-L28)).
* A `span` comprises 400 sprints in bor (check heimdall's bor [params](https://heimdall-api.polygon.technology/bor/params) endpoint).
## Overview
The validators on the bor chain produce blocks in sprints and spans. Hence, it is imperative for the protocol to formalize the validators who will be producers in a range of blocks (`span`). The `bor` module in heimdall facilitates this by pseudo-randomly selecting validators who will producing blocks (producers) from the current validator set. The bor chain fetches and persists this information before the next span begins. `bor` module is a crucial component in heimdall since the PoS chain "liveness" depends on it.
## How it works
A `Span` is defined by the data structure:
```protobuf theme={null}
message Span {
uint64 id = 1 [ (amino.dont_omitempty) = true ];
uint64 start_block = 2 [ (amino.dont_omitempty) = true ];
uint64 end_block = 3 [ (amino.dont_omitempty) = true ];
heimdallv2.stake.ValidatorSet validator_set = 4
[ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ];
repeated heimdallv2.stake.Validator selected_producers = 5
[ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ];
string bor_chain_id = 6 [ (amino.dont_omitempty) = true ];
}
```
where
* `id` means the id of the span, calculated by monotonically incrementing the id of the previous span.
* `start_block` corresponds to the block in bor from which the given span would begin.
* `end_block` corresponds to the block in bor at which the given span would conclude.
* `validator_set` defines the set of active validators.
* `selected_producers` are the validators selected to produce blocks in bor from the validator set.
* `bor_chain_id` corresponds to bor chain ID.
A validator on heimdall can construct a span proposal message:
```protobuf theme={null}
message MsgProposeSpan {
option (amino.name) = "heimdallv2/bor/MsgProposeSpan";
option (cosmos.msg.v1.signer) = "proposer";
uint64 span_id = 1;
string proposer = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
uint64 start_block = 3;
uint64 end_block = 4;
string chain_id = 5;
bytes seed = 6;
string seed_author = 7 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
}
```
The message is generally constructed and broadcast by the validator's bridge process periodically, but the CLI can also be used to do the same manually (see [below](#proposing-a-span-manually)). Upon broadcasting the message, it is initially checked by `ProposeSpan` handler for basic sanity (verify whether the proposed span is in continuity, appropriate span duration, correct chain ID, etc.). Since this is a side-transaction, the validators then vote on the data present in `MsgProposeSpan` on the basis of its correctness. All these checks are done in `SideHandleMsgSpan` (verifying `seed`, span continuity, etc.) and if correct, the validator would vote `YES`.
Finally, if there are 2/3+ `YES` votes, the `PostHandleMsgSpan` persists the proposed span in the state via the keeper :
```go theme={null}
// freeze for new span
err = s.k.FreezeSet(ctx, msg.SpanId, msg.StartBlock, msg.EndBlock, msg.ChainId, common.Hash(msg.Seed))
if err != nil {
logger.Error("unable to freeze validator set for span", "span id", msg.SpanId, "error", err)
return err
}
```
`FreezeSet` internally invokes `SelectNextProducers`, which pseudo-randomly picks producers from the validator set, leaning more towards validators with higher voting power based on stake:
```go theme={null}
// select next producers
newProducers, err := k.SelectNextProducers(ctx, seed, prevVals)
if err != nil {
return err
}
```
and then initializes and stores the span:
```go theme={null}
// generate new span
newSpan := &types.Span{
Id: id,
StartBlock: startBlock,
EndBlock: endBlock,
ValidatorSet: valSet,
SelectedProducers: newProducers,
BorChainId: borChainID,
}
logger.Info("Freezing new span", "id", id, "span", newSpan)
return k.AddNewSpan(ctx, newSpan)
```
### Proposing a span manually
A validator can use the CLI to propose a span:
```bash theme={null}
heimdalld tx bor propose-span --proposer --start-block --span-id --bor-chain-id
```
## Query commands
One can run the following query commands from the bor module:
* `span` - Query the span corresponding to the given span id.
* `span-list` - Fetch span list.
* `latest-span` - Query the latest span.
* `next-span-seed` - Query the seed for the next span.
* `next-span` - Query the next span.
* `params` - Fetch the parameters associated with the bor module.
### CLI commands
```bash theme={null}
heimdalld query bor span-by-id
```
```bash theme={null}
heimdalld query bor span-list
```
```bash theme={null}
heimdalld query bor latest-span
```
```bash theme={null}
heimdalld query bor next-span-seed [id]
```
```bash theme={null}
heimdalld query bor next-span
```
```bash theme={null}
heimdalld query bor params
```
### GRPC Endpoints
The endpoints and the params are defined in the [bor/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/bor/query.proto) file. Please refer to them for more information about the optional params.
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.bor.Query/GetSpanList
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.bor.Query/GetLatestSpan
```
```bash theme={null}
grpcurl -plaintext -d '{"id": <>}' localhost:9090 heimdallv2.bor.Query/GetNextSpanSeed
```
```bash theme={null}
grpcurl -plaintext -d '{"span_id": <>, "start_block": <>, "bor_chain_id": "<>"}' localhost:9090 heimdallv2.bor.Query/GetNextSpan
```
```bash theme={null}
grpcurl -plaintext -d '{"id": "<>"}' localhost:9090 heimdallv2.bor.Query/GetSpanById
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.bor.Query/GetBorParams
```
### REST endpoints
The endpoints and the params are defined in the [bor/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/bor/query.proto) file. Please refer to them for more information about the optional params.
```bash theme={null}
curl localhost:1317/bor/spans/list
```
```bash theme={null}
curl localhost:1317/bor/spans/latest
```
```bash theme={null}
curl localhost:1317/bor/spans/seed/
```
```bash theme={null}
curl "localhost:1317/bor/spans/prepare?span_id=&start_block=&bor_chain_id="
```
```bash theme={null}
curl localhost:1317/bor/spans/
```
```bash theme={null}
curl localhost:1317/bor/params
```
# Introduction
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/introduction
What Heimdall-v2 is, what it does in the Polygon PoS architecture, and how it differs from the original Heimdall.
Heimdall-v2 is the consensus client at the core of the Polygon PoS network. It is a complete rewrite of the original Heimdall, built on a fork of [Cosmos SDK](https://github.com/0xPolygon/cosmos-sdk) (`v0.50.13`) and a fork of [CometBFT](https://github.com/0xPolygon/cometbft/) (`v0.38.17`).
Heimdall-v2 performs the following functions:
* Manages validators and their stake.
* Handles block producer selection for the Bor layer.
* Facilitates span management.
* Orchestrates the state sync mechanism between Ethereum and Polygon PoS.
* Handles checkpoints: periodic Merkle root submissions of Bor block data to Ethereum.
* Handles milestones: fast deterministic finality within 2 to 5 seconds, using vote extensions.
## Modules
Heimdall-v2 uses modified versions of some Cosmos SDK modules and a set of fully custom modules:
* **Modified Cosmos SDK modules**: `auth`, `bank`, `gov`
* **Custom modules**: `bor`, `chainmanager`, `checkpoint`, `clerk`, `milestone`, `stake`, `topup`
## GitHub repository
For setup instructions and configuration details, see the [Heimdall-v2 README](https://github.com/0xPolygon/heimdall-v2/blob/develop/README.md).
# Milestones
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/milestones
How Heimdall milestones provide deterministic finality on Polygon PoS without waiting for checkpoints, using CometBFT vote extensions and 2/3 majority agreement.
## Overview
This module enables deterministic finality using Polygon PoS’s dual client architecture.\
This is done using a hybrid system that uses CometBFT layer consensus,\
along with an additional fork choice rule within the execution layer.
With the introduction of milestones, finality is deterministic even before a checkpoint is submitted to L1.\
After a certain number of blocks (minimum 1), a milestone is proposed and voted by Heimdall.\
Once 2/3+ of the network agrees, the milestone is finalized, and all transactions up to that milestone are considered final, with no chance of reorganization.
## Flow
Milestones are a lightweight alternative to checkpoints in Heimdall, used to finalize blocks more efficiently.\
With the introduction of milestones, finality is deterministic even before a checkpoint is submitted to L1.\
Unlike the original transaction-based design in Heimdall-v1, the current design operates without transactions, relying entirely on ABCI++ flow and validator vote extensions.\
Each validator proposes a milestone independently. Milestones are proposed as a series of recent, up to 10 block hashes, and a majority (2/3 of voting power) agreement on a consecutive sequence of these hashes is required to finalize a milestone.\
The system tolerates duplication to increase reliability and includes logic for resolving forks and ensuring milestone continuity.
### Milestone Proposals and Duplication
Validators independently propose a sequence of block hashes, at most `MaxMilestonePropositionLength` starting from the last finalized milestone:
```protobuf theme={null}
message MilestoneProposition {
option (gogoproto.equal) = true;
option (gogoproto.goproto_getters) = true;
repeated bytes block_hashes = 1 [ (amino.dont_omitempty) = true ];
uint64 start_block_number = 2 [ (amino.dont_omitempty) = true ];
}
```
Proposals are handled in the `ExtendVoteHandler`, executed at each block.\
A milestone proposed in block `N` is finalized in block `N+1` (or later), introducing acceptable duplication of proposed milestones to improve reliability.\
This duplication ensures that even if a milestone isn’t finalized in `N+1`, it may succeed in `N+2` or later.\
In cases of failed milestone propositions, the node still participates in the consensus.
### Proposed Milestone validation checks
Proposed milestone validation is performed in N block with `ValidateMilestoneProposition` function in`ExtendVoteHandler` and in `VerifyVoteExtensionHandler`:
* Length is validated: the milestone proposal, if created should not contain more block hashes than `MaxMilestonePropositionLength`;
* Each block hash length is validated to be of the appropriate length.
### Majority Determination in the following block
Vote extensions from other validators are collected and unmarshalled.\
Duplicate vote extensions from the same validator are ignored.\
The algorithm uses data structures keyed by `(block_number, block_hash)` to handle forks (same block number may have different hashes), so that fork-resilience is achieved by:
* Separating vote data by hash and block number.
* Ensuring the finalized milestones continue from the last one with no gaps.
The core algorithm looks for:
* The longest consecutive sequence of block hashes.
* Supported by >= 2/3 of the total voting power.
### Milestones Validation and Finalization
Once a consensus over the milestone is reached, the majority milestone is validated with `ValidateMilestoneProposition` checks again for integrity in `PreBlocker`.\
If the validation passes, the milestone is persisted.
## Messages
### Milestone
`Milestone` defines a message for submitting a milestone
```protobuf theme={null}
message Milestone {
option (gogoproto.equal) = true;
option (gogoproto.goproto_getters) = true;
string proposer = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 start_block = 2 [ (amino.dont_omitempty) = true ];
uint64 end_block = 3 [ (amino.dont_omitempty) = true ];
bytes hash = 4 [ (amino.dont_omitempty) = true ];
string bor_chain_id = 5 [ (amino.dont_omitempty) = true ];
string milestone_id = 6 [ (amino.dont_omitempty) = true ];
uint64 timestamp = 7 [ (amino.dont_omitempty) = true ];
}
```
## Interact with the Node
### Tx Commands
#### Send Milestone Transaction
```bash theme={null}
heimdalld tx milestone milestone [proposer] [startBlock] [endBlock] [hash] [borChainId] [milestoneId]
```
### CLI Query Commands
One can run the following query commands from the milestone module:
* `get-params` - Get milestone params
* `get-count` - Get milestone count
* `get-latest-milestone` - Get latest milestone
* `get-milestone-by-number` - Get the milestone by number
* `get-milestone-proposer` - Get the milestone proposer
* `get-latest-no-ack-milestone` - Get the latest no ack milestone
* `get-no-ack-milestone-by-id` - Get the no ack milestone by id
```bash theme={null}
heimdalld query milestone get-params
```
```bash theme={null}
heimdalld query milestone get-count
```
```bash theme={null}
heimdalld query milestone get-latest-milestone
```
```bash theme={null}
heimdalld query milestone get-milestone-by-number
```
```bash theme={null}
heimdalld query milestone get-milestone-proposer
```
```bash theme={null}
heimdalld query milestone get-latest-no-ack-milestone
```
```bash theme={null}
heimdalld query milestone get-no-ack-milestone-by-id
```
### GRPC Endpoints
The endpoints and the params are defined in the [milestone/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/milestone/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.milestone.Query/GetMilestoneParams
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.milestone.Query/GetMilestoneCount
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.milestone.Query/GetLatestMilestone
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.milestone.Query/GetMilestoneByNumber
```
### REST APIs
The endpoints and the params are defined in the [milestone/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/milestone/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
curl localhost:1317/milestones/params
```
```bash theme={null}
curl localhost:1317/milestones/count
```
```bash theme={null}
curl localhost:1317/milestones/latest
```
```bash theme={null}
curl localhost:1317/milestones/{number}
```
# Staking
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/staking
How the Heimdall stake module manages validator state, synchronizes Ethereum stake events to Heimdall, and updates the validator set.
## Overview
This module manages the validators' related transactions and state for Heimdall.\
Validators stake their tokens on the Ethereum chain
and send the transactions on Heimdall using the necessary parameters to acknowledge the Ethereum stake change.\
Once the majority of the validators agree on the change on the stake,
this module saves the validator information on Heimdall state.
## Flow
The x/stake module manages validator-related transactions and validator set management for Heimdall.
Validators stake their tokens on the Ethereum chain to participate in consensus. To synchronize these changes with Heimdall, the bridge processor broadcasts the corresponding transaction for an Ethereum-emitted event, choosing from one of the following messages each with the necessary parameters:
* `MsgValidatorJoin`: This message is triggered when a new validator joins the system by interacting with `StakingManager.sol` on Ethereum. The action emits a `Staked` event to recognize and process the validator’s participation.
* `MsgStakeUpdate`: Used to handle stake modifications, this message is sent when a validator re-stakes or receives additional delegation. Both scenarios trigger a `StakeUpdate` event on Ethereum, ensuring Heimdall accurately updates the validator’s stake information.
* `MsgValidatorExit`: When a validator decides to exit, they initiate the process on Ethereum, leading to the emission of a `UnstakeInit` event. This message ensures that Heimdall records the validator’s departure accordingly.
* `MsgSignerUpdate`: This message is responsible for processing changes to a validator’s signer key. When a validator updates their signer key on Ethereum, it emits a `SignerUpdate` event, prompting Heimdall to reflect the new signer key in its records.
Each of these transactions in Heimdall follows the same processing mechanisms, leveraging ABCI++ phases.
During the `PreCommit` phase, side transaction handlers are triggered, and a vote is injected after validating the Ethereum-emitted event and ensuring its alignment with the data in the processed message.
Once a majority of validators confirm that the action described in the message has occurred on Ethereum, the x/stake module updates the validator’s state in Heimdall during the `FinalizeBlock`’s `PreBlocker` execution.
### Replay Prevention Mechanism
Heimdall employs a replay prevention mechanism in the post-tx handler functions to ensure that validator update messages derived from Ethereum events are not processed multiple times.
This mechanism prevents replay attacks by assigning a unique sequence number to each transaction and verifying whether it has already been processed.\
The sequence number is constructed using the Ethereum block number and log index, following the formula:
* `sequence = (block number × DefaultLogIndexUnit) + log index`
where:
* `msg.BlockNumber` represents the Ethereum block where the event was emitted.
* `msg.LogIndex` is the position of the log entry within that block.
* `DefaultLogIndexUnit` ensures uniqueness when combining block numbers and log indexes.
Before processing a transaction, Heimdall checks its stake keeper to determine if the sequence number has been recorded.
If the sequence is found, the transaction is rejected as a duplicate. Once the post-tx handler completes successfully, the sequence is stored, ensuring that any future message with the same sequence is recognized and ignored.
This approach guarantees that Heimdall only processes each valid Ethereum signer update once, preventing unintended state changes due to replayed messages.
### Updating the Validator Set
In the x/stake `EndBlocker`, Heimdall updates the validator set (through the `ApplyAndReturnValidatorSetUpdates`function), ensuring consensus reflects the latest validator changes.
Before any updates, the current block’s validator set is stored as the previous block’s set. The system retrieves all existing validators, the current validator set, and the acknowledgment count from the x/checkpoint state.
Using `GetUpdatedValidators`, a list of validators that require updates (`setUpdates`) is identified and applied through `UpdateWithChangeSet`, storing the new set under `CurrentValidatorSetKey`.
To maintain fair block proposer selection, Heimdall implements a proposer priority system, ensuring all validators have a fair chance to propose new blocks.
The proposer priority is dynamically adjusted using `IncrementProposerPriority(times int)`, which prevents any validator from monopolizing block proposals.
This function limits priority differences by re-scaling priorities (`RescalePriorities(diffMax)`) and shifting values based on the average proposer priority (`shiftByAvgProposerPriority()`).
During each round, the validator with the highest priority is selected as the proposer, after which their priority is adjusted to prevent indefinite accumulation.\
These mechanisms collectively ensure efficient and fair validator rotation, maintaining a balanced consensus process while preventing priority overflows and unfair selection biases.
## Messages
### MsgValidatorJoin
`MsgValidatorJoin` defines a message for a node to join the network as validator.
Here is the structure for the transaction message:
```protobuf theme={null}
// MsgValidatorJoin defines a message for a new validator to join the network
message MsgValidatorJoin {
option (cosmos.msg.v1.signer) = "from";
option (amino.name) = "heimdallv2/stake/MsgValidatorJoin";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = true;
string from = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 val_id = 2 [ (amino.dont_omitempty) = true ];
uint64 activation_epoch = 3 [ (amino.dont_omitempty) = true ];
string amount = 4 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(amino.dont_omitempty) = true
];
bytes signer_pub_key = 5 [ (amino.dont_omitempty) = true ];
bytes tx_hash = 6 [ (amino.dont_omitempty) = true ];
uint64 log_index = 7 [ (amino.dont_omitempty) = true ];
uint64 block_number = 8 [ (amino.dont_omitempty) = true ];
uint64 nonce = 9 [ (amino.dont_omitempty) = true ];
}
```
### MsgStakeUpdate
`MsgStakeUpdate` defines a message for a validator to perform a stake update on Ethereum network.
```protobuf theme={null}
message MsgStakeUpdate {
option (cosmos.msg.v1.signer) = "from";
option (amino.name) = "heimdallv2/stake/MsgStakeUpdate";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = true;
string from = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 val_id = 2 [ (amino.dont_omitempty) = true ];
string new_amount = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(gogoproto.customtype) = "cosmossdk.io/math.Int"
];
bytes tx_hash = 4 [ (amino.dont_omitempty) = true ];
uint64 log_index = 5 [ (amino.dont_omitempty) = true ];
uint64 block_number = 6 [ (amino.dont_omitempty) = true ];
uint64 nonce = 7 [ (amino.dont_omitempty) = true ];
}
```
### MsgSignerUpdate
`MsgSignerUpdate` defines a message for updating the signer of the existing validator.
```protobuf theme={null}
message MsgSignerUpdate {
option (cosmos.msg.v1.signer) = "from";
option (amino.name) = "heimdallv2/stake/MsgSignerUpdate";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = true;
string from = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 val_id = 2 [ (amino.dont_omitempty) = true ];
bytes new_signer_pub_key = 3 [ (amino.dont_omitempty) = true ];
bytes tx_hash = 4 [ (amino.dont_omitempty) = true ];
uint64 log_index = 5 [ (amino.dont_omitempty) = true ];
uint64 block_number = 6 [ (amino.dont_omitempty) = true ];
uint64 nonce = 7 [ (amino.dont_omitempty) = true ];
}
```
### MsgValidatorExit
`MsgValidatorExit` defines a message for a validator to exit the network.
```protobuf theme={null}
message MsgValidatorExit {
option (cosmos.msg.v1.signer) = "from";
option (amino.name) = "heimdallv2/stake/MsgValidatorExit";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = true;
string from = 1 [
(amino.dont_omitempty) = true,
(cosmos_proto.scalar) = "cosmos.AddressString"
];
uint64 val_id = 2 [ (amino.dont_omitempty) = true ];
uint64 deactivation_epoch = 3 [ (amino.dont_omitempty) = true ];
bytes tx_hash = 4 [ (amino.dont_omitempty) = true ];
uint64 log_index = 5 [ (amino.dont_omitempty) = true ];
uint64 block_number = 6 [ (amino.dont_omitempty) = true ];
uint64 nonce = 7 [ (amino.dont_omitempty) = true ];
}
```
## Interact with the Node
### Tx Commands
#### Validator Join
```bash theme={null}
heimdalld tx stake validator-join --proposer {proposer address} --signer-pubkey {signer pubkey with 04 prefix} --tx-hash {tx hash} --block-number {L1 block number} --staked-amount {total stake amount} --activation-epoch {activation epoch} --home="{path to home}"
```
#### Signer Update
```bash theme={null}
heimdalld tx stake signer-update --proposer {proposer address} --id {val id} --new-pubkey {new pubkey with 04 prefix} --tx-hash {tx hash} --log-index {log index} --block-number {L1 block number} --nonce {nonce} --home="{path to home}"
```
#### Stake Update
```bash theme={null}
heimdalld tx stake stake-update [valAddress] [valId] [amount] [txHash] [logIndex] [blockNumber] [nonce]
```
#### Validator Exit
```bash theme={null}
heimdalld tx stake validator-exit [valAddress] [valId] [deactivationEpoch] [txHash] [logIndex] [blockNumber] [nonce]
```
### CLI Query Commands
One can run the following query commands from the stake module:
* `current-validator-set` - Query all validators that are currently active in the validators' set
* `signer` - Query validator info for given validator address
* `validator` - Query validator info for a given validator id
* `validator-status` - Query validator status for given validator address
* `total-power` - Query total power of the validator set
* `is-old-tx` - Check if a tx is old (already submitted)
```bash theme={null}
heimdalld query stake current-validator-set
```
```bash theme={null}
heimdalld query stake signer [val_address]
```
```bash theme={null}
heimdalld query stake validator [id]
```
```bash theme={null}
heimdalld query stake validator-status [val_address]
```
```bash theme={null}
heimdalld query stake total-power
```
```bash theme={null}
heimdalld query stake is-old-tx [txHash] [logIndex]
```
### GRPC Endpoints
The endpoints and the params are defined in the [stake/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/stake/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.stake.Query/GetCurrentValidatorSet
```
```bash theme={null}
grpcurl -plaintext -d '{"val_address": <>}' localhost:9090 heimdallv2.stake.Query/GetSignerByAddress
```
```bash theme={null}
grpcurl -plaintext -d '{"id": <>}' localhost:9090 heimdallv2.stake.Query/GetValidatorById
```
```bash theme={null}
grpcurl -plaintext -d '{"val_address": <>}' localhost:9090 heimdallv2.stake.Query/GetValidatorStatusByAddress
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.stake.Query/GetTotalPower
```
```bash theme={null}
grpcurl -plaintext -d '{"tx_hash": <>, "log_index": <>}' localhost:9090 heimdallv2.stake.Query/IsStakeTxOld
```
```bash theme={null}
grpcurl -plaintest -d '{"times": <>}' localhost:9090 heimdallv2.stake.Query/GetProposersByTimes
```
## REST APIs
The endpoints and the params are defined in the [stake/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/stake/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
curl localhost:1317/stake/validators-set
```
```bash theme={null}
curl localhost:1317/stake/signer/{val_address}
```
```bash theme={null}
curl localhost:1317/stake/validator/{id}
```
```bash theme={null}
curl localhost:1317/stake/validator-status/{val_address}
```
```bash theme={null}
curl localhost:1317/stake/total-power
```
```bash theme={null}
curl localhost:1317/stake/is-old-tx?tx_hash=&log_index=
```
```bash theme={null}
curl localhost:1317/stake/proposers/{times}
```
# Topup
Source: https://docs.polygon.technology/pos/architecture/heimdall_v2/topup
How the Heimdall topup module manages validator fee balances on Heimdall, including funding methods, bridge processing, and fee withdrawal.
## Overview
Heimdall Topup is an amount that will be used to pay fees on the heimdall chain.
There are two ways to top up your account:
1. When new validator joins, they can mention a `topup` amount as top-up in addition to the staked amount, which will be
moved as balance on the heimdall chain to pay fees on Heimdall.
2. A user can directly call the top-up function on the staking smart contract on Ethereum to increase top-up balance on
Heimdall.
## Flow
The Heimdall Top-up Mechanism facilitates the management of validator fees on the Heimdall chain by allowing deposits from the Ethereum (L1) root chain.\
This mechanism ensures validators have enough balances on Heimdall to cover operational fees.\
The system integrates the Ethereum staking contract with Heimdall's x/topup and x/checkpoint modules and a bridge component for cross-chain fee management.
### Top-Up Funding Methods
There are two primary ways to fund a validator’s fee balance on Heimdall:
* During Validator Initialization: When a new validator joins, they can specify a top-up amount in addition to the staked amount. This top-up is transferred to Heimdall as an initial balance.
* Direct Top-Up: Any user can invoke the top-up function on the Ethereum staking smart contract to increase the validators’ top-up balance on Heimdall.
### Bridge Processing
Top-up events on the Ethereum layer trigger automated processing through the bridge process:
* The Root Chain Log Listener monitors for `StakinginfoTopUpFee` events.
* the Top-up Fee Processor task, upon detecting such an event, triggers the `sendTopUpFeeToHeimdall` execution.
The task:
* Decodes the Ethereum log.
* Verifies the event hasn’t already been processed.
* Broadcasts a `MsgTopupTx` to the Heimdall chain.
In addition to the automated broadcasting of `MsgTopupTx` transactions, it is also possible to manually craft and submit these transactions at the Heimdall layer.\
This fallback mechanism is used in scenarios where issues arise in bridging or processing Ethereum events.
### Heimdall x/topup Module Implementation
Two core messages are defined in the x/topup module for fee management:
* `MsgTopupTx`: Handles minting the top-up amount on Heimdall based on Ethereum events.\
Each top-up is uniquely identified by a sequence number built from `TxHash` and `LogIndex` to prevent duplicate processing.
`MsgTopupTx` is a side-transaction, ensuring state changes only after the successful pre-commit majority of the votes are collected and final validation and post-tx handler execution in the following block height.\
When broadcasting the `MsgTopupTx` - sender (proposer of the topup) must sign it, and additional user address must be sent.
For the top-up to be accepted, the `MsgTopupTx.Fee` must be at least equal to the `DefaultFeeWantedPerTx` amount. The top-up processing on Heimdall involves:
* Minting the top-up number of pol tokens to the top-up module account.
* Transferring the entire amount from the top-up module account to the user account.
* Transferring the `DefaultFeeWantedPerTx` amount from the user account to the proposer validator account.
The remaining top-up amount stays on the user account.
* `MsgWithdrawFeeTx`: Allows validators to withdraw fees from Heimdall back to Ethereum.
The withdrawal process involves:
* Transferring the amount from the validator to the top-up module account.
* Burning the amount from the top-up module account.
* Updating the validator’s dividend “account” with the withdrawn amount.
* No impact on the user account used during the `MsgTopupTx`.
## Messages
### MsgTopupTx
`MsgTopupTx` is responsible for minting balance to an address on Heimdall based on Ethereum chain's `TopUpEvent` on
staking manager contract.
Handler for this transaction processes top-up and increases the balance only once for any given `msg.TxHash`
and `msg.LogIndex`. It throws an error if trying to process the top-up more than once.
Here is the structure for the top-up transaction message:
```protobuf theme={null}
message MsgTopupTx {
option (cosmos.msg.v1.signer) = "proposer";
option (amino.name) = "heimdallv2/topup/MsgTopupTx";
string proposer = 1 [
(cosmos_proto.scalar) = "cosmos.AddressString",
(amino.dont_omitempty) = true
];
string user = 2 [
(cosmos_proto.scalar) = "cosmos.AddressString",
(amino.dont_omitempty) = true
];
string fee = 3 [
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
bytes tx_hash = 4 [ (amino.dont_omitempty) = true ];
uint64 log_index = 5 [ (amino.dont_omitempty) = true ];
uint64 block_number = 6 [ (amino.dont_omitempty) = true ];
}
```
### MsgWithdrawFeeTx
`MsgWithdrawFeeTx` is responsible for withdrawing balance from Heimdall to the Ethereum chain.
A Validator can withdraw any amount from Heimdall.
Handler processes the withdrawal by deducting the balance from the given validator and prepares the state to send the next
checkpoint. The next possible checkpoint will contain the withdrawal-related state for the specific validator.
Handler gets validator information based on `ValidatorAddress` and processes the withdrawal.
```protobuf theme={null}
message MsgWithdrawFeeTx {
option (cosmos.msg.v1.signer) = "proposer";
option (amino.name) = "heimdallv2/topup/MsgWithdrawFeeTx";
string proposer = 1 [ (amino.dont_omitempty) = true ];
string amount = 2 [
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
}
```
## Interact with the Node
### Tx Commands
#### Topup fee
```bash theme={null}
heimdalld tx topup handle-topup-tx [proposer] [user] [fee] [tx_hash] [log_index] [block_number]
```
#### Withdraw fee
```bash theme={null}
heimdalld tx topup withdraw-fee [proposer] [amount]
```
### CLI Query Commands
One can run the following query commands from the topup module:
* `topup-sequence` - Query the sequence of a topup tx
* `is-old-tx` - Check if a tx is old (already submitted)
* `dividend-account` - Query a dividend account by its address
* `dividend-account-root` - Query dividend account root hash
* `account-proof` - Query account proof
* `verify-account-proof` - Verify account proof
```bash theme={null}
heimdalld query topup topup-sequence [tx_hash] [log_index]
```
```bash theme={null}
heimdalld query topup is-old-tx [tx_hash] [log_index]
```
```bash theme={null}
heimdalld query topup dividend-account [address]
```
```bash theme={null}
heimdalld query topup dividend-account-root
```
```bash theme={null}
heimdalld query topup account-proof [address]
```
```bash theme={null}
heimdalld query topup verify-account-proof [address] [proof]
```
### GRPC Endpoints
The endpoints and the params are defined in the [topup/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/topup/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
grpcurl -plaintext -d '{"tx_hash": <>, "log_index": <>}' localhost:9090 heimdallv2.topup.Query/IsTopupTxOld
```
```bash theme={null}
grpcurl -plaintext -d '{"tx_hash": <>, "log_index": <>}' localhost:9090 heimdallv2.topup.Query/GetTopupTxSequence
```
```bash theme={null}
grpcurl -plaintext -d '{"address": <>}' localhost:9090 heimdallv2.topup.Query/GetDividendAccountByAddress
```
```bash theme={null}
grpcurl -plaintext -d '{}' localhost:9090 heimdallv2.topup.Query/GetDividendAccountRootHash
```
```bash theme={null}
grpcurl -plaintext -d '{"address": <>, "proof": <>}' localhost:9090 heimdallv2.topup.Query/VerifyAccountProofByAddress
```
```bash theme={null}
grpcurl -plaintext -d '{"address": <>}' localhost:9090 heimdallv2.topup.Query/GetAccountProofByAddress
```
### REST APIs
The endpoints and the params are defined in the [topup/query.proto](https://github.com/0xPolygon/heimdall-v2/blob/main/proto/heimdallv2/topup/query.proto) file.
Please refer to them for more information about the optional params.
```bash theme={null}
curl localhost:1317/topup/is-old-tx?tx_hash=&log_index=
```
```bash theme={null}
curl localhost:1317/topup/sequence?tx_hash=&log_index=
```
```bash theme={null}
curl localhost:1317/topup/dividend-account/{address}
```
```bash theme={null}
curl localhost:1317/topup/dividend-account-root
```
```bash theme={null}
curl localhost:1317/topup/account-proof/{address}/verify
```
```bash theme={null}
curl localhost:1317/topup/account-proof/{address}
```
# Overview
Source: https://docs.polygon.technology/pos/architecture/overview
Architectural overview of Polygon PoS from a node perspective, covering the Heimdall-v2 consensus layer and Bor execution layer.
Polygon PoS is structured as a two-layer system: a consensus layer called Heimdall-v2 and an execution layer called Bor.
Nodes on Polygon are designed around this two-layer split. Bor handles block production; Heimdall-v2 handles validation, checkpoint submission, and coordination with Ethereum.
## Three-layer view
The network can be described as three layers:
* **Ethereum layer**: a set of contracts on Ethereum mainnet. These include staking contracts, checkpoint storage, and the bridge.
* **Heimdall-v2 layer**: a set of proof-of-stake nodes running in parallel to Ethereum mainnet. Heimdall-v2 monitors staking contracts on Ethereum, validates Bor block data, and submits checkpoints to Ethereum. Built on Cosmos SDK and CometBFT.
* **Bor layer**: a set of block-producing nodes shuffled by Heimdall. Built on Go Ethereum.
## Staking contracts on Ethereum
The PoS mechanism relies on staking management contracts deployed on Ethereum mainnet. These contracts:
* Allow anyone to stake POL tokens and register as a validator.
* Distribute staking rewards for validating state transitions on the network.
* Record checkpoints submitted by Heimdall.
The PoS mechanism also provides a partial mitigation for the data unavailability problem on Polygon sidechains, since checkpoint data is anchored to Ethereum.
## Heimdall-v2: validation layer
Heimdall-v2 aggregates blocks produced by Bor into Merkle trees and publishes the Merkle root periodically to Ethereum. These periodic snapshots are called checkpoints.
For every set of Bor blocks, a Heimdall-v2 validator:
1. Validates all blocks since the last checkpoint.
2. Creates a Merkle tree of the block hashes.
3. Publishes the Merkle root hash to Ethereum mainnet.
Checkpoints serve two purposes:
* Providing finality on Ethereum for cross-chain withdrawals.
* Providing proof of burn for asset withdrawal to Ethereum.
Block producer selection works as follows:
* A subset of active validators from the pool is selected as block producers for a span. These producers create and broadcast blocks.
* A checkpoint includes the Merkle root hash of all blocks in a given interval. All nodes validate the hash and attach their signatures.
* A proposer from the validator set collects all signatures and submits the checkpoint to Ethereum mainnet.
* Validator selection probability is proportional to stake ratio in the overall pool.
Heimdall-v2 is a complete rewrite of the original Heimdall. It is based on Cosmos SDK and CometBFT. The migration is specified in:
* [PIP-43: Replacing Tendermint with CometBFT](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/cb371136414b5e198c44750cd4c30f7aad16043a/PIPs/PIP-43.md)
* [PIP-44: Upgrade Cosmos-SDK](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/cb371136414b5e198c44750cd4c30f7aad16043a/PIPs/PIP-44.md)
* [PIP-62: Heimdall-v2 Migration](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/cb371136414b5e198c44750cd4c30f7aad16043a/PIPs/PIP-62.md)
Throughout these docs, "Heimdall" refers to Heimdall-v2 unless otherwise specified.
## Bor: block production layer
Bor is Polygon PoS's block producer, responsible for aggregating transactions into blocks. Block producers are a rotating subset of validators selected periodically by Heimdall based on stake.
See [Bor architecture](/pos/architecture/bor/introduction/) for detailed mechanics.
# Finality
Source: https://docs.polygon.technology/pos/concepts/finality/finality
How finality works on Polygon PoS, including the milestone mechanism and the difference between probabilistic and deterministic finality.
With the upgrade to Heimdall v2, deterministic finality is now achieved in 2 to 5 seconds, thanks to 1 to 2 second block times in Heimdall. Milestones are voted on and finalized much faster than the previous checkpoint-only model.
## Types of finality
There are two main types of finality in blockchains: probabilistic and deterministic.
**Probabilistic finality** means there is a chance of a reorganization where a different chain might become the canonical chain. Bitcoin is a well-known example.
**Deterministic finality** means there is no chance of a reorganization once a block is finalized. Ethereum uses deterministic finality via its Casper FFG mechanism.
Polygon PoS uses deterministic finality via its milestone mechanism.
## How finality works on Polygon PoS
Polygon PoS achieves finality through two distinct mechanisms that serve different purposes:
### Milestones
Milestones provide fast deterministic finality on the Polygon chain itself, without waiting for a checkpoint to be submitted to Ethereum.
At every Heimdall height, each validator proposes the Bor block hashes they have seen since the last finalized milestone, using vote extensions in the CometBFT consensus. When finalizing Heimdall height H+1, Heimdall looks for the longest common sequence of block hashes from all validators that have 2/3 or more agreement. That sequence is finalized as the new milestone.
This means finality is deterministic even before a checkpoint reaches Ethereum. Milestones typically finalize a transaction within 2 to 5 seconds.
### Checkpoints
Checkpoints still occur every 256 blocks (minimum) and are submitted to Ethereum mainnet. They:
* Provide proof of burn for asset withdrawals from Polygon to Ethereum.
* Anchor Polygon state to Ethereum for additional security.
Checkpoints are separate from milestones. A transaction is finalized on Polygon by a milestone well before the next checkpoint is submitted.
## Querying finality
Use the standard `eth_getBlockByNumber` JSON-RPC method with the `"finalized"` block tag to retrieve the most recently finalized block on Polygon PoS. Finalized blocks are considered irreversible.
```json theme={null}
{
"method": "eth_getBlockByNumber",
"params": ["finalized", true],
"id": 1,
"jsonrpc": "2.0"
}
```
To check whether a specific transaction has reached finality, compare its block number to the latest finalized block number:
```ts theme={null}
async function milestones_checkFinality(client: any, txHash: string): Promise {
const tx = await client.getTransaction({ hash: `0x${txHash}` })
if (!tx || !tx.blockNumber) return false
const latestBlock: Block = await client.getBlock({ blockTag: 'finalized' })
console.log(`Latest finalized block: ${latestBlock.number}`)
console.log(`Your transaction block: ${tx.blockNumber}`)
// Returns true if the finalized block has passed the transaction's block number.
return (latestBlock.number !== null && latestBlock.number > tx.blockNumber)
}
```
*Finality is achieved after a consensus period among validators, approximately 2 to 5 seconds.*
# MATIC
Source: https://docs.polygon.technology/pos/concepts/tokens/matic
What MATIC was as the native token of Polygon PoS, and its relationship to the POL token that replaced it.
Polygon network is transitioning from MATIC to POL, which will serve as the gas and staking token on Polygon PoS. Use the links below to learn more:
* [Migrate from MATIC to POL](/pos/concepts/tokens/matic-to-pol)
* [POL token specs](/pos/concepts/tokens/pol/)
[MATIC](https://etherscan.io/token/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0) was the native cryptocurrency of Polygon PoS. It served the same role that ETH serves on Ethereum: users paid MATIC as gas fees for transactions and smart contract interactions, and validators staked MATIC to participate in consensus.
MATIC has been superseded by POL through a 1:1 migration. Validator and staking rewards are now distributed in POL. For details on what action you may need to take depending on which chain holds your tokens, see the [MATIC to POL migration guide](/pos/concepts/tokens/matic-to-pol).
## Testing with MATIC on Amoy testnet
Testnet MATIC is available on the Amoy testnet for testing purposes. Testnet tokens have no real-world value.
To obtain testnet tokens, visit the [Polygon Faucet](https://faucet.polygon.technology/), select MATIC Token, choose the Polygon PoS (Amoy) network, and enter your account address.
## Example: sending MATIC
This [example script](https://gist.github.com/rahuldamodar94/ea3bc4c551e6fc2d318767dcd7e5bffe) demonstrates sending MATIC tokens between accounts on the Polygon chain, showing how the token was used before the POL migration.
# Migrate to POL
Source: https://docs.polygon.technology/pos/concepts/tokens/matic-to-pol
How to migrate MATIC tokens to POL, depending on which chain they are currently on.
## Overview
The upgrade from MATIC to POL marks a significant change for the Polygon networks. POL is the native gas and staking token on Polygon PoS and supports the network's future expansion as an aggregated network of blockchains.
The migration operates on a 1:1 basis. For every MATIC you migrate, you receive one POL.
## Migrate MATIC tokens on Ethereum
MATIC stakers and delegators do not need to take any action to migrate from MATIC to POL.
1. Navigate to [Polygon Portal's migration interface](https://portal.polygon.technology/pol-upgrade).
2. Switch to the Ethereum network in your wallet and connect to the Portal UI.
3. Approve the migration action by granting the upgrade contract permission to access your MATIC tokens.
4. Confirm the migration transaction to receive POL in your wallet.
## Migrate MATIC tokens on Polygon PoS
MATIC tokens stored on the Polygon PoS chain are automatically converted to POL at a 1:1 ratio. No manual migration is required.
However, you may need to update the native token symbol in your wallet's network settings. If the symbol is not updated, your wallet may continue to display "MATIC" instead of "POL."
To update the symbol in MetaMask:
1. Open MetaMask in expanded mode by selecting **Expand view** from the options menu in the top-right corner.
2. Open the options menu again and select **Settings** from the drop-down list.
3. Select the **Networks** tab from the left sidebar and switch to **Polygon Mainnet**. The **Currency symbol** field shows the current value, which is **MATIC**.
4. Change the **Currency symbol** to **POL** and select **Save**. You can ignore the yellow warning in this case.
The process to change the token symbol varies by wallet. Refer to your wallet's documentation for the specific steps.
## Migrate MATIC tokens on Polygon zkEVM
If your MATIC tokens are on the zkEVM chain:
1. Use [Polygon Portal](https://portal.polygon.technology/bridge) to bridge your tokens to Ethereum.
2. Follow the steps in [Migrate MATIC tokens on Ethereum](#migrate-matic-tokens-on-ethereum).
## Further reading
* [Detailed blog post on MATIC to POL migration](https://polygon.technology/blog/save-the-date-matic-pol-migration-coming-september-4th-everything-you-need-to-know)
* [POL token reference](/pos/concepts/tokens/pol/)
# POL
Source: https://docs.polygon.technology/pos/concepts/tokens/pol
What POL is, how it works as the native token of Polygon PoS, and how its emission and governance mechanisms are designed.
POL is the native token of the Polygon ecosystem, replacing MATIC. It serves as the gas and staking token on Polygon PoS, and is designed to support the network's expansion as an aggregated network of blockchains.
Like MATIC, POL is built on OpenZeppelin's ERC-20 implementation and supports [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) for signature-based permit approvals.
## Governance proposals behind POL
Community-driven governance shaped POL's design and functionality. The relevant proposals are:
1. [PIP-17: Polygon Ecosystem Token (POL)](https://forum.polygon.technology/t/pip-17-polygon-ecosystem-token-pol/12912)
2. [PIP-18: Polygon 2.0 Phase 0 - Frontier](https://forum.polygon.technology/t/pip-18-polygon-2-0-phase-0-frontier/12913)
3. [PIP-19: Update Polygon PoS Native Token to POL](https://forum.polygon.technology/t/pip-19-update-polygon-pos-native-token-to-pol/12914)
4. [PIP-25: Adjust POL Total Supply](https://forum.polygon.technology/t/pip-25-adjust-pol-total-supply/13008)
5. [PIP-26: Transition from MATIC to POL Validator Rewards](https://forum.polygon.technology/t/pip-26-transition-from-matic-to-pol-validator-rewards/13046)
The initial supply of POL is 10 billion tokens, matching the MATIC supply on a 1:1 basis at migration.
For migration instructions, see the [MATIC to POL migration guide](/pos/concepts/tokens/matic-to-pol).
## Emission
POL has an ongoing emissions schedule. The original proposal set a 2% annual emission rate, with 1% to the community treasury and 1% to validator rewards. Community consensus via [PIP-26](https://forum.polygon.technology/t/pip-26-transition-from-matic-to-pol-validator-rewards/13046) revised the validator reward percentage to:
* 2% for year four (2023-2024)
* 1.5% for year five (2024-2025)
* 1% thereafter
This results in an effective 2% annual POL emission beginning after June 2025. The emission rate can be changed through governance by upgrading the `EmissionManager` contract, but cannot exceed the `mintPerSecondCap` defined in the primary POL smart contract.
### How POL is minted
The `EmissionManager` smart contract initiates minting using the `INTEREST_PER_YEAR_LOG2` constant to calculate an annual emission rate compounded per year. The contract distributes newly minted tokens to the `StakeManager` and `Treasury` contracts. The `EmissionManager` is upgradeable, allowing governance to change its behavior.
## Token migration and reversal
POL migration from MATIC operates on a 1-to-1 conversion. A migration contract accepts MATIC and provides an equal amount of POL. The full supply of MATIC can be upgraded through this contract.
After migration, MATIC is held in the migration contract. It is not burned. The migration contract includes an "unmigration" feature that allows users to convert POL back to an equivalent amount of MATIC. Governance controls whether this feature is enabled, providing flexibility in response to network conditions or security concerns.
## Bridging behavior
With community approval, the bridge was modified to use POL as the native token:
* Bridging POL to Polygon PoS: you receive an equal amount of native POL on Polygon PoS.
* Bridging native tokens from Polygon PoS to Ethereum: the bridge disburses POL.
Existing contracts that relied on receiving MATIC from the bridge and now receive POL instead may have locked funds. Developers must verify their contracts handle the token change correctly.
## Governance and security
The POL contracts are governed by the Polygon decentralized governance model. Community proposals follow the PIP process. Security measures include rate limits on minting and the ability to lock or unlock features such as unmigration.
## Implications for dApp developers
Developers generally will not encounter breaking changes from the MATIC to POL transition, because the token implements the same ERC-20 interface. However, any contract that relies on receiving MATIC from the bridge needs to be checked and updated to handle POL.
If you have questions, reach out via the [Polygon R\&D Discord](https://discord.com/invite/0xpolygonrnd).
Read the [blog post on the POL migration](https://polygon.technology/blog/save-the-date-matic-pol-migration-coming-september-4th-everything-you-need-to-know) for a detailed explanation of the token, its properties, and what the migration means for the ecosystem.
## Avoiding scams
Always verify contract addresses through official sources. Exercise caution with any claims about "swaps" or "transfers" from unverified sources, as the token migration has attracted fraudulent activity.
# sPOL
Source: https://docs.polygon.technology/pos/concepts/tokens/spol
What sPOL is, how Polygon's liquid staking token works, and how to stake POL to receive sPOL.
sPOL is the liquid staking token for Polygon PoS. When you stake POL through the [Polygon Liquid Staking](https://staking.polygon.technology/lst) interface, you receive sPOL in return. sPOL represents your share of the staking pool, including accumulated rewards. It is a standard ERC-20 token that can be transferred, traded, or used in DeFi protocols while your underlying POL continues earning staking rewards.
**Contract address (Ethereum mainnet):** `0x3B790d651e950497c7723D47B24E6f61534f7969`
## How sPOL works
Liquid staking pools the POL of many stakers and delegates it to validators on Polygon PoS. The pool receives validator rewards on behalf of all participants. Rather than locking your tokens and managing delegation yourself, you stake through the pool and receive sPOL tokens that represent your proportional claim on the pooled POL plus rewards.
The exchange rate between sPOL and POL increases over time as staking rewards accrue. When you unstake, you return sPOL and receive POL at the current exchange rate, which reflects the rewards earned since you staked.
### Validators
sPOL delegates to a curated mix of high-performing validators from the Polygon validation set, selected by Polygon Labs. You do not choose individual validators when using liquid staking.
### Reward sources
sPOL accumulates rewards from multiple sources, all of which are reflected in the sPOL/POL exchange rate over time:
* Standard Polygon PoS staking rewards
* Priority fees under PIP-85
* Additional priority fees shared by validators in the pool
## Liquidity and lock-up
sPOL is a liquid staking token, so staking POL does not lock your position. You have two ways to exit:
* **Via exchange**: Swap or sell sPOL on any exchange or DeFi protocol that supports it. This gives you immediate liquidity at the prevailing market rate.
* **Via unstaking**: Return sPOL through the [Polygon Liquid Staking](https://staking.polygon.technology/lst) interface. This follows the same withdrawal delay as regular Polygon PoS staking and redeems sPOL 1:1 at the current exchange rate.
## Staking and unstaking
To stake:
1. Go to [staking.polygon.technology/lst](https://staking.polygon.technology/lst).
2. Connect your wallet.
3. Enter the amount of POL you want to stake.
4. Approve the transaction and confirm the staking transaction.
You receive sPOL tokens immediately in your connected wallet. The amount of sPOL you receive is calculated using the current exchange rate. sPOL appears as a standard ERC-20 token and is visible in any wallet that supports ERC-20 tokens.
To unstake, return sPOL through the same interface. Unstaking is subject to a withdrawal delay determined by the underlying staking contracts.
## Using sPOL in DeFi
Because sPOL is a standard ERC-20 token, it can be used in any protocol that supports ERC-20 tokens, such as lending markets, liquidity pools, and collateral vaults. Rewards continue to accrue in the exchange rate regardless of where sPOL is held.
## Contract security
Always verify the contract address before interacting with any token claiming to be sPOL. The official contract address on Ethereum mainnet is `0x3B790d651e950497c7723D47B24E6f61534f7969`. Do not interact with contracts at any other address.
If you prefer to delegate directly to a specific validator, see [how to stake as a delegator](/pos/how-to/delegate).
# EIP-1559
Source: https://docs.polygon.technology/pos/concepts/transactions/eip-1559
How EIP-1559 changed gas estimation and transaction fees on Polygon PoS, and how Type 2 transactions differ from legacy transactions.
The [London hard fork](https://blog.polygon.technology/eip-1559-upgrades-are-going-live-on-polygon-mainnet/) introduced [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) to Polygon, modifying how gas estimation and transaction costs work.
## What changed
Before EIP-1559, transactions specified a single `gasPrice` field. Miners prioritized transactions with higher `gasPrice` bids. This model made gas estimation unpredictable during periods of high demand.
EIP-1559 introduced a new transaction type, Type 2, which splits the old `gasPrice` into two components:
* **`baseFee`**: a network-determined fee that is burned, calculated based on how full the previous block was. All transactions must pay this fee.
* **`priorityFee` (tip)**: an optional amount offered to the validator to prioritize the transaction.
The relationship is: `maxFeePerGas` sets the ceiling on what you are willing to pay. The actual fee paid is `baseFee + priorityFee`, where `priorityFee = min(maxPriorityFeePerGas, maxFeePerGas - baseFee)`.
Legacy Type 0 transactions remain compatible but the Type 2 format is recommended.
## Legacy transaction (Type 0)
In a legacy transaction, only `gasPrice` is specified:
```jsx theme={null}
const sendLegacyTransaction = async () => {
const web3 = new Web3('https://polygon-rpc.com');
await web3.eth.sendTransactions({
from: 0x05158d7a59FA8AC5007B3C8BabAa216568Fd32B3,
to: 0xD7Fbe63Db5201f71482Fa47ecC4Be5e5B125eF07,
value: 1000000000000000000,
gasPrice: 200000000000
})
}
```
## Type 2 transaction (EIP-1559)
Type 2 transactions use `maxPriorityFeePerGas` instead of `gasPrice`. The `baseFee` is paid regardless, so only the tip is bid:
```jsx theme={null}
const sendEIP1559Transaction = async () => {
const web3 = new Web3('https://polygon-rpc.com');
await web3.eth.sendTransactions({
from: 0xFd71Dc9721d9ddCF0480A582927c3dCd42f3064C,
to: 0x8C400f640447A5Fc61BFf7FdcE00eCf20b85CcAd,
value: 1000000000000000000,
maxPriorityFeePerGas: 40000000000
})
}
```
## Gas estimation
The Polygon Gas Station V2 provides current gas fee estimates:
```
https://gasstation.polygon.technology/v2
```
Sample response:
```json theme={null}
{
"safeLow": {
"maxPriorityFee": 37.181444553750005,
"maxFee": 326.2556979087
},
"standard": {
"maxPriorityFee": 49.575259405,
"maxFee": 435.00759721159994
},
"fast": {
"maxPriorityFee": 61.96907425625,
"maxFee": 543.7594965144999
},
"estimatedBaseFee": 275.308812719,
"blockTime": 6,
"blockNumber": 23948420
}
```
## See also
* [How to send transactions with EIP-1559](https://docs.alchemy.com/alchemy/guides/eip-1559/send-tx-eip-1559) - covers both the legacy and EIP-1559 approaches.
* [Send an EIP-1559 transaction with ethers.js](https://www.quicknode.com/guides/web3-sdks/how-to-send-an-eip-1559-transaction) - quicknode guide.
# EIP-4337
Source: https://docs.polygon.technology/pos/concepts/transactions/eip-4337
How ERC-4337 account abstraction works on Polygon PoS, including UserOperations, Bundlers, EntryPoints, and Paymasters.
The ERC-4337 standard, also known as EIP-4337, allows developers to achieve account abstraction on the Polygon PoS. This page provides a simplified overview of the different components of ERC-4337 and how they work together.
The ERC-4337 standard consists of four main components: `UserOperation`, `Bundler`, `EntryPoint`, and `Contract Account`. Optional components include `Paymasters` and `Aggregators`.
## Building ERC-4337 transactions
ERC-4337 transactions are called `UserOperations` to avoid confusion with the regular transaction type. `UserOperations` are pseudo-transaction objects that are used to execute transactions with contract accounts. ERC-4337 transactions have to be sent to nodes that include ERC-4337 bundlers.
The `UserOperation` object has a few fields.
| Field | Type | Description |
| ---------------------- | --------- | --------------------------------------------------------------------------------------- |
| `sender` | `address` | The address of the smart contract account |
| `nonce` | `uint256` | Anti-replay protection |
| `initCode` | `bytes` | Code used to deploy the account if not yet onchain |
| `callData` | `bytes` | Data that's passed to the `sender` for execution |
| `callGasLimit` | `uint256` | Gas limit for execution phase |
| `verificationGasLimit` | `uint256` | Gas limit for verification phase |
| `preVerificationGas` | `uint256` | Gas to compensate the bundler |
| `maxFeePerGas` | `uint256` | Similar to EIP-1559 max fee |
| `maxPriorityFeePerGas` | `uint256` | Similar to EIP-1559 priority fee |
| `paymasterAndData` | `bytes` | `Paymaster Contract` address and any extra data required for verification and execution |
| `signature` | `bytes` | Used to validate a `UserOperation` along with the `nonce` during verification |
## ERC-4337 wallets
Users must have an ERC-4337 smart contract account to validate `UserOperations`. The core interface for an ERC-4337 wallet is:
```solidity theme={null}
interface IAccount {
function validateUserOp
(UserOperation calldata userOp, bytes32 userOpHash, address aggregator, uint256 missingAccountFunds)
external returns (uint256 sigTimeRange);
}
```
## Resources
* [ERC-4337 proposal](https://eips.ethereum.org/EIPS/eip-4337) is the link to the official proposal and technical specification.
* [@account-abstraction SDK](https://www.npmjs.com/package/@account-abstraction/sdk) is an npm package for using ERC-4337 developed by the authors of the proposal.
* [Stackup](https://docs.stackup.sh/) provides node services with ERC-4337 bundlers and other ERC-4337 infrastructure.
* [WalletKit](https://walletkit.com) is an all-in-one platform for adding smart, gasless wallets to your app. It has integrated support for ERC-4337 and comes with a paymaster and bundler included, requiring no extra setup.
# Meta transactions
Source: https://docs.polygon.technology/pos/concepts/transactions/meta-transactions
What meta transactions are, why they matter, and how the relayer model decouples gas payment from transaction signing on Polygon PoS.
## The problem meta transactions solve
The traditional transaction model requires users to pay gas fees directly from their wallet. This creates friction: users need to acquire cryptocurrency before they can interact with a dApp. Meta transactions address this by separating the transaction sender from the gas payer.
## What meta transactions are
Meta transactions enable users to interact with the blockchain without holding tokens to cover gas fees. Instead of signing a complete Ethereum transaction, the user signs a message describing their intended action. A third-party relayer wraps that signed message into a standard transaction, pays the gas, and submits it to the network.
The contract receiving the meta transaction unwraps it by validating the original signature, then executes the action on behalf of the user.
In summary, the roles are:
* **Sender (intender)**: signs a request with their private key and sends it to a relayer. Does not pay gas.
* **Relayer**: validates the signed request, wraps it into a transaction, pays the gas, and submits it to the contract.
* **Contract**: unwraps the transaction, validates the original signature, and executes the requested action.
A meta transaction is different from a batch transaction. A batch transaction sends multiple transactions at once from a single sender in sequence. A meta transaction is a single action where the gas payer is separated from the signer.
The sender becomes an "intender" rather than a payer: they express intent by signing a message, but do not construct or fund the underlying transaction.
## Use cases
### Voting
A user wanting to vote in onchain governance signs a meta transaction containing their vote off-chain, then sends it to a relayer. The relayer validates priority, wraps it into a transaction, pays gas, and submits it to the voting contract. The contract validates the user's signature and records the vote.
### Gaming
In blockchain-based games, players can perform in-game actions (trading items, upgrading characters) without holding ETH or POL. The relayer covers gas, and the player's signed message is submitted on their behalf.
### Minting NFTs
Users can mint or purchase NFTs by signing a request. The relayer submits it, so users do not need to manage gas tokens. This lowers the barrier for users unfamiliar with managing cryptocurrency.
## Technical implementation
The meta transaction pattern requires:
1. A contract that inherits `NativeMetaTransactions` and exposes an `executeMetaTransaction` function. For example, see the [ChildERC20 implementation](https://github.com/maticnetwork/pos-portal/blob/34be03cfd227c25b49c5791ffba6a4ffc9b76036/flat/ChildERC20.sol#L1338).
2. A relayer server that accepts signed EIP-712 formatted messages from clients and submits them to the contract.
3. Client-side code that fetches an EIP-712 signature from the user and calls the relayer API.
## Relayer options on Polygon PoS
You can use an existing relayer service or run a custom relayer:
* [Biconomy](https://docs.biconomy.io/quickstart)
* [Gas Station Network (GSN)](https://docs.opengsn.org/#ethereum-gas-station-network-gsn)
* [Infura](https://infura.io/product/ethereum/transactions-itx)
* [Gelato](https://docs.gelato.network/developer-products/gelato-relay-sdk)
# Becoming a validator
Source: https://docs.polygon.technology/pos/get-started/becoming-a-validator
Requirements, responsibilities, and links to the step-by-step guides for joining and operating the Polygon PoS validator set.
Polygon network is transitioning from MATIC to POL, which will serve as the gas and staking token on Polygon PoS. Use the links below to learn more:
* [Migrate from MATIC to POL](/pos/concepts/tokens/matic-to-pol)
* [POL token specs](/pos/concepts/tokens/pol/)
Validators maintain the Polygon PoS network by running nodes, staking POL, producing blocks, and participating in consensus. In exchange, they earn staking rewards and transaction fees.
The network supports a maximum of 105 active validators at any time. New validators can join the active set only when an existing validator unbonds or is removed for poor performance.
To apply for a validator slot, submit an application at the [Polygon validators hub](https://polygoncommunity.typeform.com/validatorshub). Submitting an application does not guarantee a slot.
## What validators run
To participate as a validator, you must operate two types of nodes:
* **Sentry node**: a Heimdall node and a Bor node, open to all peers on the network. Acts as a public-facing entry point.
* **Validator node**: a Heimdall node and a Bor node, accessible only through its sentry node and closed to the rest of the network.
You also stake POL tokens on the staking contracts deployed on Ethereum mainnet. The minimum stake is 10,000 POL (set by governance proposal PIP-4).
### What each layer does
**Heimdall** (consensus layer):
* Monitors staking contracts on Ethereum mainnet.
* Verifies all state transitions on the Bor chain.
* Submits Bor chain state checkpoints to Ethereum mainnet.
Built on CometBFT and Cosmos SDK. See [GitHub: Heimdall](https://github.com/0xPolygon/heimdall-v2).
**Bor** (execution layer):
* Produces blocks on Polygon PoS.
* Block producers are selected from the validator set by Heimdall based on stake.
Built on Go Ethereum. See [GitHub: Bor](https://github.com/0xPolygon/bor).
## Validator responsibilities
### Node operations
Node operations that happen automatically:
* Block producer selection for each span.
* Block validation on Bor.
* Checkpoint proposal and signing.
* State sync from Ethereum to Bor via the `ack/no-ack` checkpoint mechanism.
### Daily manual operations
* Maintain high uptime. Approximately every 34 minutes, a checkpoint transaction must be signed by every validator. Missed signatures reduce your performance score.
* Check Heimdall and Bor services and processes daily. Prune nodes regularly to reduce disk usage.
* Monitor node health using Grafana dashboards (for example, [Matic-Jagar](https://github.com/vitwit/matic-jagar)) or your own tooling.
* Keep an ETH balance of 0.5 to 1 ETH on your signer address. You need ETH to sign and submit checkpoint transactions on Ethereum mainnet. Running low causes delays in checkpoint submission and downstream finality.
### Delegation
* Accept delegation from the community. Each validator sets their own commission rate. There is no upper limit.
* Communicate commission rates and any rate changes via [Discord](https://discord.com/invite/0xPolygonCommunity) or the [Forum](https://forum.polygon.technology/).
### Communication
* Report issues early via [Discord](https://discord.com/invite/0xPolygonCommunity), [Forum](https://forum.polygon.technology/), or [GitHub](https://github.com/0xPolygon).
* Monitor [Polygon forum announcements](https://forum.polygon.technology/c/announcement/6) for node and validator updates.
## Join the network as a validator
Use one of these guides to set up and run your validator node:
* [Start and run nodes with Ansible](/pos/how-to/validator/validator-ansible/)
* [Start and run nodes with binaries](/pos/how-to/validator/validator-binaries/)
* [Stake as a validator](/pos/how-to/operate-validator-node/next-steps/#stake-tokens)
## Maintain your validator nodes
* [Change the signer address](/pos/how-to/operate-validator-node/change-signer-address/)
* [Change your commission rate](/pos/how-to/operate-validator-node/next-steps/#changing-your-commission-rate)
## Community assistance
* [Discord](https://discord.com/invite/0xPolygonCommunity)
* [Forum](https://forum.polygon.technology/)
# Building on PoS
Source: https://docs.polygon.technology/pos/get-started/building-on-polygon
How to connect to Polygon PoS, deploy contracts, bridge tokens, and find the right tooling for your project.
Polygon network is transitioning from MATIC to POL, which will serve as the gas and staking token on Polygon PoS. Use the links below to learn more:
* [Migrate from MATIC to POL](/pos/concepts/tokens/matic-to-pol)
* [POL token specs](/pos/concepts/tokens/pol/)
Polygon PoS is EVM-compatible. Foundry, Remix, Hardhat, ethers.js, and web3.js all work on Polygon without modification. Point your tooling at the [Polygon RPC](https://polygon-rpc.com/) and deploy.
## Prerequisites
* An EVM-compatible wallet (MetaMask or equivalent). See [supported wallets](/tools/wallets/getting-started/).
* Network configuration for Polygon mainnet or Amoy testnet. See [RPC endpoints and chain IDs](/pos/reference/rpc-endpoints/).
* POL tokens for gas. For testnet, use the [Polygon faucet](/tools/gas/matic-faucet/).
## Connect to Polygon PoS
Use any EVM wallet or node provider. The following example uses the Alchemy SDK to read from the network:
```js theme={null}
// Setup: npm install alchemy-sdk
const { Alchemy, Network } = require("alchemy-sdk");
const settings = {
apiKey: "demo", // Replace with your Alchemy API key from https://www.alchemy.com
network: Network.MATIC_MAINNET, // Use MATIC_AMOY for testnet
};
const alchemy = new Alchemy(settings);
async function main() {
const latestBlock = await alchemy.core.getBlockNumber();
console.log("The latest block number is", latestBlock);
}
main();
```
## Deploy a contract
Polygon PoS supports any contract that runs on Ethereum. There are no changes required to your Solidity code. Configure your deployment tool to target the Polygon RPC:
* Mainnet: `https://polygon-rpc.com` (chain ID 137)
* Amoy testnet: see [RPC endpoints](/pos/reference/rpc-endpoints/)
## Bridge tokens
Most dApps that interact with Ethereum need to move tokens between the two networks. Use the bridge how-to guides:
* [Bridge tokens from Ethereum to PoS](/pos/how-to/bridging/ethereum-polygon/ethereum-to-matic/)
* [Bridge tokens from PoS to Ethereum](/pos/how-to/bridging/ethereum-polygon/matic-to-ethereum/)
* [L1-L2 state transfer](/pos/how-to/bridging/l1-l2-communication/state-transfer/)
## Tooling and SDKs
Core tools for building on Polygon PoS:
* [Faucets](/tools/gas/matic-faucet/) - test token faucet
* [Polygon Gas Station](/tools/gas/polygon-gas-station/) - gas estimation API
* [Polygon dApp Launchpad](/tools/dApp-development/launchpad/intro/) - dApp development CLI
* [Matic.js library](/tools/matic-js/get-started/) - JavaScript SDK for Polygon
* [Popular third-party tooling](/tools/dApp-development/third-party-tutorials/)
General Ethereum development resources that work on Polygon:
* [Remix](https://remix.ethereum.org/) - browser-based Solidity IDE
* [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started) - development environment
* [Foundry](https://github.com/foundry-rs/foundry/blob/master/README.md) - fast Solidity toolchain
* [Ethers.js](https://docs.ethers.io/v5/)
* [Web3.js](https://www.dappuniversity.com/articles/web3-js-intro)
* [thirdweb](https://portal.thirdweb.com)
If you are new to dApp development, the [Full Stack dApp Tutorial Series](https://kauri.io/full-stack-dapp-tutorial-series/5b8e401ee727370001c942e3/c) covers the basics end to end.
## Migrating an existing dApp from Ethereum
Polygon PoS is EVM-compatible, so any contract that runs on Ethereum can be deployed to Polygon without code changes. The main considerations are:
* Update your RPC endpoint and chain ID.
* Check that any contracts receiving bridge funds handle the POL token rather than MATIC, since the native token has changed.
* [Map your tokens](/pos/how-to/bridging/ethereum-polygon/submit-mapping-request/) if you need them recognized by the bridge.
If you encounter issues or have questions, open an issue on the [Polygon Docs GitHub repository](https://github.com/0xPolygon/polygon-docs/issues), or reach out on [Community Discord](https://discord.com/invite/0xPolygonCommunity) or [R\&D Discord](https://discord.com/invite/0xpolygonrnd).
# Governance fundamentals
Source: https://docs.polygon.technology/pos/governance/governance-fundamentals
How Polygon PoS governance works, from off-chain ecosystem consensus through onchain upgrade execution.
Polygon PoS is a decentralized network of validator nodes. No single node controls the network, so changes require broad ecosystem consensus. If the ecosystem disagrees over a change, it can result in the network splitting, a protocol-level change generally referred to as forking.
Changes to the network require a high level of coordination and ecosystem consensus to execute successfully.
A hard fork happens when the node software changes in such a way that the new version is no longer backward-compatible with earlier blocks. This is usually the result of a change in the consensus logic, meaning that blocks validated using the latest software will produce a different hash.
A block number is selected before which all nodes in the network should have upgraded to the new version. Nodes running the old version will be disconnected from the canonical chain after the hard fork block.
Should there be $`{1/3}`+1$ staked POL in disagreement with the fork, two canonical chains will temporarily form until the end of the current span. Afterwards, Bor will stop producing blocks, and the chain will halt until consensus is reached.
In contrast, a soft fork is backward-compatible with the pre-fork blocks. This type of protocol change does not require nodes to upgrade before a deadline, so multiple versions of the node software can run simultaneously and still validate transactions.
The key ecosystem stakeholders involved in implementing a change are:
* Users
* Token holders
* Validators
* Infrastructure providers
* Full nodes
* Core developers
## Ecosystem consensus
The preliminary ecosystem consensus takes place through an off-chain process involving key stakeholders, including users and core developers. The framework accommodates different perspectives and provides a platform for constructive discussion and community cohesion.
The framework has three key components:
1. **[Polygon Improvement Proposals (PIPs)](https://github.com/maticnetwork/Polygon-Improvement-Proposals)**: Outlined in [PIP-1](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-01.md), PIPs are formal proposals that enable the community to put forward protocol upgrades. The framework is modeled on [Ethereum's EIP process](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1.md), with guiding principles from the [IETF](https://www.ietf.org/about/introduction/) and the broader open-source community.
2. **[Polygon Protocol Governance Call (PPGC)](https://github.com/maticnetwork/Polygon-Improvement-Proposals/tree/main/Project%20Management)**: Synchronous discussions where ["rough consensus"](https://datatracker.ietf.org/doc/html/rfc1603#:~:text=decisions%20through%20a%20%22-,rough%20consensus,-%22%20process.%0A%20%20%20IETF%20consensus) is established. The technical community uses these calls to decide which PIPs are included in a given upgrade and the rollout schedule.
3. **[Polygon Community Forum](https://forum.polygon.technology/)**: A space for long-form discussions ranging from high-level meta discussions to low-level technical details.
## Implementation
The PoS network runs two clients simultaneously:
* **Heimdall**: the [consensus layer](/pos/architecture/heimdall_v2/introduction/) client. [See GitHub](https://github.com/0xPolygon/heimdall-v2).
* **Bor**: the [execution layer](/pos/architecture/bor/introduction/) client. [See GitHub](https://github.com/0xPolygon/bor).
These clients are ecosystem focal points rather than control switches operated by core developers. Neither client alone can dictate protocol decisions.
When a hard fork is agreed upon through community consensus, the upgrade process generally follows this sequence:
* The protocol decision is ratified on a PPGC, and implementation begins in the relevant GitHub repositories.
* Core developers create pull requests containing the changes, which are merged into the codebase, and a new release tag is created.
* Core developers test new releases on local devnets. If everything functions normally, the tag is marked as beta (pre-release).
* The changes are deployed to the Amoy testnet and left to run for at least one week. The Amoy Testing Committee reports on stability in the PPGC.
* Once confirmed stable, the upgrade is scheduled for mainnet release on a PPGC. The tag version is marked final.
* Validators upgrade their nodes to the latest version. The upgrade becomes canonical through onchain consensus of the validating stake, including delegated stake from token holders.
## Onchain consensus
Onchain consensus parameters are inherited from [CometBFT](https://github.com/0xPolygon/cometbft/), which requires at least $`{2/3}`rd$ of the total validating stake to be in favor of the upgrade.
For the chain to remain stable after the change becomes canonical, non-validating full nodes must also upgrade to the latest version.
Key ecosystem stakeholders such as dApps, exchanges, and RPC providers run full nodes. These nodes propagate transactions and blocks, and can accept or reject blocks. This makes them enforcers of the network consensus rules and vital to the onchain governance process. If these nodes are incompatible with the changes, users and dApps would find their transactions invalid.
## Onchain governance module
The Heimdall client has an [in-built governance module](https://github.com/0xPolygon/cosmos-sdk/tree/devel/x/gov) that can carry out consensus parameter changes across the network without a full client upgrade:
* Proposals are submitted to the onchain module along with a deposit containing the proposed changes.
* Each validator tallies votes cast by the validator set.
* When the defined voting parameters are met, each validator applies the upgrade with the proposal data.
Current voting parameters (denominated in staked POL):
| Parameter | Value |
| --------- | ----- |
| Quorum | 33.4% |
| Threshold | 50% |
| Veto | 33.4% |
# Ethereum to PoS
Source: https://docs.polygon.technology/pos/how-to/bridging/ethereum-polygon/ethereum-to-matic
Deploy sender and receiver contracts to transfer arbitrary data from Ethereum to Polygon PoS using the state sync mechanism.
This guide walks through deploying a sender contract on Sepolia (Ethereum testnet) and a receiver contract on Amoy (Polygon testnet), then sending and verifying arbitrary data across the bridge. For background on how state sync works, see the [state sync architecture docs](/pos/architecture/bor/state-sync/).
The contracts and scripts in this guide are simplified illustrations. Do not deploy them to production without a thorough security review and audit appropriate for your use case.
Looking to bridge a token to Polygon PoS using the official bridge? Check out the guide on [how to submit a request to get your token mapped](/pos/how-to/bridging/ethereum-polygon/submit-mapping-request/).
### Deploy sender contract
The sender contract calls [`syncState`](https://github.com/0xPolygon/pos-contracts/blob/e999579e9dc898ab6e66ddcb49ee84c2543a9658/contracts/root/stateSyncer/StateSender.sol#L33) on the `StateSender` contract on Ethereum. This emits a `StateSynced` event that Heimdall validators pick up and relay to Bor.
`StateSender` is deployed at:
* `0x49E307Fa5a58ff1834E0F8a60eB2a9609E6A5F50` on Sepolia
* `0x28e4F3a7f651294B9564800b2D01f35189A5bFbE` on Ethereum mainnet
```solidity title="Sender.sol" theme={null}
pragma solidity ^0.8.0;
interface IStateSender {
function syncState(address receiver, bytes calldata data) external;
}
contract Sender {
address public stateSenderContract;
address public receiver;
uint public states;
constructor(address _stateSender, address _receiver) {
stateSenderContract = _stateSender;
receiver = _receiver;
}
function sendState(bytes calldata data) external {
states += 1;
IStateSender(stateSenderContract).syncState(receiver, data);
}
}
```
Deploy `Sender.sol` to Sepolia, passing in the `StateSender` address and your receiver contract address. Note the deployed address and ABI.
### Deploy receiver contract
The receiver contract is invoked by the `StateReceiver` system contract on Bor whenever a matching `StateSynced` event is processed.
Always check that `msg.sender` is the `StateReceiver` contract at `0x0000000000000000000000000000000000001001`. Without this check, any address on Polygon can call `onStateReceive` on your contract directly.
```solidity title="Receiver.sol" theme={null}
pragma solidity ^0.8.0;
interface IStateReceiver {
function onStateReceive(uint256 stateId, bytes calldata data) external;
}
contract Receiver is IStateReceiver {
address constant STATE_RECEIVER = 0x0000000000000000000000000000000000001001;
uint public lastStateId;
bytes public lastData;
function onStateReceive(uint256 stateId, bytes calldata data) external {
require(msg.sender == STATE_RECEIVER, "unauthorized");
lastStateId = stateId;
lastData = data;
}
}
```
`stateId` is a monotonically increasing counter that uniquely identifies each state sync event. Deploy `Receiver.sol` to Amoy and note the deployed address and ABI.
### Register your sender and receiver contracts
Before state sync will trigger your receiver, your sender/receiver pair must be registered with the `StateSender` contract. This registration is what authorizes the pair: only events originating from a registered sender contract targeting a registered receiver will be relayed by Heimdall validators.
Registration for new custom sender/receiver pairs is managed by the Polygon team. Contact the team on [Discord](https://discord.com/invite/0xPolygonCommunity) or submit a request via [the mapping form](https://docs.google.com/forms/d/e/1FAIpQLSeq8HTef2dYpRx35_WWYhyr4C146K9dfhyYJQcoD1RuTTVABg/viewform) to initiate the process.
If you want to test end-to-end before your custom contracts are registered, you can use the already-registered example contracts referenced in the form.
### Send and verify data
With contracts deployed and registered, use the following script to send arbitrary bytes from Sepolia and verify receipt on Amoy. State sync takes approximately 7 to 8 minutes to complete.
```javascript title="test.js" theme={null}
const { Web3 } = require('web3');
const main = new Web3('');
const matic = new Web3('');
const privateKey = '0x...';
main.eth.accounts.wallet.add(privateKey);
const senderAddress = '';
const senderABI = [/* paste ABI here */];
const receiverAddress = '';
const receiverABI = [/* paste ABI here */];
const sender = new main.eth.Contract(senderABI, senderAddress);
const receiver = new matic.eth.Contract(receiverABI, receiverAddress);
async function sendData(message) {
const data = matic.utils.asciiToHex(message);
const tx = await sender.methods.sendState(data).send({
from: main.eth.accounts.wallet[0].address,
gas: 200000,
});
console.log('Sent from Ethereum:', tx.transactionHash);
}
async function checkReceiver() {
const stateId = await receiver.methods.lastStateId().call();
const rawData = await receiver.methods.lastData().call();
const message = matic.utils.hexToAscii(rawData);
console.log('Last state ID:', stateId);
console.log('Received message:', message);
}
async function run() {
await sendData('Hello from Ethereum!');
console.log('Waiting ~8 minutes for state sync...');
setTimeout(checkReceiver, 8 * 60 * 1000);
}
run();
```
Successful output looks like:
```bash theme={null}
Sent from Ethereum: 0x4f64ae4ab4d2b2d2dc82cdd9ddae73af026e5a9c46c086b13bd75e38009e5204
Waiting ~8 minutes for state sync...
Last state ID: 453
Received message: Hello from Ethereum!
```
# PoS to Ethereum
Source: https://docs.polygon.technology/pos/how-to/bridging/ethereum-polygon/matic-to-ethereum
Deploy child and root contracts to transfer data from Polygon PoS back to Ethereum using checkpoints and the RootChainManager.
This guide demonstrates transferring a `uint256` value from Polygon PoS to Ethereum. The same approach applies to any data type: encode the data as bytes, emit it from the child contract, then decode it in the root contract after checkpoint verification. For background on the checkpoint and predicate mechanism, see the [state sync architecture docs](/pos/architecture/bor/state-sync/).
The contracts in this guide are simplified illustrations. Do not deploy them to production without a thorough security review and audit appropriate for your use case.
## Implementation
### Deploy contracts
Create the child contract on Polygon PoS and the root contract on Ethereum. The function that performs the state change must emit an event whose parameters include the data to transfer.
```solidity Child.sol theme={null}
pragma solidity ^0.8.0;
contract Child {
event Data(address indexed from, bytes bytesData);
uint256 public data;
function setData(bytes memory bytesData) public {
data = abi.decode(bytesData, (uint256));
emit Data(msg.sender, bytesData);
}
}
```
```solidity Root.sol theme={null}
pragma solidity ^0.8.0;
contract Root {
address public predicate;
constructor(address _predicate) {
predicate = _predicate;
}
modifier onlyPredicate() {
require(msg.sender == predicate, "unauthorized");
_;
}
uint256 public data;
function setData(bytes memory bytesData) public onlyPredicate {
data = abi.decode(bytesData, (uint256));
}
}
```
Pass `0x1470E07a6dD1D11eAE439Acaa6971C941C9EF48f` as the `_predicate` value in the `Root` constructor. The `onlyPredicate` modifier ensures that only the predicate contract can update state on the root contract. The predicate is invoked by `RootChainManager` on Ethereum after it verifies the transaction against a Polygon checkpoint, guaranteeing state changes on Ethereum reflect verified Polygon activity.
### Map your contracts
Once both contracts are deployed, submit a mapping request so the PoS bridge recognizes the child/root pair. See the [submit a mapping request guide](/pos/how-to/bridging/ethereum-polygon/submit-mapping-request/) for steps.
Already working with a widely used token? The [Polygon Portal](/tools/wallets/portal/) lists tokens that are already mapped and available for bridging without a new mapping request.
### Initiate a transfer on Polygon
Call `setData` on the child contract with the encoded value you want to transfer. Wait for the transaction to be included in a Polygon checkpoint. Checkpoints are submitted to Ethereum roughly every 30 minutes.
You can verify checkpoint inclusion using the [Polygon checkpoint tracker](https://github.com/rahuldamodar94/matic-learn-pos/blob/transfer-matic-ethereum/script/check-checkpoint.js) or by polling the `RootChainManager` on Ethereum.
### Exit on Ethereum
After the checkpoint is confirmed, call `exit` on `RootChainManager` on Ethereum to finalize the transfer. You need the transaction hash of the `setData` call on Polygon and the keccak-256 hash of the `Data` event signature.
The `logEventSignature` for the `Data(address,bytes)` event is:
```
0x93f3e547dcb3ce9c356bb293f12e44f70fc24105d675b782bd639333aab70df7
```
Using `matic.js`:
```javascript theme={null}
import { POSClient, use } from "@maticnetwork/maticjs";
const client = new POSClient();
await client.init({ network: "mainnet", version: "v1" });
const txHash = "0x";
const logEventSignature = "0x93f3e547dcb3ce9c356bb293f12e44f70fc24105d675b782bd639333aab70df7";
const tx = await client.exitUtil.buildPayloadForExit(txHash, logEventSignature, false);
```
Alternatively, call `exit(bytes calldata inputData)` directly on `RootChainManager` at `0xA0c68C638235ee32657e8f720a23ceC1bfc77C77` on Ethereum mainnet, constructing the proof payload from the burn receipt.
Once the exit transaction is confirmed, query the `data` variable on the root contract to verify the value has been reflected on Ethereum.
The full contract source and exit script for this example are available in the [matic-learn-pos repository](https://github.com/rahuldamodar94/matic-learn-pos/tree/transfer-matic-ethereum).
# Polygon Portal
Source: https://docs.polygon.technology/pos/how-to/bridging/ethereum-polygon/portal-ui
Reference overview of the Polygon Portal bridge, including how tokens are locked and minted when crossing between Ethereum and Polygon PoS.
The Polygon Portal is the official trustless two-way bridge between Polygon PoS and Ethereum. It lets you transfer tokens without third-party risk or market liquidity constraints. The bridge is available for both the PoS Amoy testnet and mainnet. Access it at [Polygon Portal](https://portal.polygon.technology).
To learn more about the features that Polygon Portal offers, and a series of step-by-step instructions that help you with using the platform, check out [the Portal guide](/tools/wallets/portal/).
There is no change to the circulating supply of your token when it crosses the bridge. This is what goes on in the background when you bridge your tokens over to Polygon PoS from Ethereum:
* When depositing, tokens that leave the Ethereum network are locked and the same number of tokens are minted on Polygon PoS as a pegged token (1:1).
* When withdrawing tokens back to the Ethereum network, tokens are burned on Polygon PoS and unlocked on Ethereum during the process.
## Additional resources
* [Introduction to Blockchain Bridges](https://ethereum.org/en/bridges/)
* [What are Cross-Chain Bridges?](https://www.alchemy.com/overviews/cross-chain-bridges)
# Submit mapping request
Source: https://docs.polygon.technology/pos/how-to/bridging/ethereum-polygon/submit-mapping-request
Steps to submit a token mapping request to enable bridging between Ethereum and Polygon PoS.
Submit a mapping request to enable your token for bridging between Ethereum and Polygon PoS.
Thinking about bridging a popular token? Refer to the [reference guide on Polygon Portal](/tools/wallets/portal/).
## Steps to submit a mapping request
1. To submit a request for mapping your token on Polygon PoS, start by navigating to [the Google form available here](https://docs.google.com/forms/d/e/1FAIpQLSeq8HTef2dYpRx35_WWYhyr4C146K9dfhyYJQcoD1RuTTVABg/viewform).
2. Choose a **Network** option depending upon the chain on which you're looking to map your token. This would be **Sepolia ↔ Polygon Amoy** for testnet, and **Ethereum ↔ Polygon PoS** for mainnet.
3. Next, input the contract address for the token contract that you've deployed on Sepolia/Ethereum mainnet in the **Root Contract Address (L1)** field.
4. Choose the correct **Token Type** for your token. i.e., [ERC-20](https://eips.ethereum.org/EIPS/eip-20) for a standard token, [ERC-721](https://eips.ethereum.org/EIPS/eip-721) for an NFT, or [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) for a multi token.
5. Finally, select **Submit** to send in your request. The Polygon team will review your mapping request, and get back to you with a response. This generally takes up to 7 days.
* Check out the list of supported tokens available in JSON format by following this URL: [https://api-polygon-tokens.polygon.technology/tokenlists/polygonTokens.tokenlist.json](https://api-polygon-tokens.polygon.technology/tokenlists/polygonTokens.tokenlist.json)
* Once approved, your token will be added to the above list!
# Delegate tokens
Source: https://docs.polygon.technology/pos/how-to/delegate
Steps to delegate POL tokens to a validator, withdraw rewards, restake, and manage your stake on Polygon PoS.
This guide covers delegating tokens, withdrawing rewards, restaking, unbonding, and moving stake on Polygon PoS.
## Prerequisites
* POL tokens and ETH on your Ethereum mainnet address.
Polygon network is transitioning from MATIC to POL, which will serve as the gas and staking token on Polygon PoS. Use the links below to learn more:
* [Migrate from MATIC to POL](/pos/concepts/tokens/matic-to-pol)
* [POL token specs](/pos/concepts/tokens/pol/)
It is advisable to [migrate your MATIC tokens to POL](/pos/concepts/tokens/matic-to-pol), but if you continue to delegate MATIC tokens, you'll receive the staking rewards in the form of POL tokens.
## Access the dashboard
1. In your wallet (e.g. MetaMask), choose the Ethereum mainnet.
2. Log in to [Polygon Staking](https://staking.polygon.technology/).
3. Once you log in, you will see overall statistics along with the list of validators.
If you are a validator, use a different non-validating address to log in as delegator.
## Delegate to a validator
1. Select **Become a Delegator**, or scroll down to a specific validator and select **Delegate**.
2. Select POL or MATIC from the drop-down list and enter the token amount to delegate. It is recommended to migrate your MATIC tokens to POL and delegate POL tokens. If you choose MATIC, you'll still receive your staking rewards in POL. Then, select **Continue**.
3. Approve the delegate transaction from your wallet and select **Delegate**.
After the delegation transaction completes, you will see the **Delegation Completed** message.
## View your delegations
To view your delegations, select **My Account**.
## Withdraw rewards
1. Select **My Account**.
2. Under your delegated validator, select **Withdraw Rewards**.
This will withdraw the POL token rewards to your Ethereum address.
## Restake rewards
1. Select **My Account**.
2. Under your delegated validator, click **Restake Reward**.
This will restake the POL token rewards to the validator and increase your delegation stake.
## Unbond from a validator
1. Select **My Account**.
2. Under your delegated validator, select **Unbond**.
This will withdraw your rewards from the validator and your entire stake from the validator.
Your withdrawn rewards will show up immediately in your Ethereum wallet.
Your withdrawn stake funds will remain locked for *80 checkpoints*.
The fund locking for the unbonding period is in place to ensure there is no malicious behavior on the network.
## Move stake from one node to another node
Moving stake from one node to another node is a single transaction. There are no delays or unbonding periods during this event.
1. Select **My Account** and login to the staking dashboard.
2. Select **Move Stake** under your delegated validator.
3. Select an external validator and select **Stake here**.
4. Provide the stake amount and select **Move Stake**.
This will move the stake. The dashboard will update *after 12 block confirmations*.
## Common questions
### What is the staking dashboard URL?
The staking dashboard URL is [https://staking.polygon.technology/](https://staking.polygon.technology/).
### What is the minimum stake amount?
There is no minimum stake amount to delegate. However, you can always start with 1 POL token.
### How to stake tokens on Polygon?
For staking, you would need to have funds on the Ethereum mainnet (more information [here](https://etherscan.io/gastracker)). Log into your wallet on the Ethereum network using the [staking dashboard](https://staking.polygon.technology/).
Please watch this video for a graphical illustration of how this works:
**VIDEO\_UNAVAILABLE\_PLACEHOLDER**: `/img/pos/staking.mp4` (removed because it exceeds Cloudflare Workers’ 25MB upload limit).
### Why does my transaction take so long?
All staking transactions of Polygon PoS take place on Ethereum for security reasons.
The time taken to complete a transaction depends on the gas fees that you have allowed and also the network congestion of Ethereum mainnet at that point in time. You can always use the **Speed Up** option to increase the gas fees so that your transaction can be completed soon.
### I've staked my POL tokens. How can I stake more?
Navigate to the **Your Delegations** page and choose one of the stakes. Then click on **Stake More**.
Please watch this video for a graphical illustration of how this works:
### Why am I not able to stake?
Check if you have funds on the Main Ethereum Network, to delegate your tokens. All staking happens on the Ethereum Network only.
### I am unable to view the staking tab. How do I access staking?
You just need to access **[https://staking.polygon.technology/](https://staking.polygon.technology/)**, where you will see the following landing page:
### How do I know which validator to select for better rewards?
It depends on your understanding and research on which validator you would want to stake on. You can find the list of validators here : [https://staking.polygon.technology/validators](https://staking.polygon.technology/validators)
### How to unbond?
To unbond from a validator, navigate to **My Account**, where you'll find **Your Delegations**.
There you will see an **Unbond** button for each of the validators. Click on the **Unbond** button for the validator that you want to unbond from.
Please watch the video for a graphical illustration of how this works:
### What is the unbonding period?
The unbonding period on Polygon PoS is 80 checkpoints. Every checkpoint takes approximately *30 minutes*. However, some checkpoints could be delayed due to congestion on Ethereum. This period applies to the originally delegated amount and re-delegated amounts. It does not apply to any rewards that were not re-delegated.
### How to restake rewards?
Go to **My Account** to check **Your Delegations**. Clicking on **Restake Reward** will ask you for confirmation from your wallet account. Once you confirm the transaction in your wallet, the restake transaction is completed.
#### Step 1
#### Step 2
Please watch the video for a graphical illustration of how this works:
### I want to restake rewards but I am unable to.
You'll need to have a minimum of **2 POL** to restake rewards.
### How to withdraw rewards?
You can claim your rewards by clicking on the **My Account**, all the delegators for a validator are displayed. Click on the **Withdraw Reward** button and the rewards will be transferred to your delegated account in wallet.
#### Step 1
#### Step 2
Please watch the video for a graphical illustration of how this works:
### I want to withdraw rewards but I am unable to.
You'll need to have a minimum of **2 POL** available to withdraw rewards.
### How to claim stake?
Once the unbonding period is complete, the **Claim Stake** button will be enabled and you can then claim your staked tokens. The tokens will be transferred to your account.
#### Step 1
#### Step 2
#### Step 3
Please watch the video for a graphical illustration of how this works:
### Which wallets are currently supported?
We have recently upgraded the wallet support to WalletConnect v2.0. Now you can choose from a plethora of wallets, including Metamask, Coinbase, and others, on both desktop and mobile devices to log in.
### Are hardware wallets supported?
Yes, hardware wallets are supported. You can use the **Connect Hardware Wallet** option on MetaMask and connect your hardware wallet and then continue the delegation process.
### Why can’t I stake directly from Binance?
Staking through Binance is not yet supported. There will be an announcement if and when Binance starts supporting it.
### I have completed my delegation, where can I check details?
Once you have completed your delegation, wait for 12 block confirmations on Ethereum (approx. 3-5 minutes), then on the dashboard, you can click on **My Account**.
### Where can I check my rewards?
On the dashboard, you can click on the **My Account** option on the left-hand side.
### Do I need ETH to pay for gas fees?
Yes. You should maintain at least \~0.05-0.1 ETH balance for gas fees to be safe.
### Do I need to deposit POL tokens to the Polygon mainnet network for staking?
No. All your funds need to be on the main Ethereum network.
### When I try to do the transaction my **Confirm** button is disabled.
Please check if you have enough ETH for the gas fees.
### When do rewards get distributed?
The rewards are distributed whenever a checkpoint is submitted.
Currently, 71795 POL tokens are distributed proportionately on each successful checkpoint submission to each delegator based on their stake relative to the overall staking pool of all validators and delegators. Also, the percentage for the reward distributed to each delegator will vary with each checkpoint depending on the relative stake of the delegator, validator and the overall stake.
Note that there is a 10% proposer bonus that accrues to the validator who submits the checkpoint, but over time, the effect of the extra bonus is nullified over multiple checkpoints by different validators.
The checkpoint submission is done by one of the validators approximately every 30 minutes. This time is approximate and may vary based on validator consensus on the Polygon Heimdall layer. This may also vary based on Ethereum Network. Higher congestion in the network may result in delayed checkpoints.
You can track checkpoints on the staking contract [here](https://etherscan.io/address/0x86e4dc95c7fbdbf52e33d563bbdb00823894c287)
### Why do rewards keep getting decreased at every checkpoint?
Actual rewards earned will depend on the actual total locked supply in the network at each checkpoint. This is expected to vary significantly as more POL tokens get locked in the staking contracts.
Rewards will be higher, to begin with, and will keep decreasing as the locked supply % goes up. This change in locked supply is captured at every checkpoint, and rewards are calculated based on this.
### How can I claim my rewards?
You can claim your rewards instantly by clicking on the **Withdraw Reward** button. This will transfer the rewards accumulated to your delegated account on Metamask.
### Will I keep receiving rewards after I unbond?
No. Once you unbond, you will stop receiving rewards.
### How many transactions does the delegation require?
The delegation action takes place in 2 successive transactions. The first transaction to *approve* the request, and the second to *deposit* the approved token amount.
### What does re-delegate rewards mean?
Re-delegating your rewards simply means that you want to increase your stake by restaking the rewards you have accumulated.
### Can I stake to any validator?
Yes, you can delegate to any validator except the ones which are offline.
### Can I move the stake to another validator?
Yes, you just have to access **Your Delegations**, click on **Move Stake**, and then choose your new validator.
Please watch the video for a graphical illustration of how this works:
### Which browser is compatible with the Polygon earnings calculator?
Chrome, Firefox, and Brave.
### My MetaMask is stuck at confirming after login, what do I do? Or nothing happens when I try to login?
Check for the following:
* If you’re using Brave, please turn off the option for **Use Crypto Wallets** in the settings panel.
* Check if you are logged into Metamask
* Check if you are logged into MetaMask with Trezor/Ledger. You need to additionally turn on permission to call contracts on your Ledger device, if not enabled already.
* Check your system timestamp. If the system time is not correct, you will need to correct it.
### How do I send funds from Binance or other exchanges to Polygon wallet?
The Polygon Wallet Suite is a web application.
First, you must withdraw your funds from Binance or any other exchange to your Ethereum address on Metamask. If you don't know how to use Metamask, google it a bit. There are plenty of videos and blogs to get started with it.
### When can I become a validator and how many tokens do I need for that?
To become a validator in the PoS network, you'll need to hold and stake a minimum of 10,000 POL tokens, and go through an admissions process to ensure network security."
### If I have earned rewards while delegating, and if I add additional funds to the same validator node, what happens?
If you have not re-delegated your rewards before delegating additional funds to the same validator node, your rewards will be withdrawn automatically.
In case you don't want that to happen, re-delegate your rewards before delegating additional funds.
### I have delegated my tokens via MetaMask on the dashboard. Do I need to keep my system or device on?
No. Once your delegation transactions are confirmed, and you can see your tokens reflected in the **Total Stake** and **New Reward** sections, then you are done. There is no need to keep your system or device on.
### I have unbonded, how long will it take to unbond?
The unbonding period is currently set to 80 checkpoints. Every checkpoint takes approximately 30 minutes. However, some checkpoints could be delayed up to \~1 hour due to congestion on Ethereum.
### I have unbonded, and I now see the **Claim Stake** button, but it is disabled.
The Claim Stake button will only be enabled when your unbonding period is complete. The unbonding period is currently set at 80 checkpoints.
### When will the **Claim Stake** button be enabled?
Under the **Claim Stake** button you should see a note on how many checkpoints are pending before the **Claim Stake** button is enabled. Every checkpoint takes approximately 30 minutes. However, some checkpoints could be delayed up to \~1 hour due to congestion on Ethereum.
### Have the foundations nodes been turned off?
Yes, the foundation nodes had been turned off.
### Will there be any foundation nodes in the future?
No, there won't be any Foundation nodes in the future.
### How many transactions do I need to pay for gas when I perform a 'move stake' action?
The 'move stake' operation is performed in a single transaction. Since all transactions are executed on the Ethereum blockchain, you will need to spend some ETH for gas fees to complete the transaction.
# Run an Erigon archive node
Source: https://docs.polygon.technology/pos/how-to/erigon-archive-node
Steps to install, configure, and start an Erigon archive node for Polygon PoS mainnet or Amoy testnet.
## System requirements
* CPU: 16-core, 64-bit architecture
* RAM: 64GB
* Storage
* Basically `io1` or above with at least 20k+ iops and RAID-0 based disk structure
* Mainnet archive node: 15TB
* Amoy testnet archive node: 1TB
* SSD or NVMe. Bear in mind that SSD performance deteriorates when close to capacity.
* Golang: >= v1.20
* GCC: >= v10
On HDDs, Erigon will always remain *N* blocks behind the chain tip, but will not fall further behind.
## Install Erigon client
Run the following commands to install Erigon:
```bash theme={null}
git clone --recurse-submodules -j8 https://github.com/ledgerwatch/erigon
cd erigon
git checkout v2.57.3
make erigon
```
This should create the binary at `./build/bin/erigon`
## Start Erigon client
If you're deploying to mainnet, run the following command:
```bash theme={null}
erigon --chain=bor-mainnet --db.size.limit=12TB --db.pagesize=16KB # remaining flags follow
```
When connecting to Amoy testnet, use the following command to start your Erigon client:
```bash theme={null}
erigon --chain=amoy
```
## Configure Erigon client
If you want to store Erigon files in a non-default location, use `-datadir` to specify a new location:
```bash theme={null}
erigon --chain=amoy --datadir=
```
If you are not using local **heimdall**, use `-bor.heimdall=`. By default, it will try to connect to `localhost:1317`.
```bash theme={null}
erigon --chain=amoy --bor.heimdall= --datadir=
```
## Node RPC
* If you want to connect to PoS Amoy Testnet, use: [https://heimdall-api-amoy.polygon.technology](https://heimdall-api-amoy.polygon.technology)
* For PoS mainnet, use: [https://heimdall-api.polygon.technology](https://heimdall-api.polygon.technology)
Remote heimdall is better suited for testing, and is not recommended for production use.
## Tips for faster sync
* Use the machine with high IOPS and RAM for the faster initial sync
* Memory optimized nodes are recommended for faster sync. For example, AWS EC2 `r5` or `r6` series instances.
## Reporting issues
In case you encounter any issues and are looking for support, please get in touch with the Erigon team. More details available in [the Erigon GitHub README](https://github.com/ledgerwatch/erigon?tab=readme-ov-file#getting-in-touch).
# Using Ansible
Source: https://docs.polygon.technology/pos/how-to/full-node/full-node-ansible
Steps to deploy and manage a Polygon PoS full node using an Ansible playbook.
This guide deploys a full Polygon PoS node using an [Ansible playbook](https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html).
## Prerequisites
* Install Ansible on your local machine with Python3.x. The setup doesn't run on Python 2.x.
* To install Ansible with Python 3.x, you can use pip. If you do not have pip on your machine,
follow the steps outlined [here](https://pip.pypa.io/en/stable/). Run `pip3 install ansible` to install
Ansible.
* Check the [Polygon PoS Ansible repository](https://github.com/0xPolygon/node-ansible#requirements) for requirements.
* You also need to ensure that Go is *not installed* in your environment. You will run into issues if you attempt to set up your full node through Ansible with Go installed as Ansible requires specific packages of Go.
* You will also need to make sure that your VM / Machine does not have any previous setups for Polygon Validator or Heimdall or Bor. You will need to delete them as your setup will run into issues.
## Full node setup
* Ensure you have access to the remote machine or VM on which the full node is being set up.
> Refer to [https://github.com/0xPolygon/node-ansible](https://github.com/0xPolygon/node-ansible) for more details.
* Clone the [https://github.com/0xPolygon/node-ansible](https://github.com/0xPolygon/node-ansible) repository.
* Navigate into the node-ansible folder: `cd node-ansible`
* Edit the `inventory.yml` file and insert your IP(s) in the `sentry->hosts` section.
> Refer to [https://github.com/0xPolygon/node-ansible#inventory](https://github.com/0xPolygon/node-ansible#inventory) for more details.
* Check if the remote machine is reachable by running: `ansible sentry -m ping`
* To test if the correct machine is configured, run the following command:
```bash theme={null}
# Mainnet:
ansible-playbook playbooks/network.yml --extra-var="bor_version=v1.0.0 heimdall_version=v1.0.3 network=mainnet node_type=sentry" --list-hosts
# Testnet:
ansible-playbook playbooks/network.yml --extra-var="bor_version=v1.1.0 heimdall_version=v1.0.3 network=amoy node_type=sentry" --list-hosts
```
* Next, set up the full node with this command:
```bash theme={null}
# Mainnet:
ansible-playbook playbooks/network.yml --extra-var="bor_version=v1.1.0 heimdall_version=v1.0.3 network=mainnet node_type=sentry"
# Testnet:
ansible-playbook playbooks/network.yml --extra-var="bor_version=v1.0.0 heimdall_version=v1.0.3 network=amoy node_type=sentry"
```
* In case you run into any issues, delete and clean the whole setup using:
```bash theme={null}
ansible-playbook playbooks/clean.yml
```
* Once you initiate the Ansible playbook, log in to the remote machine.
* Please *ensure that the value of seeds and bootnodes mentioned [here](https://docs.polygon.technology/pos/reference/seed-and-bootnodes/) is the same value as mentioned in Heimdall and Bor `config.toml` files*. If not, change the values accordingly.
* To check if Heimdall is synced
* On the remote machine/VM, run `curl localhost:26657/status`
* In the output, `catching_up` value should be `false`
* Once Heimdall is synced, run:
* `sudo service bor start`
If you've reached this point, you have successfully set up a full node with Ansible.
If Bor presents an error of permission to data, run this command to make the Bor user the owner of the Bor files:
```bash theme={null}
sudo chown bor /var/lib/bor
```
## Logs
Logs can be managed by the `journalctl` linux tool. Here is a tutorial for advanced usage: [How To Use Journalctl to View and Manipulate Systemd Logs](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs).
### Check Heimdall node logs
```bash theme={null}
journalctl -u heimdalld.service -f
```
### Check Bor node logs
```bash theme={null}
journalctl -u bor.service -f
```
# Using binaries
Source: https://docs.polygon.technology/pos/how-to/full-node/full-node-binaries
Steps to install Heimdall and Bor binaries, configure, and start a Polygon PoS full node.
This guide walks through installing and starting a Polygon PoS full node using binaries. For system requirements, see the [prerequisites guide](/pos/how-to/prerequisites/).
Steps in these guide involve waiting for the Heimdall and Bor services to fully sync. This process takes several days to complete.
Please use snapshots for faster syncing without having to sync over the network. For detailed instructions, see [Sync node using snapshots](/pos/how-to/snapshots/).
## Overview
It is essential to follow the outlined sequence of actions precisely, as any deviation may lead to potential issues.
* Prepare the machine.
* Install Heimdall and Bor binaries on the full node machine.
* Set up Heimdall and Bor services on the full node machine.
* Configure the full node machine.
* Start the full node machine.
* Check node health with the community.
### Install `build-essential`
This is *required* for your full node. In order to install, run the below command:
```bash theme={null}
sudo apt-get update
sudo apt-get install build-essential
```
## Install binaries
Install both Heimdall and Bor in order. Both must be running to operate a full node.
### Heimdall
Install the latest version of Heimdall and related services. Make sure you checkout to the correct [release version](https://github.com/0xPolygon/heimdall-v2/releases).
To install *Heimdall*, run the following commands:
```bash theme={null}
curl -L https://raw.githubusercontent.com/0xPolygon/install/heimdall-v2/heimdall-v2.sh | bash -s --
```
You can run the above command with the following options:
* `heimdall_version`: Valid v0.2+ release tag from [https://github.com/0xPolygon/heimdall-v2/releases](https://github.com/0xPolygon/heimdall-v2/releases)
* `network_type`: `mainnet` and `amoy`
* `node_type`: `sentry`
This will install the `heimdalld` binary.
Then, edit the configuration files under `/var/lib/heimdall/config`\
The templates for each supported network are available [here](https://github.com/0xPolygon/heimdall-v2/tree/develop/packaging/templates/config)\
Download the `genesis.json` file and place it under `/var/lib/heimdall/config/`
Use the following commands based on your target network:
```bash theme={null}
cd /var/lib/heimdall/config
curl -fsSL -o genesis.json
```
Where `BUCKET_URL` is
* [https://storage.googleapis.com/amoy-heimdallv2-genesis/migrated\_dump-genesis.json](https://storage.googleapis.com/amoy-heimdallv2-genesis/migrated_dump-genesis.json) for amoy
* [https://storage.googleapis.com/mainnet-heimdallv2-genesis/migrated\_dump-genesis.json](https://storage.googleapis.com/mainnet-heimdallv2-genesis/migrated_dump-genesis.json) for mainnet
Verify the installation by checking the Heimdall version on your machine:
```bash theme={null}
heimdalld version
```
It should return the version of Heimdall you installed.
### Bor
Install the latest version of Bor, based on valid v2.0+ [released version](https://github.com/0xPolygon/bor/releases).
```bash theme={null}
curl -L https://raw.githubusercontent.com/0xPolygon/install/main/bor.sh | bash -s --
```
You can run the above command with following options:
* `bor_version`: valid v2.0+ release tag from [https://github.com/0xPolygon/bor/releases](https://github.com/0xPolygon/bor/releases)
* `network_type`: `mainnet` and `amoy`
* `node_type`: `sentry`
That will install the `bor` binary. Verify the installation by checking the Bor version on your machine:
```bash theme={null}
bor version
```
### Configure Heimdall and Bor seeds
The latest bor and heimdall seeds can be found [here](https://docs.polygon.technology/pos/reference/seed-and-bootnodes/). To configure them, update the following lines:
* If not done previously, set the `seeds` and `persistent_peers` values in `/var/lib/heimdall/config/config.toml`
* Set the `bootnodes` in `/var/lib/bor/config.toml`
This will ensure your node connects to the peers.
### (Optional) Start Heimdall from snapshot
In case you want to start Heimdall from a snapshot,\
you can download it, and extract in the `data` folder.
Examples of snapshots can be found here [https://all4nodes.io/Polygon](https://all4nodes.io/Polygon), and they are managed by the community.
e.g.:
```bash theme={null}
lz4 -dc polygon-heimdall-24404501-25758577.tar.lz4 | tar -x
```
### Update service config user permission
```bash theme={null}
sed -i 's/User=heimdall/User=root/g' /lib/systemd/system/heimdalld.service
sed -i 's/User=bor/User=root/g' /lib/systemd/system/bor.service
```
## Start services
Run the full Heimdall node with these commands on your Sentry Node:
```bash theme={null}
sudo service heimdalld start
```
Ensure that Heimdall is fully synced before starting Bor. Initiating Bor without complete synchronization of Heimdall may lead to frequent issues.
To check if Heimdall is synced:
1. On the remote machine/VM, run `curl localhost:26657/status`.
2. In the output, `catching_up` value should be `false`.
Once Heimdall is synced, run the following command:
```bash theme={null}
sudo service bor start
```
## Logs
Logs can be managed by the `journalctl` linux tool. Here is a tutorial for advanced usage: [How To Use Journalctl to View and Manipulate Systemd Logs](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs).
### Check Heimdall node logs
```bash theme={null}
journalctl -u heimdalld.service -f
```
### Check Bor node logs
```bash theme={null}
journalctl -u bor.service -f
```
# Using Docker
Source: https://docs.polygon.technology/pos/how-to/full-node/full-node-docker
Steps to run a Polygon PoS full node using official Docker images, including Heimdall and Bor setup on Linux.
This guide runs a full Polygon PoS node using official Docker images. The same steps can be adapted for sentry nodes and validators.
## Initial setup
To get started, you'll need to have shell access with root privileges to a linux machine.
### Install Docker
It is likely that your operating system won’t have Docker installed by default. Please follow the instructions for your particular distribution found here: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/)
We’re following the instructions for Ubuntu. The steps are included below, but please refer to the official instructions in case they’ve been updated.
```bash theme={null}
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
```
At this point you should have Docker installed. In order to verify, you should be able to run the following command successfully:
```bash theme={null}
sudo docker run hello-world
```
In many cases, it’s inconvenient to run docker as `root` user so we’ll follow the post install steps [here](https://docs.docker.com/engine/install/linux-postinstall/) in order to interact with docker without needing to be `root`:
```bash theme={null}
sudo groupadd docker
sudo usermod -aG docker $USER
```
Now you should be able to logout and log back in, and run docker commands without `sudo`.
### Disk setup
The exact steps required here may vary a lot based on your needs. Most likely you’ll have a root partition running your operating system on one device. You’ll probably want one or more devices for actually holding the blockchain data. For the rest of the walkthrough, we’re going to have that additional device mounted at `/mnt/data`.
In this example, we have a device with 4 TB of available space located at `/dev/nvme1n1`. We are going to mount that using the steps below:
```bash theme={null}
sudo mkdir /mnt/data
sudo mount /dev/nvme1n1 /mnt/data
```
We use `df -h` to make sure the mount looks good.
Once we've verified that successfully, we might as well create the home directories on this mount for Bor and Heimdall.
```bash theme={null}
sudo mkdir /mnt/data/bor
sudo mkdir /mnt/data/heimdall
```
Depending on your use case and operating system, you’ll likely want to create an entry in `/etc/fstab` in order to make sure your device is mounted when the system reboots.
In our case we're following some steps like this:
```bash theme={null}
# Use blkid to get the UUID for the device that we're mounting
blkid
# Edit the fstab file and add a line to mount your device
# UUID={your uuid} /mnt/data {your filesystem} defaults 0 1
sudo emacs /etc/fstab
# use this to verify the fstab actually works
sudo findmnt --verify --verbose
```
At this point you should be able to reboot and confirm that the system loads your mount properly.
### Heimdall setup
At this point, we have a host with docker running on it and we have ample mounted storage to run our Polygon node software. So let’s get Heimdall configured and running.
First let’s make sure we can run Heimdall with docker. Run the following command:
```bash theme={null}
docker run -it 0xpolygon/heimdall-v2:0.2.16 heimdalld version
```
If this is the first time you’ve run Heimdall with docker, it should pull the required image automatically and output the version information.
If you’d like to check the details of the Heimdall image or find a different tag, you can take a look at the repository on Docker Hub: [https://hub.docker.com/repository/docker/0xpolygon/heimdall-v2](https://hub.docker.com/repository/docker/0xpolygon/heimdall-v2)
At this point, let’s run the Heimdall `init` command to set up our home directory. Set `--chain-id` to `heimdallv2-80002` for Amoy or `heimdallv2-137` for mainnet.
```bash theme={null}
docker run -v /mnt/data/heimdall:/heimdall-home:rw --entrypoint /usr/bin/heimdalld -it 0xpolygon/heimdall-v2:0.2.16 init test-moniker --chain-id= --home=/heimdall-home
```
After running the `init` command, your `/mnt/data/heimdall` directory should have some structure.
Now we need to make a few updates before starting Heimdall.
Download the genesis file and place it under `HEIMDALL_HOME/config/genesis.json` (replacing the one potentially already present there)
```bash theme={null}
curl -L -o "/config/genesis.json"
```
where `BUCKET_URL` is [https://storage.googleapis.com/amoy-heimdallv2-genesis/migrated\_dump-genesis.json](https://storage.googleapis.com/amoy-heimdallv2-genesis/migrated_dump-genesis.json) for amoyand [https://storage.googleapis.com/mainnet-heimdallv2-genesis/migrated\_dump-genesis.json](https://storage.googleapis.com/mainnet-heimdallv2-genesis/migrated_dump-genesis.json) for mainnet
Replace `HEIMDALL_HOME` with the actual path to your Heimdall home directory.
Please note that the genesis file size is around 50MB for amoy and 3GB for mainnet.\
Hence, the download might take a while, and it’s recommended to use a stable and fast connection.
Then, you can customize the configs under `HEIMDALL_HOME/config` (`app.toml`, `client.toml`, `config.toml`), based on your setup.
Templates for each supported network are available [here](https://github.com/0xPolygon/heimdall-v2/tree/develop/packaging/templates/config)
Make sure to configure your Ethereum and Bor connection parameters based on your infrastructure.
## (Optional) Start Heimdall from snapshot
In case you want to start Heimdall from a snapshot,\
you can download it, and extract in the `data` folder.
Examples of snapshots can be found here [https://all4nodes.io/Polygon](https://all4nodes.io/Polygon), and they are managed by the community.
e.g.:
```bash theme={null}
lz4 -dc polygon-heimdall-24404501-25758577.tar.lz4 | tar -x
```
## Starting Heimdall
Before we start Heimdall, we’re going to create a docker network so that the containers can easily network with each other based on names.\
In order to create the network, run the following command:
```bash theme={null}
docker network create polygon
```
Now we’re going to start Heimdall. Run the following command:
```bash theme={null}
docker run -p 26657:26657 -p 26656:26656 -v /mnt/data/heimdall:/heimdall-home:rw --net polygon --name heimdall --entrypoint /usr/bin/heimdalld -d --restart unless-stopped 0xpolygon/heimdall-v2:0.2.16 start --home=/heimdall-home
```
Check container status and logs:
```bash theme={null}
# ps will list the running docker processes. At this point you should see one container running
docker ps
# This command will print out the logs directly from the heimdall application
docker logs -ft heimdall
```
At this point, Heimdall should start syncing. When you look at the logs, you should see a log of information being spit out that looks like this:
```
2022-12-14T19:43:23.687640820Z INFO [2022-12-14|19:43:23.687] Executed block module=state height=26079 validTxs=0 invalidTxs=0
2022-12-14T19:43:23.721220869Z INFO [2022-12-14|19:43:23.721] Committed state module=state height=26079 txs=0 appHash=CAEC4C181C9F82D7F55C4BB8A7F564D69A41295A3B62DDAA45F2BB41333DC20F
2022-12-14T19:43:23.730533414Z INFO [2022-12-14|19:43:23.730] Executed block module=state height=26080 validTxs=0 invalidTxs=0
2022-12-14T19:43:23.756646938Z INFO [2022-12-14|19:43:23.756] Committed state module=state height=26080 txs=0 appHash=CAEC4C181C9F82D7F55C4BB8A7F564D69A41295A3B62DDAA45F2BB41333DC20F
2022-12-14T19:43:23.768129711Z INFO [2022-12-14|19:43:23.767] Executed block module=state height=26081 validTxs=0 invalidTxs=0
2022-12-14T19:43:23.794323918Z INFO [2022-12-14|19:43:23.794] Committed state module=state height=26081 txs=0 appHash=CAEC4C181C9F82D7F55C4BB8A7F564D69A41295A3B62DDAA45F2BB41333DC20F
2022-12-14T19:43:23.802989809Z INFO [2022-12-14|19:43:23.802] Executed block module=state height=26082 validTxs=0 invalidTxs=0
2022-12-14T19:43:23.830960386Z INFO [2022-12-14|19:43:23.830] Committed state module=state height=26082 txs=0 appHash=CAEC4C181C9F82D7F55C4BB8A7F564D69A41295A3B62DDAA45F2BB41333DC20F
2022-12-14T19:43:23.840941976Z INFO [2022-12-14|19:43:23.840] Executed block module=state height=26083 validTxs=0 invalidTxs=0
2022-12-14T19:43:23.866564767Z INFO [2022-12-14|19:43:23.866] Committed state module=state height=26083 txs=0 appHash=CAEC4C181C9F82D7F55C4BB8A7F564D69A41295A3B62DDAA45F2BB41333DC20F
2022-12-14T19:43:23.875395744Z INFO [2022-12-14|19:43:23.875] Executed block module=state height=26084 validTxs=0 invalidTxs=0
```
If you’re not seeing any information like this, your node might not be finding enough peers. The other useful command at this point is an RPC call to check the status of Heimdall syncing:
```bash theme={null}
curl localhost:26657/status
```
This will return a response like:
```json theme={null}
{
"latest_block_hash":"6217E7BDAABE4DF58F8FCA2AAF0BD41BD93A96983F533DA7A0034E514D15BC5B",
"latest_app_hash":"BAA894A82E756A2797E3F608F6F7EED295549B8F7FB621E31542D206F5CA740C",
"latest_block_height":26191190,
"latest_block_time":"2025-08-06T12:50:35.062210407Z",
"earliest_block_hash":"14C55F6E824DD1C1D3D9AB424D0A983953F6C2BCA0C9ED692AE2C43498D870CD",
"earliest_app_hash":"E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
"earliest_block_height":24404501,
"earliest_block_time":"2025-07-10T15:20:00Z",
"catching_up":false
}
```
In this initial setup phase, it’s important to pay attention to the `catching_up` field.\
If `catching_up` is true, it means that Heimdall is not fully synced.
## Starting Bor
At this point, you should have a node that’s successfully running Heimdall. You should be ready now to run Bor.
Before we get started with Bor, we need to run the Heimdall rest server. This command will start a REST API that Bor uses to retrieve information from Heimdall. The command to start the server is:
```bash theme={null}
docker run -p 1317:1317 -v /mnt/data/heimdall:/heimdall-home:rw --net polygon --name heimdallrest --entrypoint /usr/bin/heimdalld -d --restart unless-stopped 0xpolygon/heimdall:1.0.3 rest-server --home=/heimdall-home --node "tcp://heimdall:26657"
```
If this command runs successfully, when you run `docker ps`, you should see two containers running. Verify the REST server is responding:
```bash theme={null}
curl localhost:1317/bor/span/1
```
Bor will rely on this interface. So if you don’t see JSON output, there is something wrong!
Now let’s download the `genesis` file for Bor specifically:
```bash theme={null}
sudo curl -o /mnt/data/bor/genesis.json 'https://raw.githubusercontent.com/0xPolygon/bor/master/builder/files/genesis-mainnet-v1.json'
```
Let’s verify the `sha256 sum` again for this file:
```
# sha256sum genesis.json
4bacbfbe72f0d966412bb2c19b093f34c0a1bd4bb8506629eba1c9ca8c69c778 genesis.json
```
Generate a default Bor config file, then edit it. Only the changed lines are shown below. Full image details: [https://hub.docker.com/repository/docker/0xpolygon/bor](https://hub.docker.com/repository/docker/0xpolygon/bor).
```bash theme={null}
docker run -it 0xpolygon/bor:1.1.0 dumpconfig | sudo tee /mnt/data/bor/config.toml
```
```bash theme={null}
# Similar to moniker, you might want to update this with a name of your own choosing
identity = "docker.example"
# Setting this to the location of a mount that we'll make
datadir = "/bor-home"
# We'll want to specify some boot nodes
[p2p]
[pep.discovery]
bootnodes = ["enode://0cb82b395094ee4a2915e9714894627de9ed8498fb881cec6db7c65e8b9a5bd7f2f25cc84e71e89d0947e51c76e85d0847de848c7782b13c0255247a6758178c@44.232.55.71:30303", "enode://88116f4295f5a31538ae409e4d44ad40d22e44ee9342869e7d68bdec55b0f83c1530355ce8b41fbec0928a7d75a5745d528450d30aec92066ab6ba1ee351d710@159.203.9.164:30303"]
# Because we're running inside docker, we'll likely need to change the way we connect to heimdall
[heimdall]
url = "http://heimdallrest:1317"
# Assuming you want to access the RPC, you'll need to make a change here as well
[jsonrpc]
[jsonrpc.http]
enabled = true
host = "0.0.0.0"
```
At this point, we should be ready to start Bor. We’re going to use this command:
```bash theme={null}
docker run -p 30303:30303 -p 8545:8545 -v /mnt/data/bor:/bor-home:rw --net polygon --name bor -d --restart unless-stopped 0xpolygon/bor:1.1.0 server --config /bor-home/config.toml
```
If everything goes well, you should see log entries that look like this:
```bash theme={null}
2022-12-14T19:53:51.989897291Z INFO [12-14|19:53:51.989] Fetching state updates from Heimdall fromID=4 to=2020-05-30T23:47:46Z
2022-12-14T19:53:51.989925064Z INFO [12-14|19:53:51.989] Fetching state sync events queryParams="from-id=4&to-time=1590882466&limit=50"
2022-12-14T19:53:51.997640841Z INFO [12-14|19:53:51.997] StateSyncData Gas=0 Block-number=12800 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.021990622Z INFO [12-14|19:53:52.021] Fetching state updates from Heimdall fromID=4 to=2020-05-30T23:49:58Z
2022-12-14T19:53:52.022015930Z INFO [12-14|19:53:52.021] Fetching state sync events queryParams="from-id=4&to-time=1590882598&limit=50"
2022-12-14T19:53:52.040660857Z INFO [12-14|19:53:52.040] StateSyncData Gas=0 Block-number=12864 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.064795784Z INFO [12-14|19:53:52.064] Fetching state updates from Heimdall fromID=4 to=2020-05-30T23:52:10Z
2022-12-14T19:53:52.064828634Z INFO [12-14|19:53:52.064] Fetching state sync events queryParams="from-id=4&to-time=1590882730&limit=50"
2022-12-14T19:53:52.085029612Z INFO [12-14|19:53:52.084] StateSyncData Gas=0 Block-number=12928 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.132067703Z INFO [12-14|19:53:52.131] ✅ Committing new span id=3 startBlock=13056 endBlock=19455 validatorBytes=f8b6d906822710940375b2fc7140977c9c76d45421564e354ed42277d9078227109442eefcda06ead475cde3731b8eb138e88cd0bac3d9018238a2945973918275c01f50555d44e92c9d9b353cadad54d905822710947fcd58c2d53d980b247f1612fdba93e9a76193e6d90482271094b702f1c9154ac9c08da247a8e30ee6f2f3373f41d90282271094b8bb158b93c94ed35c1970d610d1e2b34e26652cd90382271094f84c74dea96df0ec22e11e7c33996c73fcc2d822 producerBytes=f8b6d906822710940375b2fc7140977c9c76d45421564e354ed42277d9078227109442eefcda06ead475cde3731b8eb138e88cd0bac3d9018238a2945973918275c01f50555d44e92c9d9b353cadad54d905822710947fcd58c2d53d980b247f1612fdba93e9a76193e6d90482271094b702f1c9154ac9c08da247a8e30ee6f2f3373f41d90282271094b8bb158b93c94ed35c1970d610d1e2b34e26652cd90382271094f84c74dea96df0ec22e11e7c33996c73fcc2d822
2022-12-14T19:53:52.133545235Z INFO [12-14|19:53:52.133] Fetching state updates from Heimdall fromID=4 to=2020-05-30T23:54:22Z
2022-12-14T19:53:52.133578948Z INFO [12-14|19:53:52.133] Fetching state sync events queryParams="from-id=4&to-time=1590882862&limit=50"
2022-12-14T19:53:52.135049605Z INFO [12-14|19:53:52.134] StateSyncData Gas=0 Block-number=12992 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.152067646Z INFO [12-14|19:53:52.151] Fetching state updates from Heimdall fromID=4 to=2020-05-30T23:56:34Z
2022-12-14T19:53:52.152198357Z INFO [12-14|19:53:52.151] Fetching state sync events queryParams="from-id=4&to-time=1590882994&limit=50"
2022-12-14T19:53:52.176617455Z INFO [12-14|19:53:52.176] StateSyncData Gas=0 Block-number=13056 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.191060112Z INFO [12-14|19:53:52.190] Fetching state updates from Heimdall fromID=4 to=2020-05-30T23:58:46Z
2022-12-14T19:53:52.191083740Z INFO [12-14|19:53:52.190] Fetching state sync events queryParams="from-id=4&to-time=1590883126&limit=50"
2022-12-14T19:53:52.223836639Z INFO [12-14|19:53:52.223] StateSyncData Gas=0 Block-number=13120 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.236025906Z INFO [12-14|19:53:52.235] Fetching state updates from Heimdall fromID=4 to=2020-05-31T00:00:58Z
2022-12-14T19:53:52.236053406Z INFO [12-14|19:53:52.235] Fetching state sync events queryParams="from-id=4&to-time=1590883258&limit=50"
2022-12-14T19:53:52.269611566Z INFO [12-14|19:53:52.269] StateSyncData Gas=0 Block-number=13184 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.283199351Z INFO [12-14|19:53:52.283] Fetching state updates from Heimdall fromID=4 to=2020-05-31T00:03:10Z
2022-12-14T19:53:52.283737573Z INFO [12-14|19:53:52.283] Fetching state sync events queryParams="from-id=4&to-time=1590883390&limit=50"
2022-12-14T19:53:52.314141359Z INFO [12-14|19:53:52.314] StateSyncData Gas=0 Block-number=13248 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.325150782Z INFO [12-14|19:53:52.325] Fetching state updates from Heimdall fromID=4 to=2020-05-31T00:05:22Z
2022-12-14T19:53:52.325171075Z INFO [12-14|19:53:52.325] Fetching state sync events queryParams="from-id=4&to-time=1590883522&limit=50"
2022-12-14T19:53:52.354470271Z INFO [12-14|19:53:52.354] StateSyncData Gas=0 Block-number=13312 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.372354857Z INFO [12-14|19:53:52.372] Fetching state updates from Heimdall fromID=4 to=2020-05-31T00:07:34Z
2022-12-14T19:53:52.372389214Z INFO [12-14|19:53:52.372] Fetching state sync events queryParams="from-id=4&to-time=1590883654&limit=50"
2022-12-14T19:53:52.398246950Z INFO [12-14|19:53:52.398] StateSyncData Gas=0 Block-number=13376 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.413321099Z INFO [12-14|19:53:52.413] Fetching state updates from Heimdall fromID=4 to=2020-05-31T00:09:46Z
2022-12-14T19:53:52.413345355Z INFO [12-14|19:53:52.413] Fetching state sync events queryParams="from-id=4&to-time=1590883786&limit=50"
2022-12-14T19:53:52.437176855Z INFO [12-14|19:53:52.437] StateSyncData Gas=0 Block-number=13440 LastStateID=3 TotalRecords=0
2022-12-14T19:53:52.450356966Z INFO [12-14|19:53:52.450] Fetching state updates from Heimdall fromID=4 to=2020-05-31T00:11:58Z
```
There are a few ways to check the sync state of Bor. The simplest is using `curl`:
```bash theme={null}
curl 'localhost:8545/' \
--header 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0",
"method":"eth_syncing",
"params":[],
"id":1
}'
```
When you run this command, you'll see an output like this:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"currentBlock": "0x2eebf",
"healedBytecodeBytes": "0x0",
"healedBytecodes": "0x0",
"healedTrienodeBytes": "0x0",
"healedTrienodes": "0x0",
"healingBytecode": "0x0",
"healingTrienodes": "0x0",
"highestBlock": "0x1d4ee3e",
"startingBlock": "0x0",
"syncedAccountBytes": "0x0",
"syncedAccounts": "0x0",
"syncedBytecodeBytes": "0x0",
"syncedBytecodes": "0x0",
"syncedStorage": "0x0",
"syncedStorageBytes": "0x0"
}
}
```
This will indicate the `currentBlock` that’s been synced and also the `highestBlock` that we’re aware of. If the node is already synced, we should get `false`.
## Seeds and Bootnodes
The latest bor and heimdall seeds can be found [here](https://docs.polygon.technology/pos/reference/seed-and-bootnodes/). Using them will ensure that bor/heimdall is able to connect to peers quickly.
# Using GCP
Source: https://docs.polygon.technology/pos/how-to/full-node/full-node-gcp
Steps to deploy a Polygon PoS node in a Google Cloud Platform VM instance using the gcloud CLI.
This guide deploys a Polygon PoS node in a GCP VM instance. The examples use Ubuntu 20.04, though any modern Debian or Ubuntu LTS release (e.g., Debian 11) works.
This setup is currently only supported for mainnet. GCP support for deploying Amoy testnet nodes will be available soon.
## Deploy VM instance
You may use any of the following ways to create an instance in Google Cloud:
1. Google Cloud CLI, local or [Cloud Shell](https://cloud.google.com/shell)
2. Web Console
We only cover the first case in this guide. Let's start with deployment using Google Cloud CLI.
1. Follow ["Before you begin" section](https://cloud.google.com/compute/docs/instances/create-start-instance#before-you-begin) to install and configure gcloud command-line tool.
Pay attention to default region and zone, choose ones closer to you or your customers. You may use [gcping.com](https://gcping.com) to measure latency to choose the closest location.
2. Adjust the following command variables using your favorite editor prior to executing, when required:
* `POLYGON_NETWORK` - choose `mainnet` network to run.
* `POLYGON_NODETYPE` - choose `archive`,`fullnode` node type to run.
* `POLYGON_BOOTSTRAP_MODE` - choose bootstrap mode `snapshot` or `from_scratch`.
* `POLYGON_RPC_PORT` - choose JSON RPC bor node port to listen on, the default value is what used on VM instance creation and in firewall rules.
* `EXTRA_VAR` - choose Bor and Heimdall branches, use `network_version=mainnet-v1` with `mainnet` network and `network_version=testnet-v4` with `amoy` network.
* `INSTANCE_NAME` - the name of a VM instance with Polygon we are going to create.
* `INSTANCE_TYPE` - GCP [machine type](https://cloud.google.com/compute/docs/machine-types), default value is recommended, You may change it later if required.
* `BOR_EXT_DISK_SIZE` - additional disk size in GB to use with Bor, default value with `fullnode` is recommended, You may expand it later if required. You'll need 8192GB+ with `archive` node though.
* `HEIMDALL_EXT_DISK_SIZE` - additional disk size in GB to use with Heimdall, default value is recommended.
* `DISK_TYPE` - GCP [disk type](https://cloud.google.com/compute/docs/disks#disk-types), SSD is highly recommended. You may need to increase the total SSD GB quota in the region you are spinning up the node.
3. Use the following command to create an instance with the correct hardware and software requirements. In the example below, we deploy Polygon PoS `mainnet` from `snapshot` in the `fullnode` mode:
```bash theme={null}
export POLYGON_NETWORK=mainnet
export POLYGON_NODETYPE=fullnode
export POLYGON_BOOTSTRAP_MODE=snapshot
export POLYGON_RPC_PORT=8747
export GCP_NETWORK_TAG=polygon
export EXTRA_VAR=(bor_branch=v1.1.0 heimdall_branch=v1.0.3 network_version=mainnet-v1 node_type=sentry/sentry heimdall_network=${POLYGON_NETWORK})
gcloud compute firewall-rules create "polygon-p2p" --allow=tcp:26656,tcp:30303,udp:30303 --description="polygon p2p" --target-tags=${GCP_NETWORK_TAG}
gcloud compute firewall-rules create "polygon-rpc" --allow=tcp:${POLYGON_RPC_PORT} --description="polygon rpc" --target-tags=${GCP_NETWORK_TAG}
export INSTANCE_NAME=polygon-0
export INSTANCE_TYPE=e2-standard-8
export BOR_EXT_DISK_SIZE=1024
export HEIMDALL_EXT_DISK_SIZE=500
export DISK_TYPE=pd-ssd
gcloud compute instances create ${INSTANCE_NAME} \
--image-project=ubuntu-os-cloud \
--image-family=ubuntu-2004-lts \
--boot-disk-size=20 \
--boot-disk-type=${DISK_TYPE} \
--machine-type=${INSTANCE_TYPE} \
--create-disk=name=${INSTANCE_NAME}-bor,size=${BOR_EXT_DISK_SIZE},type=${DISK_TYPE},auto-delete=no \
--create-disk=name=${INSTANCE_NAME}-heimdall,size=${HEIMDALL_EXT_DISK_SIZE},type=${DISK_TYPE},auto-delete=no \
--tags=${GCP_NETWORK_TAG} \
--metadata=user-data='
#cloud-config
bootcmd:
- screen -dmS polygon su -l -c bash -c "curl -L https://raw.githubusercontent.com/0xPolygon/node-ansible/master/install-gcp.sh | bash -s -- -n '${POLYGON_NETWORK}' -m '${POLYGON_NODETYPE}' -s '${POLYGON_BOOTSTRAP_MODE}' -p '${POLYGON_RPC_PORT}' -e \"'${EXTRA_VAR}'\"; bash"'
```
The instance should be created and live in a couple of minutes.
## Login to VM
It will take a couple of minutes to install all the required software, and a couple of hours to download a snapshot when chosen.
* You should see working `bor` and `heimdalld` processes filling up additional drives. You may run the following commands to check it.
```bash theme={null}
gcloud compute ssh ${INSTANCE_NAME}
# inside the connected session
sudo su -
ps uax|egrep "bor|heimdalld"
df -l -h
```
* You may use the following command to watch the installation progress, it's really handy in case of `snapshot` bootstrap:
```bash theme={null}
# inside the connected session
screen -dr
```
Use `Control+a d` key combination to disconnect from progress review.
* You may use the following commands to get Bor and Heimdall logs:
```bash theme={null}
# inside the connected session
journalctl -fu bor
journalctl -fu heimdalld
```
Blockchain data is saved onto additional drives which are kept by default on VM instance removal. You need to remove additional disks manually if you don't need this data anymore.
At the end, you will get an instance as shown in the diagram below.
# Using packages
Source: https://docs.polygon.technology/pos/how-to/full-node/full-node-packages
Steps to install Heimdall and Bor packages, configure, and start a Polygon PoS full node.
## Overview
* Prepare the Full Node machine.
* Install Heimdall and Bor packages on the Full Node machine.
* Configure the Full node.
* Start the Full node.
It is essential to follow the outlined sequence of actions precisely, as any deviation may lead to potential issues.
## Install packages
### Prerequisites
* One machine is needed.
* Bash is installed on the machine.
### Heimdall
* Install the default latest version of sentry for Mainnet:
```shell theme={null}
curl -L https://raw.githubusercontent.com/0xPolygon/install/heimdall-v2/heimdall-v2.sh | bash -s --
```
or install a specific version, node type (`sentry` or `validator`), and network (`mainnet` or `amoy`). All release versions can be found on
[Heimdall GitHub repository](https://github.com/0xPolygon/heimdall-v2/releases).
```shell theme={null}
# Example:
# curl -L https://raw.githubusercontent.com/maticnetwork/install/heimdall-v2/heimdall-v2.sh | bash -s -- v0.2.15 mainnet validator
```
### Bor
* Install the default latest version of sentry for Mainnet:
```shell theme={null}
curl -L https://raw.githubusercontent.com/0xPolygon/install/main/bor.sh | bash -s --
```
or install a specific version, node type (`sentry` or `validator`), and network (`mainnet` or `amoy`). All release versions could be found on
[Bor Github repository](https://github.com/0xPolygon/bor/releases).
```shell theme={null}
# Example:
# curl -L https://raw.githubusercontent.com/0xPolygon/install/main/bor.sh | bash -s -- v2.2.9 mainnet sentry
```
## Configuration
### Configure Heimdall
* Initialize Heimdall configs
```shell theme={null}
# For mainnet
sudo -u heimdall heimdalld init --chain-id= --home /var/lib/heimdall
```
Where `CHAIN_ID` is `heimdallv2-80002` for `amoy` and `heimdallv2-137` for `mainnet`
Then, edit the configuration files under `/var/lib/heimdall/config`\
The templates for each supported network are available [here](https://github.com/0xPolygon/heimdall-v2/tree/develop/packaging/templates/config)\
Download the `genesis.json` file and place it under `/var/lib/heimdall/config/`
Use the following commands based on your target network:
```bash theme={null}
cd /var/lib/heimdall/config
curl -fsSL -o genesis.json
```
Where `BUCKET_URL` is
* [https://storage.googleapis.com/amoy-heimdallv2-genesis/migrated\_dump-genesis.json](https://storage.googleapis.com/amoy-heimdallv2-genesis/migrated_dump-genesis.json) for amoy
* [https://storage.googleapis.com/mainnet-heimdallv2-genesis/migrated\_dump-genesis.json](https://storage.googleapis.com/mainnet-heimdallv2-genesis/migrated_dump-genesis.json) for mainne
* You will need to change a few details in the config files.
* Templates for each supported network are available [here](https://github.com/0xPolygon/heimdall-v2/tree/develop/packaging/templates/config)
### Configure service files for Bor and Heimdall
After successfully installing Bor and Heimdall through [packages](#install-packages), their service file could be found under `/lib/systemd/system`, and Bor's config file could be found under `/var/lib/bor/config.toml`.
You will need to check and modify these files accordingly.
* Make sure the chain is set correctly in `/lib/systemd/system/heimdalld.service` file. Open the file with following command `sudo vi /lib/systemd/system/heimdalld.service`
* In the service file, set `--chain` to `mainnet` or `amoy` accordingly
Save the changes in `/lib/systemd/system/heimdalld.service`.
* Make sure the chain is set correctly in `/var/lib/bor/config.toml` file. Open the file with following command `sudo vi /var/lib/bor/config.toml`
* In the config file, set `chain` to `mainnet` or `amoy` accordingly.
* To enable Archive mode you can optionally enable the following flags:
```js theme={null}
gcmode "archive"
[jsonrpc]
[jsonrpc.ws]
enabled = true
port = 8546
corsdomain = ["*"]
```
Save the changes in `/var/lib/bor/config.toml`.
## (Optional) Start Heimdall from snapshot
In case you want to start Heimdall from a snapshot,\
you can download it, and extract in the `data` folder.
Examples of snapshots can be found here [https://all4nodes.io/Polygon](https://all4nodes.io/Polygon), and they are managed by the community.
e.g.:
```bash theme={null}
lz4 -dc polygon-heimdall-24404501-25758577.tar.lz4 | tar -x
```
## Start services
Reloading service files to make sure all changes to service files are loaded correctly.
```shell theme={null}
sudo systemctl daemon-reload
```
Verify the installation by checking the Heimdall version on your machine:
```bash theme={null}
heimdalld version
```
It should return the version of Heimdall you installed.
Start Heimdall, Heimdall rest server, and Heimdall bridge.
```shell theme={null}
sudo service heimdalld start
```
You can also check Heimdall logs with the following command:
```shell theme={null}
journalctl -u heimdalld.service -f
```
At this point, please make sure that *Heimdall is synced completely*, and only then start Bor. If you start Bor without Heimdall syncing completely, you will run into issues frequently.
To check if Heimdall is synced:
* On the remote machine/VM, run `curl localhost:26657/status`
* In the output, `catching_up` value should be `false`
Now, once Heimdall is synced, run:
```shell theme={null}
sudo service bor start
```
You can check Bor logs using the following command:
```shell theme={null}
journalctl -u bor.service -f
```
# Change owner and signer address
Source: https://docs.polygon.technology/pos/how-to/operate-validator-node/change-signer-address
Steps to transfer your validator NFT to a new owner address or update the signer address for your Polygon PoS validator node.
Steps to change the owner or signer address on your validator node.
## Change the owner address
1. Access StakingNFT smart contract.
* Mainnet: [https://etherscan.io/address/0x47Cbe25BbDB40a774cC37E1dA92d10C2C7Ec897F#writeContract](https://etherscan.io/address/0x47Cbe25BbDB40a774cC37E1dA92d10C2C7Ec897F#writeContract)
* Amoy: [https://sepolia.etherscan.io/address/0x72CF5618142Eb369E75ec6529A907e9A6Fe99bB7#writeContract](https://sepolia.etherscan.io/address/0x72CF5618142Eb369E75ec6529A907e9A6Fe99bB7#writeContract)
2. Click on the **Connect to Web3** button and login using the owner address of your validator node.
3. You will see a list of functions appear. Click on **`safeTransferFrom`** function. This will be 5th function in the list. There will be 3 data fields that you will need to add information.
4. Here:
* **from (address)** is your current owner address
* **to (address**) is your new owner Address
* **tokenId** is your validator ID
5. Fill in the relevant information and select **Write**. You will be prompted to sign a transaction. Ensure that you have sufficient ETH to make the transaction.
6. Upon signing the transaction your validator NFT will be transferred to the new owner address.
7. Log in to the [staking dashboard](https://staking.polygon.technology/) with the new owner address to verify the changes.
## Change the signer address
This guide refers to your current validator node as Node 1 and your new validator node as Node 2.
1. Log in to the [staking dashboard](https://staking.polygon.technology/) with the Node 1 address.
2. On your profile, select **Edit Profile**.
3. In the **Signer's address** field, enter the Node 2 address.
4. In the **Signer's public key** field, enter the Node 2 public key.
To get the public key, run the following command on the validator node:
```sh theme={null}
heimdalld show-account
```
Selecting **Save** will save your new details for your node. This essentially means that Node 1 will be your address that controls the stake, where the rewards will be sent to, etc. And Node 2 will now be performing activities like signing blocks, signing checkpoints, etc.
Alternatively, the signer address can be updated via contract using the following process:
1. Access StakeManagerProxy smart contract.
* Mainnet: [https://etherscan.io/address/0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908#writeProxyContract](https://etherscan.io/address/0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908#writeProxyContract)
* Amoy: [https://sepolia.etherscan.io/address/0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908#writeProxyContract](https://sepolia.etherscan.io/address/0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908#writeProxyContract)
2. Click on the **Connect to Web3** button and login using the owner address of your validator node.
3. You will see a list of functions appear. Click on **`updateSigner`** function. This will be 47th function in the list. There will be 2 data fields that you will need to add information.
4. Here:
* **validatorId** is the associated ID with your validator
* **signerPubkey** is the signer pubkey for your new signer address.
To get the public key, run the following command on the validator node:
```sh theme={null}
heimdalld show-account
```
Please note that the first 2 characters after `0x` should be removed when you enter the new signer pubkey in the contract.
5. Fill in the relevant information and select **Write**. You will be prompted to sign a transaction. Ensure that you have sufficient ETH to make the transaction.
# Next steps
Source: https://docs.polygon.technology/pos/how-to/operate-validator-node/next-steps
Steps to stake tokens, set commission, claim rewards, and complete onboarding after your validator node joins the Polygon PoS active set.
There is limited space for accepting new validators. New validators can only join the active set when an already active validator unbonds. Check out the following links for more information and to apply for a validator slot:
* [Admission form](https://polygoncommunity.typeform.com/validatorshub?typeform)
* [Admission dashboard](https://play.validatrium.club/public-dashboards/1b29d3bbdcd14007a0858b68dee76bdd?orgId=1)
Once your validator node is onboarded into the active set, do the following:
* Log in to the [staking dashboard](https://staking.polygon.technology/) with the owner address.
* Go to my account, and click on edit details below the validator name.
* Click on Profile Details and update your **name**, **website**, **description**, **logo URL**, and click on **Save Profile Details**.
## Stake tokens
### Initial staking
1. Access the [validator dashboard](https://staking.polygon.technology/validators/).
2. Log in with your wallet. You can use a popular wallet such as MetaMask. Make sure you login using the owner address, and that you have POL tokens in the wallet.
3. Select **Become a Validator**. You will be asked to set up your node. If you haven't already set up your node by now, you will need to do so, else if you proceed ahead you will receive an error when you attempt to stake.
4. On the next screen, add your validator details, the commission rate, and the staking amount.
5. Select **Stake Now**.
6. Now, you'll be prompted for three confirmations to send the transaction. Once complete, your POL tokens will be added to the staked amount on the validator node. The three confirmations include:
* Approve Transaction: This approves your stake transaction.
* Stake: Confirms your stake transaction.
* Save: Saves your validator details.
For the changes to take effect on the [staking dashboard](https://staking.polygon.technology/account), it requires a *minimum of 12 block confirmations*.
### Add stake
1. Access the [validator dashboard](https://staking.polygon.technology/validators/).
2. Log in with your wallet. You can use a popular wallet such as MetaMask. Make sure you login using the owner address, and that you have POL tokens in the wallet.
3. Select **Add more Stake**.
4. Enter the amount, and select **Add More Stake**.
5. Now, you'll be prompted for three confirmations to send the transaction. Once complete, your POL tokens will be added to your staked amount on the validator node. The three confirmations include:
* Approve Transaction: This approves your stake transaction.
* Stake: Confirms your stake transaction.
* Save: Saves your validator details.
For the changes to take effect on the [staking dashboard](https://staking.polygon.technology/account), it requires a *minimum of 12 block confirmations*.
## Set commission rate
You can set up and change your commission as a validator.
A validator is entitled to charge any commission rate. The minimum commission would be 0% and the maximum commission would be 100% of the rewards earned.
You set up the commission rate as part of your initial [validator staking process](#initial-staking).
## Changing your commission rate
You are allowed to freely adjust the commission rate as and when necessary.
As a validator, it is one of your responsibilities to inform the community on commission changes. See [Validator Responsibilities](/pos/get-started/becoming-a-validator/#validator-responsibilities).
Follow the steps below to change your commission rate:
1. With your owner address, login to the [staking dashboard](https://staking.polygon.technology/).
2. On your profile, select **Edit Profile**.
3. In the **Commission** field, enter your new commission rate.
Once you have confirmed and signed the transaction your commission rate will be set.
Note that once the commission is updated, there is a cool down period of *80 checkpoints*.
## Claim validator rewards
Once you are set up and staked as a validator, you will earn rewards for performing validator duties. When you perform validator duties dutifully, you get rewarded.
To claim rewards you can go to your [validator dashboard](https://staking.polygon.technology/account).
You will see two buttons on your profile:
* **Withdraw Reward**
* **Restake Reward**
### Withdraw Reward
As a validator, you earn rewards as long as you are performing your validator duties correctly.
Selecting **Withdraw Reward** will get your rewards back to your wallet.
The dashboard will update after *12 block confirmations*.
### Restake Reward
Restaking your rewards is an easy way to increase your stake as a validator.
Selecting **Restake Reward** will restake your reward and increase your stake.
The dashboard will update after *12 block confirmations*.
## Common operations
You can use the following commands to check if your validator node is set up correctly.
### Check validator account
Run the following command *on your validator node* to check if the account is set up correctly:
```sh theme={null}
heimdalld show-account
```
The output should appear in the following format:
```json theme={null}
{
"address": "0x6c468CF8c9879006E22EC4029696E005C2319C9D",
"pub_key": "0x04b12d8b2f6e3d45a7ace12c4b2158f79b95e4c28ebe5ad54c439be9431d7fc9dc1164210bf6a5c3b8523528b931e772c86a307e8cff4b725e6b4a77d21417bf19"
}
```
This will display your address and public key for your validator node. Note that *this address must match with your signer address on Ethereum*.
### Show private key
Run the following command *on your validator node* to check if the private key configured correctly:
```sh theme={null}
heimdalld show-privatekey
```
The output should appear in the following format:
```json theme={null}
{
"priv_key": "0x********************************************************"
}
```
### Check the balance
To check the balance of your address, run the following command:
```sh theme={null}
heimdalld query auth account SIGNER_ADDRESS [flags]
```
where,
* `SIGNER_ADDRESS`: Your signer address.
The following output should appear:
```bash theme={null}
'@type': /cosmos.auth.v1beta1.BaseAccount
account_number: "0"
address: 0x6c468cf8c9879006e22ec4029696e005c2319c9d
pub_key:
'@type': /cosmos.crypto.secp256k1.PubKey
key: ...
sequence: "0"
```
# Top up Heimdall fee
Source: https://docs.polygon.technology/pos/how-to/operate-validator-node/topup-heimdall-fee
Steps to top up the Heimdall fee for a Polygon PoS validator using the Staking UI or Etherscan.
Top up your Heimdall fee using the [Polygon Staking UI](https://staking.polygon.technology/account) (recommended) or manually via Etherscan.
1. Go to [https://staking.polygon.technology/](https://staking.polygon.technology/) and login using your owner address.
2. Go to My Account Section and click on Add Heimdall Fees.
3. Enter the amount you want to add and click on continue.
4. Click on Delegate.
5. Approve the transaction on your wallet and you will see a message when your heimdall fee is added. Please note that it takes some time to reflect the fee in the account.
Alternatively, you can also do it manually by following the steps below. This requires basic Etherscan knowledge and key details like the validator signer address.
1. Head over to [Etherscan.io](https://etherscan.io)
2. Goto `POL Ecosystem Token` Contract. Mainnet - [0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6](https://etherscan.io/address/0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6#code), Amoy - [0x44499312f493F62f2DFd3C6435Ca3603EbFCeeBa](https://sepolia.etherscan.io/address/0x44499312f493F62f2DFd3C6435Ca3603EbFCeeBa#code)
3. Under the **Code** menu, select **Write Contract** and connect your web3 wallet using **Connect to Web3** button.
4. Goto `approve` function, enter the spender as the address you want to use to topup your heimdall fee, and the amount.
5. Select **Write** to sign the transaction.
6. Now enter the `StakeManagerProxy` contract address in the search box: Mainnet - [0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908](https://etherscan.io/address/0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908), Amoy - [0x4AE8f648B1Ec892B6cc68C89cc088583964d08bE](https://sepolia.etherscan.io/address/0x4AE8f648B1Ec892B6cc68C89cc088583964d08bE#code)
7. Under the **Code** menu, select the **Write as Proxy** tab. Connect your Web3 wallet using the **Connect to Web3** button.
8. Scroll down to the `topUpForFee` method (#26 in the list) and select it. You will then see a display similar to the screenshot below.
9. Fill in the details:
* `user`: Validator's Signer Address
* `heimdallFee`: Top-up fee (**minimum 1 POL**)
10. After filling in the details, select **Write** to sign the transaction.
Your Heimdall fee will be updated soon after the transaction completes.
# Validator performance requirements
Source: https://docs.polygon.technology/pos/how-to/operate-validator-node/validator-performance
Reference for the validator performance parameters, benchmarks, grace periods, and forced unstaking process defined in PIP-4.
Reference for validator performance parameters agreed to in [PIP-4](https://snapshot.org/#/polygonvalidators.eth/proposal/0x1e9d79b6406870ebd0b66ac256b59012f97f3f4d652052681c6fb1077e251804). These parameters and health statuses provide objective performance benchmarks for validators.
### Parameters
A validator’s performance is measured based on the checkpoints it signed over a fixed monitoring period. Performance is measured on a rolling basis at each new checkpoint to provide an objective figure. This figure is then measured against a benchmark of the total network performance in the monitoring period, as detailed below.
* Monitoring Period (“MP") = previous 700 checkpoints, updated every new checkpoint.
* Take % of checkpoints signed by each validator in the MP and find the median.
* Multiply the median average by an agreed multiple = Performance Benchmark (“PB”).
* At each checkpoint, calculate the % of checkpoints signed in the MP by single validators and measure against the PB.
### Performance benchmark
To facilitate the transition, there will be a slightly lower benchmark around the first two months while validators become accustomed to the parameters.
* PB1 → 95% of the median average of the last 700 checkpoints signed by the validator set (first 2,800 checkpoints)
* PB2 → 98% of the median average of last checkpoints signed by validator set (continues thereafter)
### Deficient validator process
* If validator performance is below PB in the MP → Grace Period 1 (“GP1”).
* If validator is in GP and still below PB after 700 checkpoints → Notice of Deficiency (“NOD”), validator enters into Grace Period 2 (“GP2”).
* If validator is in GP2 and still below PB after 700 checkpoints → Final Notice (“FN”), the validator will be unstaked per PIP-4.
Each GP is an additional 700 checkpoints, allowing a validator to bring their performance back above the PB. If the deficiency is corrected within the GP, there will be no further action. Failure to match, or out perform the performance benchmark, at the end of GP2 will result in a public NOD that will be displayed on the staking dashboard.
The validator will have a 700 checkpoint period to correct the deficiency in GP2. If the deficiency is fixed within the NOD period, then no further action will occur. However, the NOD would remain public. Failure to improve after GP2 would result in the issuance of an FN of the community's intent to implement a forced exit procedure by offboarding the validator from the network by unbonding their stake.
You can quickly check a validator's health status, whether it is *Healthy*, on *Grace Period*, or \*\* on the **All Validators** table or on each validator page.
### Public notices
The **Public Notices** page shows the recent notices and messages sent to the community of validators.
This is what a notice message should look like:
### Forced unstaking
The unstaking of the deficient validator would be done as follows:
Call the `ForceUnstake` function in Polygon Commitchain Contract: `0xFa7D2a996aC6350f4b56C043112Da0366a59b74c`
# Prerequisites
Source: https://docs.polygon.technology/pos/how-to/prerequisites
System requirements, port configuration, and setup checklist for running a Polygon PoS full node or validator node.
## Node system requirements
Please note that all system requirements listed below are presented in a *Minimum/Recommended* format.
### Mainnet specs
| Node type | RAM | CPU | Storage | Network bandwidth |
| --------------------- | ----------- | -------------- | ----------------------------------------------------------------------------- | ----------------- |
| Full Node/Sentry Node | 32 GB/64 GB | 8 core/16 core | 4 TB/6 TB | 1 Gbit/s |
| Validator Node | 32 GB/64 GB | 8 core/16 core | 4 TB/6 TB | 1 Gbit/s |
| Archive Node (Erigon) | 64 GB | 16 core | 16 TB(`io1` or above with at least 20k+ iops and RAID-0 based disk structure) | 1 Gbit/s |
### Testnet (Amoy) specs
| Node type | RAM | CPU | Storage | Network bandwidth |
| --------------------- | ---------- | -------------- | ---------------------------------------------------------------------------------- | ----------------- |
| Full Node/Sentry Node | 8 GB/16 GB | 8 core/16 core | 1 TB/2 TB | 1 Gbit/s |
| Validator Node | 8 GB/16 GB | 8 core/16 core | 1 TB/2 TB | 1 Gbit/s |
| Archive Node (Erigon) | 16 GB | 16 core | 1 TB/2 TB (`io1` or above with at least 20k+ iops and RAID-0 based disk structure) | 1 Gbit/s |
## Downloading the snapshot
It is recommended that you keep your snapshots handy before setting up the node. Link to the snapshot documentation [here](https://docs.polygon.technology/pos/how-to/snapshots/).
## Open necessary ports
### Sentry/full nodes
| Port | Description |
| :---------------------: | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `26656` | Heimdall service connects your node to another node’s Heimdall service using this port. |
| `30303` | Bor service connects your node to another node’s Bor service using this port. |
| `22` | For the validator to be able to SSH from wherever they are. |
| `26660` | Prometheus port for Tendermint/Heimdall. Not required to be opened to the public. Only allow for the monitoring systems (Prometheus/Datadog). |
| `7071` | Metric port for Bor. Only needs to be opened for the Monitoring system. |
| `8545`, `8546`, `1317` | Can be opened for Bor HTTP RPC, Bor WS RPC, and Heimdall API respectively; but only if really necessary. |
### Validator nodes
| Port | Description |
| :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `22` | Opening this to the public is not a good idea as the default SSH port 22 is prone to attacks. It is better to secure it by allowing it only in a closed network (VPN). |
| `30303` | To be opened to only Sentry to which the validator is connected for Bor P2P discovery. |
| `26656` | To be opened to only Sentry to which the validator is connected for Heimdall/Tendermint P2P discovery. |
| `26660` | Prometheus port for Tendermint/Heimdall. Not required to be opened to the public. Only allow for the monitoring systems (Prometheus/Datadog). |
| `7071` | Metric port for Bor. Only needs to be opened for the monitoring system. |
## Install RabbitMQ
This step is only relevant for validator nodes.
Before setting up your validator node, it’s advisable to install the RabbitMQ service. You can use the following commands to set up RabbitMQ (if it’s not already installed):
```bash theme={null}
sudo apt-get update
sudo apt install build-essential
sudo apt install erlang
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.10.8/rabbitmq-server_3.10.8-1_all.deb
sudo dpkg -i rabbitmq-server_3.10.8-1_all.deb
```
## Connect to Ethereum RPC endpoint
This step is only relevant for validator nodes.
Validator nodes need to connect to an Ethereum RPC endpoint. You may use your own Ethereum node, or utilize [external infrastructure providers](https://www.alchemy.com/chain-connect/chain/ethereum).
## Mandatory checklist for validators
Please follow the below checklist in order to set up your validator node using binaries, Ansible, or packages.
| Checklist | Binaries | Ansible | Packages |
| ------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Machines required | 2 Machines - *sentry* & *validator* | 3 Machines - *local machine*, *sentry* and *validator* | 2 Machines - *sentry* & *validator* |
| Install Go packages | Yes | No | No |
| Install Python | No | Yes (only on the local machine where the Ansible Playbook runs) | No |
| Install Ansible | No | Yes (only on one machine) | No |
| Install Bash | No | No | Yes |
| Run Build Essential | Yes | No | No |
| Node setup | [Using binaries](https://docs.polygon.technology/pos/how-to/validator/validator-binaries/) | [Using Ansible](https://docs.polygon.technology/pos/how-to/validator/validator-ansible/) | [Using packages](https://docs.polygon.technology/pos/how-to/validator/validator-packages/) |
# Sync node using snapshots
Source: https://docs.polygon.technology/pos/how-to/snapshots
Steps to download and apply community snapshots to sync a Polygon PoS sentry, validator, or full node without syncing from scratch.
Use snapshots to sync a new node in hours rather than days. This applies to sentry, validator, and full nodes for both Heimdall and Bor.
## Community snapshots
Polygon PoS has transitioned to a community-driven model for snapshots. Active community members now contribute to provide snapshots. Some of these members include:
| Name | Available snapshots | Note |
| ---------------------------------------------------------------------- | --------------------- | -------------------------------------------- |
| Stakecraft | Mainnet, Amoy, Erigon | Support for Erigon archive snapshot |
| [PublicNode (by Allnodes)\*](https://publicnode.com/snapshots#polygon) | Mainnet, Amoy | Support for PBSS + PebbleDB enabled snapshot |
| Stakepool | Mainnet, Amoy | - |
| Vaultstaking | Mainnet | - |
| Girnaar Nodes | Amoy | - |
> *\*The PBSS + PebbleDB snapshot provided by PublicNode is currently in the beta phase.*
Visit [All4nodes.io](https://all4nodes.io/Polygon) for a comprehensive list of community snapshots.
## Downloading and using client snapshots
To begin, ensure that your node environment meets the **prerequisites** outlined [here](/pos/how-to/full-node/full-node-binaries/).
The majority of snapshot providers have also outlined the steps that need to be followed to download and use their respective client snapshots. Navigate to [All4nodes](https://all4nodes.io/Polygon) to view the snapshot source.
In case the steps are unavailable or the procedure is unclear, the following tips will come in handy:
* You can use the `wget` command to download and extract the `.tar` snapshot files. For example:
```bash theme={null}
wget -O - snapshot_url_here | tar -xvf -C /target/directory
```
* Configure your client's `datadir` setting to match the directory where you downloaded and extracted the snapshot data. This ensures the `systemd` services can correctly register the snapshot data when the client is spun up.
* To maintain your client's default configuration settings, consider using symbolic links (symlinks).
## Example
Let's say you have mounted your block device at `~/snapshots` and have downloaded and extracted the chain data into the `heimdall_extract` directory for Heimdall, and into the `bor_extract` directory for Bor. Use the following commands to register the extracted data for Heimdall and Bor `systemd` services:
```bash theme={null}
# remove any existing datadirs for Heimdall and Bor
rm -rf /var/lib/heimdall/data
rm -rf /var/lib/bor/data/bor/chaindata
# rename and setup symlinks to match default client datadir configs
mv ~/snapshots/heimdall_extract ~/snapshots/data
mv ~/snapshots/bor_extract ~/snapshots/chaindata
sudo ln -s ~/snapshots/data /var/lib/heimdall
sudo ln -s ~/snapshots/chaindata /var/lib/bor/data/bor
# bring up clients with all snapshot data properly registered
sudo service heimdalld start
# wait for Heimdall to fully sync then start Bor
sudo service bor start
```
Ensure that the Bor and Heimdall user files have appropriate permissions to access the `datadir`. To set correct permissions for Bor, execute `sudo chown -R bor:nogroup /var/lib/heimdall/data`. Similarly, for Heimdall, run `sudo chown -R heimdall:nogroup /var/lib/bor/data/bor`
## Recommended disk size guidance
### Polygon Amoy testnet
| Metric | Calculation Breakdown | Value |
| --------------------------------- | ------------------------------------ | ------- |
| approx. compressed total | 250 GB (Bor) + 35 GB (Heimdall) | 285 GB |
| approx. data growth daily | 10 GB (Bor) + 0.5 GB (Heimdall) | 10.5 GB |
| approx. total extracted size | 350 GB (Bor) + 50 GB (Heimdall) | 400 GB |
| suggested disk size (2.5x buffer) | 400 GB \* 2.5 (natural chain growth) | 1 TB |
### Polygon mainnet
| Metric | Calculation Breakdown | Value |
| --------------------------------- | --------------------------------- | ------- |
| approx. compressed total | 3000 GB (Bor) + 500 GB (Heimdall) | 3500 GB |
| approx. data growth daily | 100 GB (Bor) + 5 GB (Heimdall) | 105 GB |
| approx. total extracted size | 4 TB (Bor) + 500 GB (Heimdall) | 4.5 TB |
| suggested disk size (2.5x buffer) | 4 TB \* 2 (natural chain growth) | 8 TB |
### Polygon Amoy Erigon archive
| Metric | Calculation Breakdown | Value |
| --------------------------------- | ------------------------------------ | ------ |
| approx. compressed total | 210 GB (Erigon) + 35 GB (Heimdall) | 245 GB |
| approx. data growth daily | 4.5 GB (Erigon) + 0.5 GB (Heimdall) | 5 GB |
| approx. total extracted size | 875 GB (Erigon) + 50 GB (Heimdall) | 925 GB |
| suggested disk size (2.5x buffer) | 925 GB \* 2.5 (natural chain growth) | 2.5 TB |
## Recommended disk type and IOPS guidance
* Disk IOPS will affect the speed of downloading/extracting snapshots, getting in sync, and performing LevelDB compaction.
* To minimize disk latency, direct-attached storage is ideal.
* In AWS, when using gp3 disk types, we recommend provisioning IOPS of 16,000 and throughput of 1,000. This minimizes costs while providing significant performance benefits. io2 EBS volumes with matching IOPS and throughput values offer similar performance.
* For GCP, we recommend using performance (SSD) persistent disks (`pd-ssd`) or extreme persistent disks (`pd-extreme`) with similar IOPS and throughput values as mentioned above.
# Known issues and errors
Source: https://docs.polygon.technology/pos/how-to/troubleshoot/known-issues
Diagnosis and resolution steps for common Bor and Heimdall errors encountered while running a Polygon PoS validator node.
Reference for diagnosing and resolving common issues on Polygon PoS validator nodes, organized by service.
## Bor
### Bor is unable to connect to peers
Bor stops importing new blocks, with logs displaying messages similar to the following:
```js theme={null}
Aug 19 13:33:35 polygon-mainnet-validator-backup-4 bor[124475]: INFO [08-19|13:33:35.123] Looking for peers peercount=0 tried=0 static=7
Aug 19 13:33:36 polygon-mainnet-validator-backup-4 bor[124475]: INFO [08-19|13:33:36.916] Whitelisting milestone deferred err="chain out of sync"
Aug 19 13:33:48 polygon-mainnet-validator-backup-4 bor[124475]: INFO [08-19|13:33:48.916] Whitelisting milestone deferred err="chain out of sync"
```
##### Solution
* Increase `maxpeer` count to 200
* Add the bootnodes under static and trusted nodes
* If this doesn’t resolve the issue, try adding the peers manually using the IPC console
### Error: Bad block/Invalid Merkle
A bad block or invalid Merkle root error occurs when the Heimdall and Bor layers are not in sync. Heimdall, as the consensus layer for Polygon POS chain, directs Bor to create blocks accordingly. A bad block error occurs when the Bor moves ahead to create a block which has not been directed by Heimdall. This causes an invalid hash being created, and hence results in an invalid Merkle root.
##### Solution 1
Restart the Bor service using the following command:
```bash theme={null}
sudo service bor restart
```
Typically a restart of the Bor service should resolve the problem, and that's because restarting causes Bor to reconnect with Heimdall, start syncing, and create blocks correctly.
If restarting the Bor service does not fix the problem, try the next option.
##### Solution 2
Make the following checks:
* Check if your Heimdall and REST servers are running. The Heimdall service might have stopped, and thus causing the bad block issue on Bor.
* Check the logs for your Heimdall first using the following command:
```bash theme={null}
journalctl -u heimdalld -f
```
* Check if everything is working correctly.
* Restart the services that are not running. This should cause Bor to automatically resolve the problem
If restarting both the Bor and Heimdall services doesn't solve the problem, it could be that Bor is stuck on a particular block.
##### Solution 3
Check the bad block in logs for Bor.
* Check Bor logs with this command:
```bash theme={null}
journalctl -u bor -f
```
The bad block is typically displayed in the logs as shown in the below figure:
* Note the bad block number.
* Convert the block number to a hexadecimal number.
Use this [tool](https://www.rapidtables.com/convert/number/decimal-to-hex.html) to convert the block number to a hexadecimal number.
* Roll back the chain by a few hundred blocks, i.e., set Bor at the right block height using the `debug.setHead()` function. Use the following command:
```bash theme={null}
bor attach ./.bor/data/bor.ipc
> debug.setHead("0xE92570")
```
The `debug.setHead()` function allows Bor to set the tip at a particular block height, resyncing from a previous block.
The command should return a `null` upon successful execution. Once this occurs, you can resume monitoring Bor to verify whether the chain progresses beyond the previously problematic block.
If none of these solutions work for you, please contact the Polygon Support team immediately.
### Issue: Bor synchronization is slow
If Bor synchronization is slow, it may be caused by one or more of the following factors:
* The node is running on a fork - means at certain point the block production was done by forking on a different block and that has impacted the further block production.
* The machine is not working at optimum levels and could be with insufficient resources. This can be addressed by checking the following:
* IOPS
* IOPS stands for Input/Output state of cycle.
* The rate of reading is usually higher than write speed.
* 6000 is the recommended range for IOPS.
* Processing power
* Processor has to be 8 or 16 core.
* RAM: 32 GB is the minimum; 64 GB is recommended.
* Block import should be more than 2 block for every second.
* Node sync rate should be at 15-20 blocks every 8 secs.
##### Solution
Since the issue is likely due to insufficient hardware resources, consider upgrading to double the current specs.
### Validator Bor is stuck on a block for a long time
This implies that the Bor service on your sentry node is also stuck because your validator gets information from your sentry.
##### Solution
* Please check the Bor logs on your sentry and see if everything is normal and functional.
* Restart the Bor service on your sentry node, then simultaneously restart the Bor service on your validator.
### Retrying again in 5 seconds to fetch data from Heimdall path=bor/span/1
These logs in Bor mean that it cannot connect to Heimdall. Heimdall appears to be out of sync, and thus it lacks the data needed by Bor.
##### Solution
The recommended approach is to clear the historical data from both Heimdall and Bor, then [resync using a snapshot](/pos/how-to/snapshots/).
Verify the following:
1. Are Heimdall logs normal, or do they show any errors?
2. Confirm Heimdall is fully synced by running: curl localhost:26657/status
3. Check whether Heimdall is connected to other peers.
```bash theme={null}
curl localhost:26657/net_info? | jq .result.n_peers
```
If there are no peers, verify that the *seeds or persistent peers are correctly configured on Heimdall*, and ensure that *port 26656 is open*.
### etherbase missing: etherbase must be explicitly specified
To fix this issue, the signer address that is used to mine must be added in the `miner.etherbase` section in the `config.toml` file.
### Error: Failed to unlock account (0x…) No key for given address or file
This error occurs because the path to the `password.txt` file is incorrect. Follow the steps below to resolve this issue.
##### Solution
1. Kill the Bor process.
2. Copy the Bor keystore file to: `/var/lib/bor/keystore/`
3. And the `password.txt` file to: `/var/lib/bor/password.txt`
4. Ensure that the user 'Bor' has permission to access the password.txt file. You can do this by running the following command: `sudo chown -R bor:nogroup /var/lib/bor/`
### Steps to prune the node
Follow the steps below to prune your node:
1. Check your Bor data size before pruning using the following command:
```bash theme={null}
du -sh /usr/bin/bor
```
2. Stop Bor.
```bash theme={null}
sudo service bor stop
```
3. Start `tmux` to ensure that even if your SSH connection is reset, the process is running on the remote machine using `tmux`.
4. Start pruning.
```bash theme={null}
sudo bor snapshot prune-state --datadir /usr/bin/bor
```
The default --datadir is `/usr/bin/bor`.
5. Once the pruning is completed, you will see success logs and details. Then start Bor again using:
```bash theme={null}
sudo service bor start
```
6. Check your Bor data size after pruning using:
```bash theme={null}
du -sh /usr/bin/bor
```
## Heimdall
### Log: Error dialing seed/Looking for peers or stopping peer for error/Dialing failed
This log is expected when you first start Heimdall, as it takes some time to find and connect to peers. If the issue persists, check the following:
* Verify that your Heimdall node is configured with the latest seeds as listed in the [node setup documentation](/pos/how-to/full-node/full-node-binaries/#configure-heimdall-seeds-mainnet).
If the error persists after updating to the latest seeds or confirming that you are using the correct ones, follow these steps:
1. Increase `max_num_inbound_peers` and `max_num_outbound_peers` in `/var/lib/heimdall/config/config.toml`:
```toml theme={null}
max_num_inbound_peers = 300
max_num_outbound_peers = 100
```
2. Start `heimdalld` service using the following command:
```bash theme={null}
sudo service heimdalld start
```
### Issue: Validator Heimdall is unable to connect to peers
This typically means that your sentry Heimdall is running into issues.
##### Solution
* Check your sentry Heimdall to ensure that the service is running properly.
* If the service is stopped, restarting it on your sentry node should resolve the issue.
* Likewise, after addressing any issues with your sentry, restarting your Heimdall service should also help resolve the problem.
# Reporting issues
Source: https://docs.polygon.technology/pos/how-to/troubleshoot/reporting-issues
Where to report bugs, security vulnerabilities, and node issues for Polygon PoS, including bug bounty programs and support channels.
## Where to report a bug
Any discovered bugs or vulnerabilities related to our Bug Bounty Program should be reported as follows:
* For websites and applications: [https://hackerone.com/polygon-technology](https://hackerone.com/polygon-technology)
* For smart contracts: [https://immunefi.com/bounty/polygon](https://immunefi.com/bounty/polygon)
* For security inquiries, please contact us at [security@polygon.technology](mailto:security@polygon.technology). (Please disclose vulnerabilities through the bug bounty program)
Performing an attack and not providing submission of your proof will result in disqualification of your attempt.
Make sure you add all relevant details such as your email address and Discord ID. Providing ample details creates a rapport of communication, and helps the Polygon team evaluate your submission appropriately.
## What happens after submitting a report
Once an issue is reported, the Polygon team reviews it, comments, and updates on the status of the issue. After evaluation, the Polygon team reports the outcome of the submission. The severity of the issue also gets tagged as per the evaluation.
## Contact us for all other questions
### Via E-mail
* For node operators: [node-support@polygon.technology](mailto:node-support@polygon.technology)
* For validators: [validator-support@polygon.technology](mailto:validator-support@polygon.technology)
### Via support portal
* Visit the support portal at [support.polygon.technology](https://support.polygon.technology/).
* Sign in using your email address and select **Submit a Ticket** located in the top right corner.
* Tha above page appears. To ensure an accurate response, please include the following details when submitting your ticket:
* The versions of Bor and Heimdall you are using.
* At least one hour of logs related to the affected services.
* The `config.toml` files for the affected services.
### Via Discord
* Please visit the **#pos-full-node-queries** channel on our [Discord](https://discord.com/invite/0xPolygonCommunity) server and feel free to post your questions there. We will address your queries directly in that channel.
# Technical FAQs
Source: https://docs.polygon.technology/pos/how-to/troubleshoot/technical-faqs
Reference answers to common technical questions about running Polygon PoS validator nodes, including key management, default directories, and troubleshooting commands.
### 1. Are the private keys same for Heimdall and Bor keystore?
Yes, the private key used for generating Validator keys and Bor Keystore is the same.
The private key used in this instance is your Wallet's ETH address where your Polygon
testnet tokens are stored.
### 2. List of Common Commands
Refer to the [list of common commands](/pos/reference/commands/) that might come in handy while troubleshooting.
### 3. Default Directories
* Heimdall genesis file: `/var/lib/heimdall/config/genesis.json`
* Heimdall app.toml file: `/var/lib/heimdall/config/app.toml`
* Heimdall config.toml file: `/var/lib/heimdall/config/config.toml`
* Heimdall client.toml file: `/var/lib/heimdall/config/client.toml`
* Heimdall data directory: `/var/lib/heimdall/data/`
* Bor config.toml file: `/var/lib/bor/config.toml`
* Bor data directory: `/var/lib/bor/data/bor/chaindata`
### 4. From where do I create the API key?
You can access this link: [https://infura.io/register](https://infura.io/register) . Make sure that once you have setup your account and project, you copy the API key for Sepolia and not mainnet.
Mainnet is selected by default.
### 5. How do I delete remnants of Heimdall and Bor?
Run the following commands to delete the remnants of Heimdall and Bor from your machines.
For the Linux package, run: `$ sudo dpkg -i bor`
And delete the Bor directory using: `$ sudo rm -rf /var/lib/bor`
For binaries, run: `$ sudo rm -rf /var/lib/bor`
And then run: `$ sudo rm /var/lib/heimdall`
### 6. How many validators can be active concurrently?
Under the current limit, a maximum of 105 validators can be active at any given time. It's important to note that active validators are primarily those with high uptime, while participants with significant downtime may be removed.
### 7. How much should I stake?
A minimum stake of 10,000 POL tokens is required (as per PIP-4). We recommend setting a Heimdall fee of 10 POL.
### 8. I'm not clear on which Private Key should I add when I generate validator key.
The private key to be used is your wallet's ETH address where your Polygon testnet tokens are stored. You can complete the setup with one public-private key pair tied to the address submitted on the form.
### 9. Is there a way to know if Heimdall is synced?
You can run the following command to check it:
```bash theme={null}
$ curl [http://localhost:26657/status](http://localhost:26657/status)
```
Check the value of the `catching_up` flag. If it is `false` then the node is all synced up.
### 10. Which file do I add the API key in?
Once you have created the API key, you need to add it to the `app.toml` file.
### 11. How to check if the correct signer address is used for validator setup?
To check the signer address, run the following command *on the validator node*:
```bash theme={null}
heimdalld show-account
```
### 12. `Error: Failed to unlock account (0x...) No key for given address or file`
This error occurs because the path for the `password.txt` file is incorrect. You can follow the below steps to rectify this:
1. Copy the Bor keystore file to `/var/lib/bor/keystore`
2. Copy `password.txt` to `/var/lib/bor/`
3. Make sure you have added correct address in `/var/lib/bor/config.toml`.
4. Ensure that the `priv_validator_key.json` and `UTC-