Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
PROS is the gas token on Pharos mainnet, you need it in your wallet before you can send transactions or deploy contracts. This page covers the three supported acquisition paths and how to confirm the funds arrived.
An EVM wallet such as MetaMask, OKX Wallet or any other with Pharos Mainnet added, or the TopNod app if you don't have a wallet yet.
Crypto already, in another wallet
Halliday → wallet transfer
All the options mentioned can be accessed at .
The featured option on the page.
Click Connect Wallet.
Toggle Use crypto vs. Use cash at the top of the widget.
Use crypto shows your connected address under "Receiving Wallet Address" (confirm it's yours) plus a "Send crypto" panel — pick your source wallet or exchange from the shown icons (MetaMask, Rainbow, Coinbase, WalletConnect, etc.) or scan the QR code, and send PROS from there.
A card/bank on-ramp. Use the Alchemy Pay button on the . It will open a "Buy Crypto" checkout:
You pay — enter an amount and pick your fiat currency (e.g. USD).
You receive (estimate) — auto-fills the equivalent in PROS on the Pharos network; this is a live quote that refreshes on a short timer (shown as a countdown, e.g. "8 sec") until you proceed.
Review Your order (e.g. "749.46 PROS for 300 USD"), then click Proceed · Buy PROS.
Use the TopNod button on the Port page.
Create a wallet with Apple/Google sign-in, or open your existing one.
Use the app's Buy/Deposit function to fund it with PROS.
Once a transfer or purchase completes, confirm the balance on-chain rather than trusting a confirmation screen alone. Three ways, in order of convenience:
1. Block explorer (fastest, no setup)
Go to and paste your address into the search bar. Your PROS balance shows on the address page, along with the incoming transaction (from the sender's address, the on-ramp's settlement address, or an exchange withdrawal). This also lets you check the transaction actually confirmed rather than just being pending.
2. Your wallet
Open MetaMask / OKX Wallet / TopNod with the Pharos network selected — the balance shown there is read live from the chain. If it shows 0 but the explorer shows a balance, the wallet is likely still on the wrong network or hasn't refreshed; switch networks or reload.
3. RPC call (for scripting or double-checking a wallet UI)
The result is a hex-encoded wei value; divide by 1e18 for PROS.
Official domains only: port.pharos.xyz, alchemypay.org, topnod.com.
No flow here ever needs your seed phrase or private key.
Always double check the network is Pharos before withdrawing from an exchange.
For network parameters, token contract addresses, and wallet setup, see , the , and in the main docs.
Use + Add wallet if you want to receive to a different address than the one currently connected.
Confirm the destination address is your own before finalizing anything.
ramp.alchemypay.org directly.Complete KYC if it's your first purchase, then confirm. The destination wallet address comes from whatever address you were connected with on Pharos Port.
Crypto already, on an exchange
Withdraw directly, network = Pharos
Fiat (card/bank) only
Halliday or Alchemy Pay
No wallet yet
TopNod
curl https://rpc.pharos.xyz \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["<your-address>", "latest"],
"id": 1
}'Pharos Network is committed to driving trustless innovations that serve financially underserved communities and asset markets, supporting the future global economy and enabling true mass adoption of Web3 technologies.
The network aims to establish a Web3 infrastructure with the following characteristics:
An Inclusive Payment System: Providing a Web2-equivalent user experience at the scale of billions of user accounts globally.
An Inclusive Asset Exchange System: Facilitating extreme liquidity for both crypto assets and real-world assets, bridging traditional finance with decentralized markets.
A dApp Innovation Platform: Empowering the development of rich Web3 SuperApps through a revolutionary world computing model, leveraging a generic Coprocessor architecture.
Pharos provides two primary network environments to help you move from development to production smoothly.
Whether you're experimenting with smart contracts or deploying a live application, each network is designed to support a specific stage of your workflow.
Atlantic Testnet This is where you build and test. Use it to deploy contracts, try out integrations, and iterate on your application without worrying about real assets.
Pacific Mainnet This is the production network. Once your application is ready, you can deploy to Mainnet and interact with real users, assets, and infrastructure.
In most cases:
Start on Atlantic Testnet while developing and testing
Move to Pacific Mainnet when you're ready to go live
The Pharos Encyclopedia is a dedicated knowledge hub designed for developers, researchers, and contributors who want to explore the technical foundations, core concepts, and architecture of the Pharos Network.
While this Developer Documentation focuses on helping you build, integrate, and deploy applications, the Encyclopedia provides a deeper understanding of how Pharos works under the hood — from foundational concepts to protocol-level design.
The Encyclopedia covers in-depth topics across the Pharos stack, including:
Core Concepts Foundational concepts that help you understand how the Pharos ecosystem operates, including key primitives, system design patterns, and terminology.
Architecture Overview A breakdown of Pharos’ layered architecture, including L1 Base, Core, and Extension layers.
Consensus & Performance Design Insights into Pharos’ scalable BFT mechanisms and high-throughput execution model.
Core Technologies Deep dives into components such as:
Pharos DTVM
Dual VM architecture (EVM + WASM)
Special Processing Networks (SPNs)
Interoperability & Cross-Chain Design How Pharos enables seamless interaction across ecosystems.
Research & Design Papers Detailed technical explorations and protocol-level innovations.
You should explore the Encyclopedia if you want to:
Understand core concepts and how Pharos works internally
Dive into protocol-level architecture and design decisions
Build advanced infrastructure or protocol integrations
Contribute to the core technology or ecosystem tooling
If your goal is to build and ship applications, we recommend starting with:
Getting Started
Developer Guide & Tutorials
Tooling & Infrastructure
These sections are designed to help you go from idea to deployment as quickly as possible.
Access the full Encyclopedia here:
This guide describes how to revert the Pharos node to a previous block height.
Docker:
Binary:
Revert to latest stable block:
Revert to a specific block number:
You will see an interactive confirmation prompt:
Enter y to confirm.
Docker:
Binary:
To ensure a validator operates effectively and efficiently, the minimum recommended hardware specifications are:
To help developers and validators quickly join the Pharos Network without syncing from genesis, we provide regularly updated snapshot datasets for each network. These snapshots contain pre-synced state data and allow rapid setup of Devnet, Testnet, or Mainnet nodes.
⚠️ Snapshots should only be used from trusted sources. Always verify integrity before replacing production data.
Snapshots currently contain the full block history. In the future, we will support pruned snapshots that include only the most recent blocks and state.
Native restaking and shared security
High-performance storage systems
Evaluate Pharos for research or system design purposes
Explorer
ChainID
688689
Environment
Atlantic Testnet
Ratelimit
500 times/5m
Max Pending TXs (Addr)
64
Full Cache Nodes should still sync from genesis.
After applying a snapshot, the node will continue syncing to the latest height.
Always ensure your snapshot corresponds to the correct network and version.
Storage
5 TB SSD with at least 350MiB/s bandwidth and 30000 IOPS
Network Bandwidth
0.5 Gbps
Open Files Limit
ulimit -n ≥ 10000000
CPU
32 cores, 2.8GHz or faster, AMD Milan EPYC or Intel Xeon Platinum
Memory
256 GB
This guide helps you rebuild your Pharos Testnet node while preserving your existing validator identity (node info). This is useful when resetting the database but retaining validator keys and configuration.
Do not run docker stop or docker-compose down during this step.
You can check sync progress using:
Replace new config with the backup node info:
Wait ~3 minutes again for sync to resume. You can check status using:
cd /data/$WORKSPACE
docker compose downcd /data/$WORKSPACE
./ops stopmv /data/$WORKSPACE/data/local_storage /data/$WORKSPACE/data/local_storage.bakcd /data/$WORKSPACE/bin
./pharos_cli revert -c ../pharos.conf -t stablecd /data/$WORKSPACE/bin
./pharos_cli revert -c ../pharos.conf -t stable -n 1234567You are trying to revert stable block to 1234567, are you sure? [y/n]cd /data/$WORKSPACE
docker compose up -d./ops start --config ./pharos.confdocker stop pharos-testnet
mv /data/testnet /data/testnet_bak
docker rm pharos-testnetdocker run -d --name pharos-testnet \\
-v /data/testnet:/data \\
-p 18100:18100 -p 18200:18200 -p 19000:19000 \\
public.ecr.aws/k2g7b7g1/pharos/testnet:63b85b6bcurl 127.0.0.1:18100/ \\
-X POST \\
-H "Content-Type: application/json" \\
--data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}'docker stop pharos-testnetrm -rf /data/testnet/pharos-node/domain/light/data/
rm -rf /data/testnet/pharos-node/domain/light/certs/
rm -rf /data/testnet/resources/
mv /data/testnet_bak/pharos-node/domain/light/data/ /data/testnet/pharos-node/domain/light/data/
mv /data/testnet_bak/pharos-node/domain/light/certs/ /data/testnet/pharos-node/domain/light/certs/
mv /data/testnet_bak/resources/ /data/testnet/resources/docker start pharos-testnetcurl 127.0.0.1:18100/ \\
-X POST \\
-H "Content-Type: application/json" \\
--data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}'rm -rf /data/testnet_bakElevating Blockchain Efficiency: A Deep Dive into Scalability and Optimization
Blockchain technology has made significant strides in recent years, yet performance remains a primary bottleneck hindering the Web3 user experience. Although many protocols achieve high TPS in ideal or test environments, their performance in real-world falls far short of expectations. This performance gap makes it difficult for Web3 applications to compete with Web2 applications, limiting their mass adoption and enhancement of user experience.
For instance, with bitcoin inscription frenzy hits EVM blockchains in early 2024, we observed that most public chains could not exceed a thousand TPS(Transaction Per Second), typically ranging from dozens to a few hundred. Some public chains, under high-stress conditions, opted to raise Gas Prices to reduce transaction frequency, but this is neither a healthy nor sustainable solution. In fact, the execution cost of smart contracts is usually far higher than that of inscription transactions. Given the current infrastructure, most public chains are still unable to support the operations of Web3 Super DApps, which is one of the crucial factors constraining the current development of Web3. Paradigm also provided some insightful data.
After extensive research and testing, Pharos has identified three significant challenges currently facing high-performance Web3 blockchains:
Challenge 1: The "Bottleneck Effect" in Blockchain
Throughout the development of programmable blockchains, many developer and researchers have continuously optimized various modules, such as consensus, execution, and storage. However, due to the complexity of blockchain systems, isolated optimizations often fail to address overall performance issues, resulting in a limited improvement in end to end transactions per second (TPS). This phenomenon is what we refer to as the “Bottleneck Effect” in blockchain. This issue is particularly pronounced in traditional sequential execution blockchain networks. For instance, in a simulation of Ethereum’s transaction on a Geth client running on a single node (32GB RAM, 16 cores, 2TB SSD), we observed the following time distribution across different stages under continuous load pressure. Although many Layer 1 and Layer 2 solutions focus on optimizing performance from the perspective of executing transactions (e.g., Parallel EVM), they still face bottlenecks in merklization and database operations, which limit overall blockchain network performance. Consequently, in the past 1-2 years, we have seen blockchains, such as , , and , proposing their own database and merklization solutions.
Challenge 2: The Need for Decentralized & Large-Scale Networking
Blockchain is a decentralized distributed ledger system that must support efficient transaction execution and RPC services while maintaining decentralization and large-scale networking at a low cost to enhance network reliability and asset security. However, high hardware requirements can limit the network scale, reducing its security and degree of decentralization. The chart below shows the current hardware requirements for Validators/Sequencers on some L1 and L2 networks:
Challenge 3: State Bloat Problem Hinders Blockchain Development
In , we explore the main bottlenecks facing current blockchain storage systems. With the increase in blockchain users, state bloat has led to challenges such as performance degradation, network scalability limitations, and resource inefficiencies. These issues create significant barriers to the large-scale, production-ready deployment of most L1 and L2 networks.
Pharos, considering the current state of monolithic chains and modular ecosystems, has identified four key shortcomings in the performance optimization of existing blockchains:
Insufficient "Parallelism"
The "parallelism" in existing blockchains is limited, particularly between the execution and storage layers.
Execution Layer: While Parallel EVM technology enhances efficiency, actual parallelism remains below ideal levels due to limitations in optimistic execution algorithms and disk I/O speed. The current Parallel EVM framework also struggles to accommodate the diverse scenarios and execution logic of various DApps, especially AMM-based DEXs.
Storage Layer: Most blockchains use traditional key-value stores and Merkle Tree structures, limiting concurrency in both I/O access and merklization. Teams like Reth and Monad have recognized this issue and are exploring async I/O and parallel merklization to overcome these bottlenecks.
In the current blockchain ecosystem, isolated optimizations often fall short of expectations. A holistic approach is necessary, considering consensus, execution, storage, and parallel processing.
Pharos introduces a suite of efficient modular components and parallel solutions, with a sustained focus on research and practical advancements in this field. We aims to deliver high-performance, low-latency, and cost-effective blockchain services, bringing a Web2-like user experience to Web3.
Before running a Validator Node or Validator Full Node (VFN) on the Pharos Network, you must choose a deployment method and ensure your machine meets the required specifications.
This guide provides clear instructions for deploying nodes using different methods. Once deployed, you can connect your node to the Pharos network and begin participating in consensus or network operations.
📘 Note: Please review the Node Requirements before choosing a deployment method.
Choose one of the following deployment methods to set up your node:
This guide describes how to deploy a Pharos node using Docker and Docker Compose.
📌 For the latest Docker image tag, see .
Alchemy provides scalable RPC infrastructure and developer tooling for interacting with blockchain networks.
On Pharos, Alchemy can be used to access reliable RPC endpoints for both Pacific Mainnet and Atlantic Testnet, making it suitable for application development and production use.
To use Alchemy with Pharos, you’ll need to and generate an API key.
Here are some prerequisites before you start interacting:
Sign up or log in to Alchemy
Create a new app
Cross-chain infrastructure enables applications on Pharos to interact with other blockchain networks. It allows developers to move assets, send messages, and build applications that operate across multiple ecosystems.
For developers building multi-chain applications or integrating external liquidity and users, cross-chain infrastructure is a key component.
Transfer assets between networks
Send messages across chains
Safe (Gnosis Safe) is one of the most widely used multi-signature wallets in the blockchain ecosystem. It enables teams and DAOs to securely manage digital assets and smart contract permissions through configurable multi-signature approvals. Developers and organizations can create Safe wallets to manage treasury funds, protocol upgrades, and governance-controlled contracts on the Pharos network.
Production interface for interacting with Safe wallets on Pharos: https://app.safe.global
Developers can interact programmatically with the Safe Transaction Service for automation and integration.
Swagger API Endpoint
https://transaction.safe.pharosnetwork.xyz
Transaction creation and submission
Signature collection
Transaction status monitoring
Integration with automation workflows
This guide is suitable for developers who want to realize interoperability between EVM and WASM. If you are a new user of Ethereum, please consider researching the Ethereum documentation before continuing.
0x4e59b44847b379578588920ca78fbf26c0b4956c
MultiCall3
Allows bundling multiple transactions
0xcA11bde05977b3631167028862bE2a173976CA11
GnosisSafe (v1.3.0)
Multisignature wallet
0x69f4D1788e39c87893C980c06EdF4b7f686e2938
GnosisSafeL2 (v1.3.0)
Events-based implementation of GnosisSafe
0xfb1bffC9d739B8D520DaF37dF666da4C687191EA
SafeSingletonFactory
Safe's deterministic deployment proxy
0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7
CreateX
Advanced cross-chain deployment factory supporting CREATE, CREATE2, and CREATE3
0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed
MultiSendCallOnly (v1.3.0)
Batches multiple transactions (calls only)
0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B
MultiSend (v1.3.0)
Batches multiple transactions
0x998739BFdAAdde7C933B942a68053933098f9EDa
Permit2
Next-generation token approval system
0x000000000022D473030F116dDEE9F6B43aC78BA3
ERC-4337 EntryPoint (v0.7)
ERC-4337 entry point for account abstraction
0x0000000071727De22E5E9d8BAf0edAc6f37da032
ERC-4337 SenderCreator (v0.7)
Helper for EntryPoint
0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C
ERC-4337 EntryPoint (v0.6)
Account abstraction entry point (v0.6); validates and executes UserOperations
0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
ERC-4337 SenderCreator (v0.6)
Helper contract used by EntryPoint v0.6 to deploy smart accounts via initCode
0x7fc98430eAEdbb6070B35B39D798725049088348
DeterministicDeploymentProxy
Integrated with Foundry for deterministic deployments
0x4e59b44847b379578588920ca78fbf26c0b4956c
MultiCall3
Allows bundling multiple transactions
0xcA11bde05977b3631167028862bE2a173976CA11
GnosisSafe (v1.3.0)
Multisignature wallet
0x69f4D1788e39c87893C980c06EdF4b7f686e2938
GnosisSafeL2 (v1.3.0)
Events-based implementation of GnosisSafe
0xfb1bffC9d739B8D520DaF37dF666da4C687191EA
MultiSendCallOnly (v1.3.0)
Batches multiple transactions (calls only)
0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B
MultiSend (v1.3.0)
Batches multiple transactions
0x998739BFdAAdde7C933B942a68053933098f9EDa
Permit2
Next-generation token approval system
0x000000000022D473030F116dDEE9F6B43aC78BA3
EntryPoint (v0.7.0)
ERC-4337 entry point for account abstraction
0x0000000071727De22E5E9d8BAf0edAc6f37da032
SenderCreator (v0.7.0)
Helper for EntryPoint
0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C
Create2Deployer
Helper for CREATE2 opcode usage
0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2
Foundry Deterministic Deploy
Integrated with Foundry for deterministic deployments
Access liquidity and users from other ecosystems
Coordinate logic across multiple chains
Modern applications often extend beyond a single blockchain. Cross-chain infrastructure allows Pharos-based applications to integrate with other networks, improving composability and expanding reach.
Pharos supports cross-chain solutions for asset transfers and messaging.
Refer to the pages below for setup and usage:
Chainlink CCIP
Circle CCTP
LayerZero
Memory
256 GB
Storage
5 TB SSD with at least 350MiB/s bandwidth and 30000 IOPS
Network Bandwidth
0.5 Gbps
Open Files Limit
ulimit -n ≥ 10000000
Additionally, ensure Docker is installed:
Docker Engine 20.10+
Docker Compose 2.0+
Download the genesis configuration and VERSION file based on your network:
For Atlantic Testnet:
For Mainnet:
Note: When upgrading to a new version, you must also re-download the VERSION file to keep it in sync.
Download the node configuration file (choose one based on your node type):
For Archive/Full/Validator Node:
Note: Pruning is disabled by default. To enable pruning, see Enable Pruning in Pharos Node.
For TraceDB Node:
Create the Docker Compose configuration file:
Note: The image uses a specific version tag (pharos_community_v0.16.0_3cf35988_0914).
Edit docker-compose.yml and set your password:
Change this line:
To your actual password:
Before starting the container, verify all required files are present:
You should see:
Required files:
genesis.conf - Genesis configuration
pharos.conf - Node configuration
docker-compose.yml - Docker Compose configuration with your password
bin/VERSION - Network version information
Missing files? Go back to steps 2-4 to download and configure them.
Start the Pharos node with Docker Compose:
View logs:
Check if the node is running:
You should see:
Check node health:
Note: This does not delete your data. Data is persisted in /data/pharos/.
After deployment, your directory structure will look like:
To upgrade to a new version, update the image tag in docker-compose.yml to the new version, then:
Use strong passwords: Set a secure password for CONSENSUS_KEY_PWD
Protect your keys: Ensure /data/pharos/keys/ has proper permissions
Firewall configuration: Only expose necessary ports
Regular backups: Backup /data/pharos/keys/ and pharos.conf
Keep updated: Regularly pull and update to the latest image
Monitor logs: Regularly check logs for errors or warnings
Node Monitoring Setup - Configure Prometheus metrics for your node
CPU
32 cores, 2.8GHz or faster, AMD Milan EPYC or Intel Xeon Platinum
Select the Pharos network
Generate your API key
Once done, the API key will let you interact with the Pharos network through a set of JSON-RPC methods. Follow the guide below to start interacting with Pharos.
Choose between npm and yarn based on your preference or project requirements.
# Begin with npm by following the npm documentation
# https://docs.npmjs.com/downloading-and-installing-node-js-and-npm# For yarn, refer to yarn's installation guide
# https://classic.yarnpkg.com/lang/en/docs/installRun the following commands to create and initialize your project:
This creates a new directory named pharos-api-quickstart and initializes a Node.js project within it.
Install Axios, a popular HTTP client, to make API requests:
Create an index.js file in your project directory and paste the following code:
Replace your-api-key with your actual Alchemy API key from the Alchemy Dashboard
Run your script to make a request to the network:
You should see the latest block information from Pharos's network outputted to your console:
You've made your first request to the Pharos using Alchemy API. You can now explore the various JSON-RPC methods available on Pharos and start building your dApps.
export WORKSPACE=pharos
mkdir -p /data/$WORKSPACE && cd /data/$WORKSPACEmkdir -p bin
wget -O genesis.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/atlantic.genesis
wget -O bin/VERSION https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/atlantic.versionmkdir -p bin
wget -O genesis.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/mainnet.genesis
wget -O bin/VERSION https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/mainnet.versionwget -O pharos.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/conf/full.confwget -O pharos.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/conf/traceDB.confcat > docker-compose.yml <<EOF
services:
pharos:
image: public.ecr.aws/k2g7b7g1/pharos:pharos_community_v0.16.0_3cf35988_0914
container_name: pharos-node
environment:
- CONSENSUS_KEY_PWD=YOUR_PASSWORD_HERE # Change this to your password
- PHAROS_CONF=/data/pharos.conf
- GENESIS_CONF=/data/genesis.conf
- KEYS_DIR=/data/keys
volumes:
# Mount data directory
- /data/$WORKSPACE:/data
ports:
- "18100:18100" # HTTP RPC
- "18200:18200" # WebSocket
- "19000:19000" # P2P TCP
- "20000:20000" # RPC
restart: unless-stopped
ulimits:
nofile:
soft: 10000000
hard: 10000000
healthcheck:
test: ["CMD", "curl", "-sf", "-X", "POST", "-H", "Content-Type: application/json", "-d", "{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[],\"id\":1}", "http://localhost:18100"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
EOFvim docker-compose.yml- CONSENSUS_KEY_PWD=YOUR_PASSWORD_HERE- CONSENSUS_KEY_PWD=your_secure_passwordtree ..
├── bin
│ └── VERSION
├── docker-compose.yml
├── genesis.conf
└── pharos.confdocker compose up -ddocker compose logs -f pharosdocker compose psNAME IMAGE STATUS PORTS
pharos-node public.ecr.aws/k2g7b7g1/pharos:pharos_community_v0.16.0_3cf35988_0914 Up 2 minutes 0.0.0.0:18100->18100/tcp, ...curl http://localhost:18100docker compose stopdocker compose restartdocker compose downdocker compose logs -fdocker compose exec pharos bash/data/pharos/
├── bin/ # Auto-copied from image on each start
│ ├── pharos_light
│ ├── pharos_cli
│ └── VERSION
├── ops # Auto-copied from image on each start
├── keys/ # Auto-generated on first start
│ ├── domain.key
│ ├── domain.pub
│ ├── stabilizing.key
│ └── stabilizing.pub
├── data/ # Created after bootstrap
│ └── meta_store/
├── log/
│ ├── aldaba.log
│ ├── aldaba_cli.log
│ └── pamir.log
├── genesis.conf # Downloaded in step 2
├── pharos.conf # Downloaded in step 2
└── docker-compose.yml # Created in step 3, edited in step 4# Pull the new image
docker compose pull
# Restart with new image
docker compose up -d$ mkdir pharos-api-quickstart
$ cd pharos-api-quickstart
$ npm init --yes$ mkdir pharos-api-quickstart
$ cd pharos-api-quickstart
$ yarn init --yes$ npm install axios$ yarn add axiosconst axios = require('axios');
const url = 'https://pharos-mainnet.g.alchemy.com/v2/your-api-key';
const payload = {
jsonrpc: '2.0',
id: 1,
method: 'eth_blockNumber',
params: []$ node index.js$ Latest Block: 0x...Ethereum
16
0.025
1.5 Million
Solana
256
In reality, most personal computers and mobile devices already have decent configurations, and some L1 and L2 networks are considering incorporating them into their ecosystems.
Verifiable Storage Remains a Bottleneck in Large-Scale Scenarios
Most current L1 and L2 solutions still rely on verifiable storage architectures, but their inefficient querying and merklization performance severely limit overall blockchain throughput. The combination of MPT (Merkle Patricia Tree) and LSM Store, for instance, faces three performance issues: Long I/O Paths, Hash-Based Addressing, and State Bloat. As State Bloat worsens, the efficiency of starting new nodes continues to decline, further impacting network scale and decentralization.
Insufficient Utilization of Node Resources
The resources of existing blockchain nodes are not being fully utilized. One reason is that, in traditional blockchain models, CPU and I/O resources remain idle for much of the time. Furthermore, on-chain data and index maintenance also waste a significant amount of CPU and I/O resources. Taking the most typical LSM Database as an example, compaction consumes considerable CPU and I/O resources, competing with existing modular components and greatly reducing throughput.\
In the section, we provide a detailed overview of the resource bottlenecks at each stage of blockchain processing.
Limitations of Layer 2 Scaling Solutions
While many Layer 2 (L2) solutions (such as ZK/OP Rollup, and Sidechains) have proven effective in improving Layer 1 (L1) scalability, several issues still persist:
Increasing State Bloat in L2:
Most L2 sequencers continue to use traditional blockchain node architectures, which do not effectively address the state bloat problem, resulting in significant throughput limitations. Many L2 networks only achieve between tens to a few hundred TPS, as shown in Paradigm's recent performance data for various L2 solutions. Moreover, while decentralized sequencer networks are an aspirational goal for many L2 projects, they encounter technical challenges as complex as those faced by L1 networks. In ZK rollup designs, this problem is often compounded by the use of binary Merkle trees, which further intensify state bloat.
Data and Liquidity Fragmentation:
While L2 solutions enhance scalability, they introduce significant delays in inter-network communication. For example, rollups typically require several hours to days for messages to transfer from L2 to L1 during the challenge period, slowing down communication and leading to severe data and liquidity fragmentation. Additionally, L2 networks cannot share assets and account states directly with each other and must rely on third-party bridges for transfers, which introduces both centralization and security risks.

Atlantic Testnet
17
facd080f
public.ecr.aws/k2g7b7g1/pharos:pharos_community_v0.17.0_facd080f_1015
Pacific Mainnet
16
3cf35988
public.ecr.aws/k2g7b7g1/pharos:pharos_community_v0.16.0_3cf35988_0914
Understanding Gas Refund and Gas Limit in Pharos Transactions
When sending a transaction on Ethereum or an EVM-compatible blockchain, it is essential to set an appropriate gas limit to ensure successful execution. One common issue arises when the gas limit is set exactly equal to the gas used, particularly when the transaction involves a gas refund mechanism.
Feature
Details
Opcode pricing
Fully aligned with Ethereum EVM opcode
Following the EIP-1559 model, every transaction fee on Pharos is split into two components:
Base Fee — the base fee is burned directly, permanently removing the corresponding tokens from circulation.
Priority Fee — the priority fee (tip) is credited to the validator that produces the block, as a reward for including the transaction.
This fee-distribution mechanism is already implemented on Pharos.
On Ethereum, the priority fee is credited to the block producer's address immediately after each transaction is executed. Pharos differs because of two core design choices:
Parallel execution & pipelined architecture — to maximize performance, transactions are executed in parallel rather than sequentially.
Multi Concurrent Proposer (MCP) model — multiple proposers can contribute to a single block.
Because transactions execute in parallel, priority fees cannot be settled at the granularity of individual transactions in real time. Instead, they are accumulated and credited to the corresponding validators in a batch at epoch boundaries.
Set a slightly higher gas limit: Allow extra gas beyond the expected execution cost.
Example: If you expect 100,000 gas used before refund, set the gas limit to 120,000 to avoid failures.
Use Estimation APIs:
Monitor Gas Refund Logic:
If your contract relies heavily on gas refunds (e.g., clearing storage or SELFDESTRUCT), test transactions with different gas limits to find an optimal value.
Check for Out-of-Gas Failures:
To prevent transactions from failing due to gas refund issues, always set a gas limit slightly higher than the expected gas usage. This ensures smooth execution and prevents out-of-gas errors caused by refund mechanisms.
By following these best practices, developers can avoid failed transactions and improve the reliability of smart contract interactions.
“We believe in: rough consensus and running code” - David Clark, 1992
Pharos has designed a Modular & Full-stack Parallel L1 Blockchain Network, structured into three primary layers: L1-Base, L1-Core, and L1-Extension.
L1-Base: Delivers industry-leading data availability and hardware acceleration.
L1-Core: A high-performance, globally distributed blockchain network powered by decentralized nodes. These nodes provide high-throughput and sub-second finality, enabling real-time Web3 user experiences.
L1-Extension: Built on top of L1-Core, this layer supports network expansion across three dimensions:
Leverages heterogeneous computation to create custom , capable of running blockchain networks, sidechains, or non-blockchain applications like HFT, ZKML, and AI models.
Enables Native Restaking to support rapid SPN creation, shared security, staking rewards, and slashing mechanisms. SPNs utilize the excess computing power of validators, providing flexibility in resource use while incentivizing upgrades.
The Pharos ecosystem is structured across three key layers:
Transaction Layer: Facilitates secure, confidential, and efficient cross-chain asset and account interactions via the secure and confidential cross-chain interoperability protocol.
Consensus Layer: Integrates with external restaking protocols through the Adaptive Restaking Interaction Protocol, enabling shared security and liquidity. This includes circulating assets like stBTC and stETH within the Pharos DeFi ecosystem.
Data Layer: Uses the Decentralized Data Exchange Protocol to synchronize and collaborate with external data centers, promoting innovative on-chain use cases like AI, FHE.
A modular stack is provided to support SPNs, Pharos L2/L3 systems. Each modular component is open-sourced and available to partners and developers for scaling and customization.Performance benchmarks have been demonstrated on Geth, and future collaborations with ecosystem partners will help drive the development of Web3 infrastructure.
Pharos supports a multi-asset protocols to enhance security and liquidity, allowing seamless integration with restaking protocols like Babylon and Eigenlayer. Future updates will include more protocols, expanding interoperability and security across the network.
Welcome to the official snapshot page for Pharos Network. Here you can find regularly updated data snapshots for each network environment — Atlantic Testnet and Mainnet — to help you quickly synchronize and bootstrap your nodes without performing a full sync from genesis.
Snapshots include the latest database state and can be used for fast setup, disaster recovery, or performance testing.
Snapshots do not contain full block history, only the latest validated state.
Archival nodes should still sync from genesis.
Follow these steps to bootstrap a Pharos node using the latest snapshot:
We periodically publish . Use the following command to download and extract:
Only use snapshots that match your target network.
1~10
~1,500
Aptos
64
1
~150
Monad
32
0.1
-
Reth
(Single Node)
256
10
1
Mega ETH
(Single Sequencer)
1024~
4096
10
1
ethers.js, web3.js), always use estimateGas() and add a buffer.Example (ethers.js):
If a transaction fails unexpectedly despite having enough gas, check whether gas refunds are affecting execution.
Debug with debug_traceTransaction or EVM logs.
EIP-1559 support
✅ Compatible (base fee + priority fee)
Base fee
Dynamic (recalculated per epoch)
Transaction fee model
Charged by gas_limit at inclusion time
Fee distribution
Base fee is burned; priority fee is credited to the validator
Refund logic
Full EVM-style refund tracking supported, but refund does not affect charge
const estimatedGas = await contract.estimateGas.someFunction();
const gasLimit = estimatedGas.mul(12).div(10); // Adding a 20% buffer
await contract.someFunction({ gasLimit });wget https://snapshot.dplabs-internal.com/atlantic/atlantic-snapshot-2026-03-05-11.tar.gz
tar -zxvf atlantic-snapshot-2026-03-05-11.tar.gzdocker-compose downrm -rf /data/$WORKSPACE/pharos-node/domain/light/data/public
mv /data/$WORKSPACE/pharos-node/domain/light/data/local_storage /data/$WORKSPACE/pharos-node/domain/light/data/local_storage.bakmv public /data/$WORKSPACE/pharos-node/domain/light/data/publicdocker-compose up -dFacilitates Cross-SPNs Interoperation, allowing infrastructure, middleware, and application-focused SPNs to communicate and collaborate effortlessly, fostering a highly modular and composable ecosystem.


Always ensure your snapshot corresponds to the correct network and version.
⚠️ Always verify the snapshot source and integrity before applying it to your node.\
See Rapid Node Initialization for detailed instructions.
Type
FullHistory
Last Updated
Format
Type
Archival
Last Updated
Format
This guide is suitable for developers who want to start building dApps using Pharos and Foundry toolchains. If you are a new user of Ethereum, please consider researching the Ethereum documentation before continuing.
This guide will walk you through the process of sending your first transaction on the Pharos blockchain using three popular libraries: ethers.js, web3.js, and web3.py. By the end of this guide, you will understand how to send a transaction programmatically using these libraries.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Install your preferred contract framework from the below list:
ethers.js
web3.js
web3.py
Clone the example repo and add dependencies.
Run the script using Node.js:
Private Key: Never expose your private key in production. Use environment variables or secure storage.
Recipient Address: Replace RECIPIENT_ADDRESS with the actual recipient's wallet address.
Gas Limit: Ensure you include a sufficient gas limit for the transaction.
Transaction Fails: Ensure you have enough testnet tokens to cover the transaction fee.
Incorrect Network: Double-check that you are connected to the Pharos Testnet.
Invalid Address: Verify that the recipient's address is correct.
Now that you’ve sent your first transaction using ethers.js, web3.js, and web3.py.
This guide provides a comprehensive introduction to sending transactions on the Pharos blockchain using three popular libraries. If you encounter any issues, refer to the Troubleshooting section or consult the respective library's documentation. Happy building! 🚀
Hemera SocialScan is the official block explorer for the Pharos Network. It provides real-time visibility into on-chain activity, allowing developers and users to inspect transactions, blocks, accounts, tokens, and smart contracts.
For developers, the explorer provides a familiar interface of EVM that includes tools for contract verification, token analytics, and transaction debugging.
Fordefi provides institutional-grade wallet infrastructure for securely managing digital assets.
It uses MPC (Multi-Party Computation) technology to eliminate single private keys, enabling secure transaction signing along with policy-based access control for teams and organizations.
Pharos can be enabled directly within the Fordefi platform from the Chains settings:
https://app.fordefi.com/settings/chains?search=PHAROS&page=1
MPC-based key management
Policy-based transaction approvals
Supra provides high-performance oracle services that deliver real-time price feeds and external data to decentralized applications.
On Pharos, Supra’s DORA Pull Oracle is deployed on Pacific Mainnet and Atlantic Testnet, enabling smart contracts to securely fetch off-chain data directly from the oracle network.
The DORA Pull Oracle is available on Pacific Mainnet and Atlantic Testnet with the following contracts:
Validators are expected to actively and honestly participate in the consensus process. There is currently no automated slashing mechanism. However, if malicious behavior is detected, governance procedures may be initiated to penalize the offending validator.
The following behaviors are considered violations:
Prolonged offline: The validator node is unreachable or unresponsive for an extended period.
Consensus inactivity: Failing to propose blocks or submit consensus votes when expected.
Validators who engage in any of the above will see their consensus participation degraded. As a result:
Their block proposals may be rejected by the network.
They will lose eligibility for transaction fee rewards associated with proposed blocks.
Validator rewards are tied to consensus participation. Validators that maintain consistent uptime and submit timely, correct votes are eligible to propose blocks and receive the associated transaction fees.
Compressed TAR archive
Notes
Only includes the public database
sha256 checksum
a7d4f00822ba78f792bc086c66bb714e02051dfe24be62b1e08cfad00d53bf91
Compressed TAR archive
Notes
Only includes the public database
sha256 checksum
7b67d3f804904b4dacd6778acb9ee8f4cd383110f02ea9a2e18b370446bdb4dd
Institutional custody workflows
Secure signing infrastructure
Integration with DeFi protocols
Fordefi is typically used for:
Managing treasury and protocol funds
Secure transaction signing without single key exposure
Teams requiring policy-driven approval workflows
Institutional-grade custody and operations
Enable Pharos from the Chains settings before initiating transactions
Configure policies and approval flows based on your team’s requirements
Suitable for both operational workflows and production environments
Enable Pharos in your Fordefi account
Set up policies and access controls
Start managing assets and signing transactions securely
Pull
0x16f70cAD28dd621b0072B5A8a8c392970E87C3dD
Storage
0xaa2f56843Cec7840F0C106F0202313d8d8CB13d6
Pull
0x443A0f4Da5d2fdC47de3eeD45Af41d399F0E5702
Supra is typically used for:
Fetching real-time price feeds
Building DeFi applications (lending, trading, derivatives)
Powering RWA protocols that depend on external data
Triggering smart contract logic based on off-chain inputs
The DORA Pull model allows contracts to request data directly from the oracle
Ensure your contract is configured to interact with the correct oracle contract addresses
Suitable for applications requiring reliable and up-to-date data feeds
For detailed integration guides and supported features:
https://docs.supra.com/oracles
Integrate the DORA Pull Oracle into your smart contracts
Test oracle data flows on Mainnet and Testnet
Combine with other infrastructure such as RPC and indexing for full application workflows
Storage
0x58e158c74DF7Ad6396C0dcbadc4878faC9e93d57
git clone https://github.com/PharosNetwork/examples
cd examples/transaction/ethersjs
npm installconst { ethers } = require("ethers");
// Connect to Pharos Testnet
const provider = new ethers.providers.JsonRpcProvider("<PHAROS_RPC_URL>");
// Note: Your wallet private key (for testnet only, never expose this in production)
const privateKey = "YOUR_PRIVATE_KEY";
const wallet = new ethers.Wallet(privateKey, provider);
// Transaction details
const tx = {
to: "RECIPIENT_ADDRESS", // Note: Replace with the recipient's address
value: ethers.utils.parseEther("0.1"), // Amount to send (0.1 PHAR)
};
// Send the transaction
wallet.sendTransaction(tx)
.then((transaction) => {
console.log("Transaction sent:", transaction.hash);
})
.catch((error) => {
console.error("Error sending transaction:", error);
});node index.jsDeveloper APIs (Contract Verification API)
Explorer Developer API Key
102dfc05-661d-4efd-a81e-24afb3918c7f
Hemera SocialScan supports common developer workflows:
View and track transactions in real time
Inspect blocks and network activity
Explore wallet addresses, balances, and interactions
View deployed smart contracts and their state
Verify smart contracts and access source code
Analyze token activity and transfers
You can verify deployed smart contracts using the SocialScan Developer API.
Ensure your contract is compiled with the correct settings before verification
Verification may fail if bytecode or compiler settings do not match
Use the explorer alongside your preferred framework (Foundry, Hardhat) for a smoother workflow
Verify your deployed contracts
Use the explorer to debug and monitor transactions
Combine with RPC and indexing tools for deeper insights
Explorer Link
This guide will walk you through the process of creating and deploying a simple Uniswap-like decentralized exchange (DEX) contract on the Pharos blockchain. By the end of this guide, you will have a basic understanding of how automated market makers (AMMs) work and how to implement a simple DEX.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from nodejs.org.
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Clone the example repo:
Install OpenZeppelin Contracts:
Create a New Solidity File:
Create a new file for your uniswap contract:
Write the uniswap Contract:
Open contracts/Uniswap.sol in your favorite text editor and add the following code:
Compile the Smart Contract:
Test the Smart Contract
Set the private key:
Deploy the Contract:
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Test Failures: Check the test output for detailed error messages and adjust the tests accordingly.
This guide provides a comprehensive introduction to creating and deploying a Uniswap-like contract on the Pharos blockchain using Hardhat. If you encounter any issues, refer to the Hardhat documentation or the troubleshooting section. Happy building! 🚀
We recommend deploying Prometheus, Pushgateway, and Grafana to monitor Pharos Node metrics.
If you have already deployed a Prometheus monitoring and alerting system, you can skip this step:
Create the Prometheus namespace:
kubectl create namespace prometheusAdd the prometheus-community chart repository:
helm repo add prometheus-community https://prometheus-community.github.io/helm-chartsDeploy Prometheus:
Verify that all pods in the prometheus namespace are in the READY state:
Get the Pushgateway Service IP/Domain:
You should see the prometheus-pushgateway Service. Save the IP address of the Pushgateway Service.
For detailed instructions on configuring the metrics push in your pharos.conf, see .
Nirvana Labs provides enterprise-grade blockchain infrastructure, including high-performance RPC nodes and dedicated node services.
On Pharos, Nirvana RPC can be used to power applications that require reliable network access, low latency, and scalable infrastructure.
Nirvana RPC is well-suited for:
Production applications requiring stable and consistent RPC access
Backend services and APIs handling high request volumes
Use cases where performance and reliability are critical
Teams that may need dedicated or scalable node infrastructure
Developers building on Pharos can access Nirvana RPC endpoints by requesting API keys through the Pharos team.
Access Type: Dev API Key (unrestricted RPC access)
Request: Contact the Pharos team to obtain credentials (Reach out on mail: janesh@pharos.xyz or tg: @janesh_dani)
For more details on Nirvana Labs infrastructure and capabilities:
https://nirvanalabs.io/product#rpc-nodes
This guide is suitable for developers who want to start building dApps using Pharos and hardhat toolchains. If you are a new user of Ethereum, please consider researching the before continuing.
This guide will walk you through the process of sending your first transaction on the Pharos blockchain using three popular libraries: ethers.js, web3.js, and web3.py. By the end of this guide, you will understand how to send a transaction programmatically using these libraries.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Wallets and custody solutions are used to manage accounts, sign transactions, and control assets on the Pharos Network.
As a developer, you’ll use wallets for interacting with applications, while custody solutions are important for managing funds securely, especially in production environments.
Create and manage accounts
Sign and send transactions
Interact with smart contracts
Connect to decentralized applications
Manage assets across networks
For applications and teams, custody solutions provide more advanced capabilities such as:
Multi-signature (multisig) transaction approvals
Secure key management
Role-based access control
Operational security for managing funds
Choosing the right wallet or custody solution is important for both development and production.
Individual developers typically use wallets for testing and interaction
Teams and protocols rely on custody solutions for secure fund management and operational workflows
Pharos supports wallet and custody solutions for different use cases.
Refer to the pages below for setup and usage:
Safe MultiSig
Fordefi
You can refer to the Pharos Block Validation document for details on the Pharos consensus mechanism and block validation process.
You can use the RPC API to check whether the node is actively syncing blocks. If the block number is increasing, the node is catching up to the latest chain height.
All logs are written to the directory:
To monitor logs in real-time, use:
Ensure that the following ports are open and accessible for proper node operation:
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Install your preferred contract framework from the below list:
ethers.js
web3.js
web3.py
Clone the example repo and add dependencies.
Run the script using Node.js:
Private Key: Never expose your private key in production. Use environment variables or secure storage.
Recipient Address: Replace RECIPIENT_ADDRESS with the actual recipient's wallet address.
Gas Limit: Ensure you include a sufficient gas limit for the transaction.
Transaction Fails: Ensure you have enough testnet tokens to cover the transaction fee.
Incorrect Network: Double-check that you are connected to the Pharos Testnet.
Invalid Address: Verify that the recipient's address is correct.
Now that you’ve sent your first transaction using ethers.js, web3.js, and web3.py.
This guide provides a comprehensive introduction to sending transactions on the Pharos blockchain using three popular libraries. If you encounter any issues, refer to the Troubleshooting section or consult the respective library's documentation. Happy building! 🚀
git clone https://github.com/PharosNetwork/examples
cd examples/uniswap/hardhat/contractnpm installtouch contracts/Uniswap.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Uniswap {
address public tokenA;
address public tokenB;
uint256 public reserveA;
uint256 public reserveB;
constructor(address _tokenA, address _tokenB) {
tokenA = _tokenA;
tokenB = _tokenB;
}
function addLiquidity(uint256 amountA, uint256 amountB) external {
require(IERC20(tokenA).transferFrom(msg.sender, address(this), amountA), "Transfer failed");
require(IERC20(tokenB).transferFrom(msg.sender, address(this), amountB), "Transfer failed");
reserveA += amountA;
reserveB += amountB;
}
function removeLiquidity(uint256 amountA, uint256 amountB) external {
require(reserveA >= amountA && reserveB >= amountB, "Insufficient reserves");
reserveA -= amountA;
reserveB -= amountB;
require(IERC20(tokenA).transfer(msg.sender, amountA), "Transfer failed");
require(IERC20(tokenB).transfer(msg.sender, amountB), "Transfer failed");
}
function swapAToB(uint256 amountA) external {
uint256 amountB = (amountA * reserveB) / reserveA;
require(IERC20(tokenA).transferFrom(msg.sender, address(this), amountA), "Transfer failed");
require(IERC20(tokenB).transfer(msg.sender, amountB), "Transfer failed");
reserveA += amountA;
reserveB -= amountB;
}
function swapBToA(uint256 amountB) external {
uint256 amountA = (amountB * reserveA) / reserveB;
require(IERC20(tokenB).transferFrom(msg.sender, address(this), amountB), "Transfer failed");
require(IERC20(tokenA).transfer(msg.sender, amountA), "Transfer failed");
reserveB += amountB;
reserveA -= amountA;
}
}npx hardhat compilenpx hardhat testnpx hardhat vars set PRIVATE_KEYnpx hardhat ignition deploy ./ignition/modules/Token.js --network pharoshelm upgrade -i prometheus prometheus-community/prometheus \
--namespace prometheus \
--set alertmanager.persistence.storageClass="gp2" \
--set server.persistentVolume.storageClass="gp2"kubectl get pods -n prometheuskubectl -n prometheus-agent-namespace get svccurl 127.0.0.1:18100/ \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}'cat /data/domain.json | grep "NODE_ID"curl -X POST --data '{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [{
"to": "0x4100000000000000000000000000000000000000",
"data": "0x9de70258"
}, "latest"],
"id": 1
}' -H "Content-Type: application/json" https://atlantic.dplabs-internal.comcd /data/pharos-node/domain/light/log/tail -f pharos.log18100 — RPC
18200 — P2P
19000 — Internal servicesgit clone https://github.com/PharosNetwork/examples
cd examples/transaction/ethersjs
npm installconst { ethers } = require("ethers");
// Connect to Pharos Testnet
const provider = new ethers.providers.JsonRpcProvider("<PHAROS_RPC_URL>");
// Note: Your wallet private key (for testnet only, never expose this in production)
const privateKey = "YOUR_PRIVATE_KEY";
const wallet = new ethers.Wallet(privateKey, provider);
// Transaction details
const tx = {
to: "RECIPIENT_ADDRESS", // Note: Replace with the recipient's address
value: ethers.utils.parseEther("0.1"), // Amount to send (0.1 PHAR)
};
// Send the transaction
wallet.sendTransaction(tx)
.then((transaction) => {
console.log("Transaction sent:", transaction.hash);
})
.catch((error) => {
console.error("Error sending transaction:", error);
});node index.jsThis guide will walk you through the process of building and deploying your first end-to-end decentralized application (dApp) on the Pharos blockchain. By the end of this guide, you will have a fully functional dApp that includes a smart contract, a backend, and a frontend.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from nodejs.org.
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Clone the example repo:
Set the private key:
Then
Connect the Frontend to the Smart Contract:
Open src/app.jsx and replace its content with the following code:
Replace Placeholder Values
Replace YOUR_CONTRACT_ADDRESS with the address of your deployed contract.
Start the React App
Navigate to the frontend directory and start the app:
Interact with the dApp:
Open your browser and navigate to http://localhost:3000.
Use the input field and buttons to interact with your smart contract.
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Frontend Connection Issues: Verify that the contract address and ABI are correct.
Now that you’ve built your first end-to-end dApp.
This guide provides a comprehensive introduction to building and deploying an end-to-end dApp on the Pharos blockchain. If you encounter any issues, refer to the Troubleshooting section or consult the Pharos documentation. Happy building! 🚀
Ethereum's execution model deducts the full gas limit at the start of execution and refunds a portion of the gas at the end if applicable. However, if the gas limit is set too tightly (equal to the actual gas used before refunds), the refund cannot be processed, and the transaction fails with an out-of-gas error.
Gas is initially reserved: The EVM deducts the full gas limit at the beginning of execution.
Gas refund happens at the end: If a contract includes operations that delete storage variables (e.g., SELFDESTRUCT, SSTORE clearing a slot), the gas refund is only applied after execution completes.
If gas runs out before refunding, the transaction fails because the EVM does not get a chance to apply the refund.
Below is an example of a Uniswap V3 transaction deployed on Pharos Devnet, where the actual gas used was 4,618,680:
However, since the gas limit was set exactly equal to the gas used, the transaction failed.
To verify this behavior, we deployed the same transaction on Ethereum Sepolia Testnet, and observed the same failure due to the gas limit issue:
To ensure the transaction executes successfully, we need to set the gas limit slightly higher than the expected gas usage.
A block explorer is one of the most essential tools when building on Pharos. It gives you visibility into everything happening on-chain — from transactions and blocks to smart contracts and account activity.
As a developer, you’ll frequently use a block explorer to verify deployments, debug issues, and track interactions with your contracts.
View transactions and check their status
Inspect blocks and network activity
Explore wallet addresses and balances
Interact with and inspect smart contracts
Verify deployed contracts and view source code
The block explorer acts as your primary interface for understanding on-chain behavior. Whether you're testing on Testnet or deploying to Mainnet, it helps you quickly validate that everything is working as expected.
RPC endpoints are the primary way your application interacts with the Pharos Network. They allow you to send transactions, query on-chain data, and communicate with the network programmatically.
As a developer, you’ll use RPCs to connect your frontend, backend, or scripts to the blockchain during development and in production.
Send transactions to the network
Read on-chain data (balances, contract state, logs)
Interact with smart contracts
Listen to events and track activity
Power applications, scripts, and integrations
RPC endpoints act as the bridge between your application and the blockchain. Choosing the right provider and configuration is important for performance, reliability, and scalability.
Pharos supports multiple RPC providers for both Mainnet and Testnet environments.
ZAN RPC
Nirvana RPC
Alchemy RPC
This guide covers and .
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from .
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
This guide will walk you through the process of building and deploying your first end-to-end decentralized application (dApp) on the Pharos blockchain. By the end of this guide, you will have a fully functional dApp that includes a smart contract, a backend, and a frontend.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from .
Indexing allows developers to efficiently query and access on-chain data without directly relying on raw RPC calls.
Instead of scanning the blockchain for every request, indexing services process and organize data in a way that makes it easier to retrieve, filter, and use within applications.
Query smart contract data efficiently
Track events and historical activity
Build dashboards and analytics
Power frontend applications with structured data
Monitor protocol and application-level metrics
While RPC endpoints are useful for direct interaction with the network, they are not optimized for complex queries or large-scale data retrieval.
Indexing services solve this by providing faster and more flexible access to blockchain data, making them essential for building scalable applications.
Pharos supports indexing solutions that help developers access and structure on-chain data efficiently.
Refer to the provider below for setup and integration details:
Goldsky
Clone the example repo:
Install OpenZeppelin Contracts:
Foundry uses forge to manage dependencies. Install OpenZeppelin contracts:
Create a New Solidity File:
Create a new file for your NFT contract:
Write the Token Contract:
Open src/Token.sol in your favorite text editor and add the following code:
Compile the Smart Contract:
Use forge to compile the contract:
Test the Smart Contract
Use forge to test the contract:
Set the private key:
Create a Deployment Script:
Create a new file for the deployment script:
Write the Deployment Script:
Open script/DeployToken.s.sol and add the following code:
Deploy the Contract:
Use forge to deploy the contract to the Pharos Testnet:
For Atlantic Testnet
For Testnet
Note: Replace with your token contract address
Open script/InteractToken.s.sol and add the following code:
Execute the script using forge:
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Insufficient Balance: Ensure your wallet has enough tokens to transfer.
Now that you’ve created and deployed your first NFT using Foundry.
This guide provides a comprehensive introduction to to creating and deploying an NFT on the Pharos blockchain. If you encounter any issues, refer to the Troubleshooting section or consult the Foundry documentation. Happy building! 🚀
Clone the example repo:
Set the private key:
Then
Connect the Frontend to the Smart Contract:
Open src/app.jsx and replace its content with the following code:
Replace Placeholder Values
Replace YOUR_CONTRACT_ADDRESS with the address of your deployed contract.
Start the React App
Navigate to the frontend directory and start the app:
Interact with the dApp:
Open your browser and navigate to http://localhost:3000.
Use the input field and buttons to interact with your smart contract.
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Frontend Connection Issues: Verify that the contract address and ABI are correct.
Now that you’ve built your first end-to-end dApp.
This guide provides a comprehensive introduction to building and deploying an end-to-end dApp on the Pharos blockchain. If you encounter any issues, refer to the Troubleshooting section or consult the Pharos documentation. Happy building! 🚀
git clone https://github.com/PharosNetwork/examples
cd examples/dapp/hardhat
npm installnpx hardhat vars set PRIVATE_KEYcd contract && npm install
npx hardhat ignition deploy ./ignition/modules/Counter.js --network pharosimport React, { useState } from "react";
import { ethers } from "ethers";
const contractAddress = "YOUR_CONTRACT_ADDRESS"; // Replace with your contract address
const abi = [
{
"inputs": [],
"name": "get",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "x",
"type": "uint256"
}
],
"name": "set",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
function App() {
const [value, setValue] = useState("");
const [storedValue, setStoredValue] = useState("");
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, abi, signer);
const handleSet = async () => {
await contract.set(value);
alert("Value set!");
};
const handleGet = async () => {
const result = await contract.get();
setStoredValue(result.toString());
};
return (
<div>
<h1>Simple Storage dApp</h1>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Enter a value"
/>
<button onClick={handleSet}>Set Value</button>
<button onClick={handleGet}>Get Value</button>
<p>Stored Value: {storedValue}</p>
</div>
);
}
export default App;npm startgit clone https://github.com/PharosNetwork/examples
cd examples/nft/foundry/contractforge install OpenZeppelin/openzeppelin-contracts --no-gittouch src/Token.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
contract Token is ERC721 {
constructor() ERC721("Token", "MTK") {}
}forge buildforge testexport PRIVATE_KEY=<your private key>touch script/DeployToken.s.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
import "../src/Token.sol";
contract DeployToken is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
Token token = new Token(1000000); // Initial supply of 1,000,000 tokens
vm.stopBroadcast();
}
}forge script script/DeployToken.s.sol --rpc-url <PHAROS_RPC_URL> --broadcastforge verify-contract \
<contract_address> \
--constructor-args <abi_encoded_args> \
src/Token.sol:Token \
--chain-id 688688 \
--verifier blockscout \
--verifier-url https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contractforge verify-contract \
<contract_address> \
--constructor-args <abi_encoded_args> \
src/Token.sol:Token \
--chain-id 688688 \
--verifier blockscout \
--verifier-url https://api.socialscan.io/pharos-testnet/v1/explorer/command_api/contract// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract InteractToken is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
Token token = Token(address(0x00)); // Replace with your token contract address
// Check balance
uint256 balance = token.balanceOf(msg.sender);
console.log("Balance:", balance);
// Transfer tokens
token.transfer(address(0x00), 100); // Replace with recipient address and amount
console.log("Tokens transferred");
vm.stopBroadcast();
}
}forge script script/InteractToken.s.sol --rpc-url <PHAROS_RPC_URL> --broadcastgit clone https://github.com/PharosNetwork/examples
cd examples/dapp/foundry
npm installexport PRIVATE_KEY=<your private key>cd contract
forge install OpenZeppelin/openzeppelin-contracts --no-git
forge script script/Counter.s.sol:CounterScript --rpc-url <PHAROS_RPC_URL> --broadcastimport React, { useState } from "react";
import { ethers } from "ethers";
const contractAddress = "YOUR_CONTRACT_ADDRESS"; // Replace with your contract address
const abi = [
{
"inputs": [],
"name": "get",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "x",
"type": "uint256"
}
],
"name": "set",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
function App() {
const [value, setValue] = useState("");
const [storedValue, setStoredValue] = useState("");
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, abi, signer);
const handleSet = async () => {
await contract.set(value);
alert("Value set!");
};
const handleGet = async () => {
const result = await contract.get();
setStoredValue(result.toString());
};
return (
<div>
<h1>Simple Storage dApp</h1>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Enter a value"
/>
<button onClick={handleSet}>Set Value</button>
<button onClick={handleGet}>Get Value</button>
<p>Stored Value: {storedValue}</p>
</div>
);
}
export default App;npm startOps activation example
--stake 100000 (override required)
--stake 1000000 (ops default)
Complete the Docker Compose setup for your network using the deployment guide linked below. To create and start the container for the first time, run this from the workspace containing docker-compose.yml:
Use docker-compose up -d if your installation uses the standalone Compose command.
If the container already exists but is stopped, use the matching docker start command below.
Follow , which uses container_name: atlantic-testnet.
Follow Using Docker and select the For Mainnet downloads (mainnet.genesis and mainnet.version). Its Compose example uses container_name: pharos-node.
If you customized container_name in docker-compose.yml, use your container name in the docker start and docker exec commands.
On first start, the Docker image automatically generates the node's cryptographic keys in keys/ under the mounted data directory, including domain.pub, stabilizing.pub, and their private-key files. The image also copies ops to the data directory on each start; see the Docker directory structure. No manual ./ops generate-keys step is needed for Docker deployment.
⚠️ Protect the private keys in keys/ and keep secure backups.
With the container running, open a shell for your network:
Run node and ./ops commands from the mounted data directory (/data inside the container), where ops and keys/ are available. Commands in the container can access the node's local keys and local RPC. Continue with Join validator sets to generate an unsigned registration transaction using ./ops.
⚠️ The registration sender becomes the permanent validator owner (the Safe address when using a Safe). Changing the owner is not supported. A hardware wallet or Safe multisig is mandatory.
Use the latest ops tool and your initialized node's existing public-key files.
⚠️ The domain public key must be the real node key from keys/domain.pub, never empty or a placeholder. An empty _publicKey produces poolId = sha256("") and collides with other empty-key registrations.
The required flags are --domain-label (validator name) and --domain-endpoint (public endpoint). The endpoint must be reachable by other nodes and use TCP with the consensus port (default: 19000): tcp://YOUR_PUBLIC_IP:19000 or tcp://yourdomain:19000. Do not use 127.0.0.1 or localhost as the validator endpoint.
Replace the validator name and public endpoint below with your own. The examples use the node's existing ./keys/domain.pub and ./keys/stabilizing.pub files; these are also the defaults when the key-path flags are omitted. If your files are elsewhere, supply their actual paths.
Use the Atlantic RPC. Explicitly set --stake 100000 to meet Atlantic's 100,000 PROS activation threshold and override the ops default of 1,000,000 PROS.
./ops add-validator \
--domain-label "my-validator" \
--domain-endpoint "tcp://YOUR_PUBLIC_IP:19000" \
--rpc-endpoint "https://atlantic.dplabs-internal.com" \
--domain-pubkey "./keys/domain.pub" \
--stabilizing-pubkey "./keys/stabilizing.pub" \
--stake 100000 \
--unsignedUse the Pacific mainnet RPC. --stake 1000000 meets mainnet's 1,000,000 PROS activation threshold and matches the ops default.
./ops add-validator \
--domain-label "my-validator" \
--domain-endpoint "tcp://YOUR_PUBLIC_IP:19000" \
--rpc-endpoint "https://rpc.pharos.xyz" \
--domain-pubkey "./keys/domain.pub" \
--stabilizing-pubkey "./keys/stabilizing.pub" \
--stake 1000000 \
--unsigned--unsigned prints the staking contract address, value in wei, method, calldata, and function ABI without signing or broadcasting. VALIDATOR_PRIVATE_KEY is not required in this mode.
Contract / To: staking contract 0x4100000000000000000000000000000000000000 on both networks.
Method: registerValidator, a payable function that registers the node and locks the supplied stake.
Calldata / Data: the encoded function call, including the description, public keys, proofs of possession, and endpoint. Copy the complete hex string without line breaks.
Value: the stake sent with the transaction as msg.value, expressed in wei. The stake is not a separate function parameter.
Function ABI: the function definition used to decode calldata or enter parameters individually in a wallet.
--stake is in PROS tokens. Ops converts it to wei: 1 PROS = 10^18 wei. Use the amounts for your network's example:
Ops --stake
100000 PROS
Safe value / browser wallet amount
100000 PROS
⚠️ Safe's value field uses PROS, not wei. If you change --stake, regenerate the unsigned output and match the wallet amount or Cast value to it. Keep enough PROS available for transaction fees in addition to the stake.
Choose one submission method below. Submit from your intended owner wallet on the same network as your node and RPC.
Atlantic testnet
Use an Atlantic testnet Safe UI and a Safe wallet on Atlantic (chain ID 688689). Set Value to 100000 PROS for the Atlantic example. Verify the network against the Atlantic network information.
Pacific mainnet
Use Pharos Safe for Pacific mainnet (chain ID 1672). Set Value to 1000000 PROS for the mainnet example. Verify the network against the Pacific network information.
Build and execute the transaction
Open the intended owner Safe's transaction builder and set To to 0x4100000000000000000000000000000000000000.
Set Value in PROS to match the unsigned registration for your network, using the amounts above.
Enable custom data (recommended) and paste the generated calldata into Data, with no newlines or line breaks.
Review the network, Safe address, destination, value, and calldata. Collect the required Safe signer approvals and execute the transaction.
Alternatively, disable custom data, load the function ABI printed by ops, and select registerValidator. Enter its six string parameters individually: description, public key, public-key proof of possession, BLS public key, BLS proof of possession, and endpoint, in ABI order. Use the exact values decoded from the generated calldata, and keep the same PROS value.
To obtain those values with Cast (included in Foundry):
Replace the data placeholder with the complete generated calldata. Enter each decoded string's contents without the surrounding display quotes.
Using MetaMask as an example:
Connect your hardware wallet, select the intended owner account, and add or switch to your node's network using the Atlantic testnet or Pacific mainnet network information.
In Settings → Transactions, enable Show Hex Data.
Click Send, choose PROS, and set To to 0x4100000000000000000000000000000000000000.
Set Amount in PROS to match your generated stake (100000 for the Atlantic example or 1000000 for the Pacific mainnet example). Paste the generated calldata into Hex data, with no newlines or line breaks.
Click Continue, review the transaction details, then confirm and sign with your hardware wallet to send it.
Install Foundry to use cast, then connect and unlock your Ledger. Use your network's example and replace the data placeholder with the complete calldata printed by ops, on one line:
This uses the Atlantic RPC and the value for --stake 100000:
cast send 0x4100000000000000000000000000000000000000 \
--ledger \
--rpc-url "https://atlantic.dplabs-internal.com" \
--value 100000000000000000000000 \
--data "YOUR_CALLDATA_PRINTED_BY_OPS"This uses the Pacific mainnet RPC and the value for --stake 1000000:
cast send 0x4100000000000000000000000000000000000000 \
--ledger \
--rpc-url "https://rpc.pharos.xyz" \
--value 1000000000000000000000000 \
--data "YOUR_CALLDATA_PRINTED_BY_OPS"The unsuffixed Cast --value is in wei. If you generated a different stake, use its printed wei value. Check that the Ledger account is your intended permanent owner before approving the transaction on the device.
Registration: minimum 100 PROS; accepted registration stake is 100–50,000,000 PROS.
Atlantic testnet staking/activation: total stake (self-stake + delegators) must reach 100,000 PROS.
Pacific mainnet activation: total stake (self-stake + delegators) must reach 1,000,000 PROS. Registering with at least this amount activates the validator at the next epoch.
Registration below the network's activation threshold leaves the validator in the bootstrapping state until sufficient total stake is reached. Registering with 100 PROS alone does not activate a validator on either network.
After registration, check the node's health with the ops command:
Then test network connectivity to other validators:
Minimum stake required for activation
100,000 PROS
1,000,000 PROS
docker compose up -ddocker start atlantic-testnetdocker start pharos-nodedocker exec -it atlantic-testnet /bin/bashdocker exec -it pharos-node /bin/bashcast decode-calldata \
"registerValidator(string,string,string,string,string,string)" \
"YOUR_CALLDATA_PRINTED_BY_OPS"./ops health-check./ops network-testThis guide will walk you through the process of creating and deploying a simple Uniswap-like decentralized exchange (DEX) contract on the Pharos blockchain. By the end of this guide, you will have a basic understanding of how automated market makers (AMMs) work and how to implement a simple DEX.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from nodejs.org.
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Clone the example repo:
Install OpenZeppelin Contracts:
Foundry uses forge to manage dependencies. Install OpenZeppelin contracts:
Create a New Solidity File:
Create a new file for your uniswap contract:
Write the uniswap Contract:
Open src/Uniswap.sol in your favorite text editor and add the following code:
Compile the Smart Contract:
Use forge to compile the contract:
Test the Smart Contract
Use forge to test the contract:
Create a Deployment Script:
Create a new file for the deployment script:
Write the Deployment Script:
Open script/DeployUniswap.s.sol and add the following code:
Set the private key:
Deploy the Contract:
Use forge to deploy the contract to the Pharos Testnet:
Open script/InteractUniswap.s.sol and add the following code:
Execute the script using forge:
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Test Failures: Check the test output for detailed error messages and adjust the tests accordingly.
This guide provides a comprehensive introduction to creating and deploying a Uniswap-like contract on the Pharos blockchain using Foundry. If you encounter any issues, refer to the Foundry documentation or the troubleshooting section. Happy building! 🚀
AI agent infrastructure
Anvita Flow is an AI agent infrastructure. It provides the underlying layer that lets developers convert their Pharos Skills into hosted, discoverable, and callable Service Agents, without building their own runtime, routing, or settlement infrastructure.
Skill
As infrastructure, Anvita Flow handles everything between your Skill and the end user:
This guide will walk you through the process of calling EVM contracts from the WASM code e.g., Rust.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Rust: Install it from rust-lang.org.
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Install the pharos wasm toolchain plugin using the Cargo tool:
Add the wasm32-unknown-unknown build target to your Rust compiler:
You should now have it available as a Cargo subcommand:
Note: Pharos built the chain excution client from scratch using C++, and we design, developed and open-sourced the VM, which combines EVM and WASM in a deeply compiled VM arch with native interoperability. The Rust toolchain and Rust SDK are forked from Stylus toolchain and SDK to facilitate easier adoption, the change here is the removal of Arbitrum ink billing and the simplification of the activation and compilation process on Pharos.
Clone the example repo:
Create a new file for your interoperability contract:
Write the interoperability Contract:
Open src/lib.rs in your favorite text editor and add the following code:
Compile the Smart Contract:
Use cargo to compile the contract:
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Insufficient Balance: Ensure your wallet has enough tokens to transfer.
git clone https://github.com/PharosNetwork/examples
cd examples/uniswap/foundry/contractforge install OpenZeppelin/openzeppelin-contracts --no-gittouch src/Uniswap.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
contract Uniswap {
address public tokenA;
address public tokenB;
uint256 public reserveA;
uint256 public reserveB;
constructor(address _tokenA, address _tokenB) {
tokenA = _tokenA;
tokenB = _tokenB;
}
function addLiquidity(uint256 amountA, uint256 amountB) external {
require(IERC20(tokenA).transferFrom(msg.sender, address(this), amountA), "Transfer failed");
require(IERC20(tokenB).transferFrom(msg.sender, address(this), amountB), "Transfer failed");
reserveA += amountA;
reserveB += amountB;
}
function removeLiquidity(uint256 amountA, uint256 amountB) external {
require(reserveA >= amountA && reserveB >= amountB, "Insufficient reserves");
reserveA -= amountA;
reserveB -= amountB;
require(IERC20(tokenA).transfer(msg.sender, amountA), "Transfer failed");
require(IERC20(tokenB).transfer(msg.sender, amountB), "Transfer failed");
}
function swapAToB(uint256 amountA) external {
uint256 amountB = (amountA * reserveB) / reserveA;
require(IERC20(tokenA).transferFrom(msg.sender, address(this), amountA), "Transfer failed");
require(IERC20(tokenB).transfer(msg.sender, amountB), "Transfer failed");
reserveA += amountA;
reserveB -= amountB;
}
function swapBToA(uint256 amountB) external {
uint256 amountA = (amountB * reserveA) / reserveB;
require(IERC20(tokenB).transferFrom(msg.sender, address(this), amountB), "Transfer failed");
require(IERC20(tokenA).transfer(msg.sender, amountA), "Transfer failed");
reserveB += amountB;
reserveA -= amountA;
}
}forge buildforge testtouch script/DeployUniswap.s.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
import "../src/Uniswap.sol";
contract DeployUniswap is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
address tokenA = address(0xAA); // Replace with the address of token A
address tokenB = address(0xBB); // Replace with the address of token B
Uniswap uniswap = new Uniswap(tokenA, tokenB);
vm.stopBroadcast();
}
}export PRIVATE_KEY=<your private key>forge script script/DeployUniswap.s.sol --rpc-url <PHAROS_RPC_URL> --broadcast// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
import "../src/Uniswap.sol";
import "../test/Uniswap.t.sol";
contract DeployUniswap is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
MockERC20 tokenA = new MockERC20("TokenA", "TKA");
MockERC20 tokenB = new MockERC20("TokenB", "TKB");
Uniswap uniswap = new Uniswap(address(tokenA), address(tokenB));
console.log("TokenA deployed at:", address(tokenA));
console.log("TokenB deployed at:", address(tokenB));
console.log("Uniswap deployed at:", address(uniswap));
vm.stopBroadcast();
}
}forge script script/InteractUniswap.s.sol --rpc-url <PHAROS_RPC_URL> --broadcastcargo install --git https://github.com/PharosNetwork/pharos-cargo-stylusrustup target add wasm32-unknown-unknowncargo stylus --help
Cargo subcommand for developing Pharos Stylus projectsgit clone https://github.com/PharosNetwork/examples
cd examples/interoperability/call-evm-from-wasmtouch src/lib.rs#![cfg_attr(not(feature = "export-abi"), no_main)]
extern crate alloc;
use stylus_sdk::{
abi::Bytes,
alloy_primitives::{Address, U256},
prelude::*,
stylus_core::calls::context::Call,
};
use alloy_sol_types::{sol, SolCall};
sol! {
interface IErc20 {
function mint(uint256 value) external;
}
}
#[storage]
#[entrypoint]
pub struct Interoperability;
#[public]
impl Interoperability {
/// Here we can call an EVM contract with the given target address and calldata.
pub fn execute(&self, target: Address, data: Bytes) -> Bytes {
let result = self.vm().call(&Call::default(), target, &data);
result.unwrap().into()
}
/// Here we can transfer ETH to the given target address.
pub fn transfer_eth(&self, to: Address, amount: U256) -> Result<(), Vec<u8>> {
self.vm().transfer_eth(to, amount)
}
/// Here we call an ERC20 token contract written in Solidity.
pub fn mint_erc20(&self, erc20: Address, value: U256) -> Bytes {
self.execute(erc20, Bytes(IErc20::mintCall {
value,
}.abi_encode()))
}
}cargo stylus check --endpoint=<PHAROS_RPC_URL>cargo stylus deploy --private-key=<YOUR_PRIVATE_KEY> --endpoint=<PHAROS_RPC_URL>Ops value / Cast --value
100000000000000000000000 wei
Ops --stake
1000000 PROS
Safe value / browser wallet amount
1000000 PROS
Ops value / Cast --value
1000000000000000000000000 wei
Routing
Connects incoming requests to the right Service Agent
Settlement
Handles per-call billing automatically via the x402 micropayment protocol
Wallet & Earnings
Publishing Guide
See: Publishing a Service Agent on Anvita Flow
A packaged set of on-chain capabilities built using the Pharos Skill Engine
Service Agent
A hosted runtime that wraps a Skill, gives it an identity, and exposes it as a callable service on the network
Steward Agent
A user's personal AI assistant that finds and calls Service Agents on their behalf
Anvita On
The user-facing chat interface where users interact with their Steward Agent
Agent Card
The public profile of a Service Agent — name, capabilities, example tasks, pricing
Marketplace
The registry where all published Service Agents are listed and discoverable
x402 Protocol
The micropayment protocol powering per-call billing and value settlement
Developer Console
The interface where developers upload, debug, and publish Service Agents
Agent hosting
Runs your Service Agent in a managed runtime
Agent identity
Generates a unique identity and service profile for each published agent
Discovery
User makes a request in Anvita On
↓
Steward Agent searches the Marketplace for a matching Service Agent
↓
Anvita Flow routes the request to the Service Agent
↓
Service Agent runs the Skill and returns the result
↓
Steward Agent delivers the result to the user
↓
Settlement handled automatically via x402Write a Pharos Skill
↓
Package it as a zip with SKILL.md at the root
↓
Upload to the Anvita Flow Developer Console
↓
Configure runtime and complete the Agent Card
↓
Debug and publish
↓
Service Agent is live — discoverable and callable on the networkAnvita Flow Home
Anvita On (User Chat)
Developer Console
Lists your Service Agent in the Marketplace so Steward Agents can find it
This guide describes how to deploy a Pharos node using pre-compiled binaries and the ops tool.
📌 For the latest binary package, see Current Node Versions.
CPU
32 cores, 2.8GHz or faster, AMD Milan EPYC or Intel Xeon Platinum
Download the latest binary release from GitHub:
The extracted package contains:
bin/pharos_light - Pharos node binary
bin/pharos_cli - Pharos CLI tool
Download the genesis configuration and VERSION file based on your network:
For Atlantic Testnet:
For Mainnet:
Note: When upgrading to a new version, you must also re-download the VERSION file to keep it in sync.
Download the node configuration file (choose one based on your node type):
For Archive/Full/Validator Node:
Note: Pruning is disabled by default. To enable pruning, see .
For TraceDB Node:
Download the latest ops tool for Linux:
Set a password that will be used to encrypt your node's cryptographic keys:
Important:
Remember this password - you'll need it to start your node
The password is stored in ./.password file
Keep this password secure and backed up
Generate the domain and stabilizing keys for your node:
This will create the following files in the ./keys/ directory:
domain.key - ECDSA private key (prime256v1)
domain.pub - ECDSA public key
stabilizing.key - BLS12-381 private key
Initialize the node with genesis state:
This command will:
Create the storage database
Initialize the genesis block
Prepare the node for first startup
Start the Pharos node service:
The node will start in daemon mode and begin syncing with the network.
Check the logs to verify the node is running:
For production deployments, it's recommended to manage the Pharos node with systemd.
Create a systemd service file:
Enable and start the service:
Check service status:
View logs:
Note: If not using systemd, you can manually stop the node with pkill pharos_light and restart with ./ops start --config ./pharos.conf
After deployment, your directory structure should look like:
If you want to run a validator node, you need to register your node on-chain after it has synced with the network.
The validator registration requires an Ethereum-compatible private key that holds sufficient tokens for staking. Set it as an environment variable:
Security Note: Do not pass the private key via command-line arguments. Always use the environment variable.
Parameters:
--rpc-endpoint - RPC endpoint of a synced Pharos node (default: http://127.0.0.1:18100)
--domain-label - A description for your validator
--domain-endpoint - The public-facing endpoint of your node for P2P communication
On success, the command will output the transaction hash and your Node ID.
To voluntarily exit your validator from the network:
Protect your keys: The files in keys/ directory contain your node's private keys
Secure your password: Keep the .password file secure with proper permissions
Backup important files: Backup keys/, .password
- Configure Prometheus metrics for your node
To register as a validator, see Step 9 above
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from .
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
This guide will walk you through the process of creating and deploying your first token on the Pharos blockchain. By the end of this guide, you will have a fully functional token contract and understand how to interact with it.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from .
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Chainlink CCIP enables secure cross-chain messaging and token transfers between supported blockchains.
On Pharos, CCIP allows developers to build applications that interact with multiple networks, including cross-chain DeFi protocols, token bridges, and omnichain applications.
The following cross-chain lanes are currently available:
Pharos ↔ Ethereum
Pharos ↔ Jovay
Oracles provide access to off-chain data within smart contracts. They enable applications on Pharos to interact with real-world information such as asset prices, market data, and external events.
For many use cases, especially in DeFi and real-world asset applications, oracles are a critical component for bringing reliable data on-chain.
Access price feeds and market data
Build applications that rely on real-world inputs
Trigger contract logic based on external conditions
Pharos ↔ Base
Chainlink CCIP is available on Pacific Mainnet with the following configuration:
Chain Selector
7801139999541420232
Router Contract
0x4e52dD94e9BCfeFE3C78153bDfB0AB1d30687297
Chainlink CCIP is typically used for:
Cross-chain token transfers
Sending messages between smart contracts on different chains
Building omnichain applications
Integrating liquidity and users from other ecosystems
Use the Router contract to initiate cross-chain messages and token transfers
Ensure correct configuration of chain selectors and destination chains
Suitable for applications requiring secure and standardized cross-chain communication
For detailed integration guides and supported configurations:
https://docs.chain.link/ccip/directory/mainnet/chain/pharos-mainnet
Configure your contracts to interact with the CCIP Router
Test cross-chain messaging and transfers
Combine with other infrastructure such as RPC and oracles for full application workflows
Power lending, trading, and RWA protocols
Integrate off-chain data into on-chain workflows
Smart contracts operate in a deterministic on-chain environment and cannot directly access external data.
Oracles bridge this gap by securely delivering off-chain data to the blockchain, enabling more advanced and dynamic applications.
Pharos supports oracle solutions for different data and integration needs.
Refer to the pages below for setup and usage:
Supra DORA Pull Oracle
Chainlink Data Streams
stabilizing.pub - BLS12-381 public key--domain-pubkey - Path to the domain public key file generated in Step 5
--stabilizing-pubkey - Path to the stabilizing public key file generated in Step 5
--stake - Stake amount in tokens (default: 1,000,000 tokens)
pharos.confFirewall configuration: Ensure required ports are open (18100, 19000, 20000)
Regular updates: Keep your node software up to date with latest releases
Memory
256 GB
Storage
5 TB SSD with at least 350MiB/s bandwidth and 30000 IOPS
Network Bandwidth
0.5 Gbps
Open Files Limit
ulimit -n ≥ 10000000
Clone the example repo:
Install OpenZeppelin Contracts:
Foundry uses forge to manage dependencies. Install OpenZeppelin contracts:
Create a New Solidity File:
Create a new file for your token contract:
Write the Token Contract:
Open src/Token.sol in your favorite text editor and add the following code:
Compile the Smart Contract:
Use forge to compile the contract:
Test the Smart Contract
Use forge to test the contract:
Create a Deployment Script:
Create a new file for the deployment script:
Write the Deployment Script:
Open script/DeployToken.s.sol and add the following code:
Set the private key:
Deploy the Contract:
Use forge to deploy the contract to the Pharos Testnet:
For Atlantic Testnet
For Testnet
Note: Replace with your token contract address
Open script/InteractToken.s.sol and add the following code:
Execute the script using forge:
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Insufficient Balance: Ensure your wallet has enough tokens to transfer.
Now that you’ve created and deployed your first token using Foundry.
This guide provides a comprehensive introduction to creating and deploying a token on the Pharos blockchain using Foundry. If you encounter any issues, refer to the Troubleshooting section or consult the Foundry documentation. Happy building! 🚀
export WORKSPACE=pharos
mkdir -p /data/$WORKSPACE && cd /data/$WORKSPACEwget https://github.com/PharosNetwork/resources/releases/download/v0.16.0/pharos-v0.16.0-3cf35988.tar.gz
tar -zxvf pharos-v0.16.0-3cf35988.tar.gzwget -O genesis.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/atlantic.genesis
wget -O bin/VERSION https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/atlantic.versionwget -O genesis.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/mainnet.genesis
wget -O bin/VERSION https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/mainnet.versionwget -O pharos.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/conf/full.confwget -O pharos.conf https://raw.githubusercontent.com/PharosNetwork/resources/refs/heads/main/conf/traceDB.confwget https://github.com/PharosNetwork/ops/releases/latest/download/ops-linux-amd64 -O ops
chmod +x ops./ops set-password YOUR_SECURE_PASSWORD./ops generate-keys./ops bootstrap --config ./pharos.conf./ops start --config ./pharos.conftail -f log/aldaba.logsudo tee /etc/systemd/system/pharos.service > /dev/null <<EOF
[Unit]
Description=Pharos Node
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=/data/$WORKSPACE/bin
Environment="CONSENSUS_KEY_PWD=$(cat /data/$WORKSPACE/.password)"
Environment="PORTAL_SSL_PWD=$(cat /data/$WORKSPACE/.password)"
ExecStart=/data/$WORKSPACE/bin/pharos_light -c /data/$WORKSPACE/pharos.conf
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# Resource limits
LimitNOFILE=10000000
[Install]
WantedBy=multi-user.target
EOFsudo systemctl daemon-reload
sudo systemctl enable pharos
sudo systemctl start pharossudo systemctl status pharossudo journalctl -u pharos -f/data/pharos/
├── bin/
│ ├── pharos_light
│ ├── pharos_cli
│ └── VERSION
├── keys/
│ ├── domain.key
│ ├── domain.pub
│ ├── stabilizing.key
│ └── stabilizing.pub
├── data/
│ └── (blockchain data)
├── log/
│ ├── aldaba.log
│ ├── pamir.log
│ └── (other logs)
├── genesis.conf
├── pharos.conf
├── ops
└── .passwordexport VALIDATOR_PRIVATE_KEY=your_private_key_hex./ops add-validator \
--rpc-endpoint http://127.0.0.1:18100 \
--domain-label "My Validator Node" \
--domain-endpoint "your-node-public-endpoint:20000" \
--domain-pubkey ./keys/domain.pub \
--stabilizing-pubkey ./keys/stabilizing.pub \
--stake 1000000export VALIDATOR_PRIVATE_KEY=your_private_key_hex
./ops exit-validator \
--rpc-endpoint http://127.0.0.1:18100 \
--domain-pubkey ./keys/domain.pubgit clone https://github.com/PharosNetwork/examples
cd examples/token/foundry/contractforge install OpenZeppelin/openzeppelin-contracts --no-gittouch src/Token.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
contract Token is ERC20 {
constructor(uint256 initialSupply) ERC20("Token", "MTK") {
_mint(msg.sender, initialSupply);
}
}forge buildforge testtouch script/DeployToken.s.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
import "../src/Token.sol";
contract DeployToken is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
Token token = new Token(1000000); // Initial supply of 1,000,000 tokens
vm.stopBroadcast();
}
}export PRIVATE_KEY=<your private key>forge script script/DeployToken.s.sol --rpc-url <PHAROS_RPC_URL> --broadcastforge verify-contract \
<contract_address> \
--constructor-args <abi_encoded_args> \
src/Token.sol:Token \
--chain-id 688688 \
--verifier blockscout \
--verifier-url https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contractforge verify-contract \
<contract_address> \
--constructor-args <abi_encoded_args> \
src/Token.sol:Token \
--chain-id 688689 \
--verifier blockscout \
--verifier-url https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contract// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract InteractToken is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
Token token = Token(address(0x00)); // Replace with your token contract address
// Check balance
uint256 balance = token.balanceOf(msg.sender);
console.log("Balance:", balance);
// Transfer tokens
token.transfer(address(0x00), 100); // Replace with recipient address and amount
console.log("Tokens transferred");
vm.stopBroadcast();
}
}forge script script/InteractToken.s.sol --rpc-url <PHAROS_RPC_URL> --broadcastClone the example repo:
Install OpenZeppelin Contracts:
Create a New Solidity File:
Create a new file for your NFT contract:
Write the Token Contract:
Open contracts/Token.sol in your favorite text editor and add the following code:
Compile the Smart Contract:
Test the Smart Contract
Set the private key:
Deploy the Contract:
Add the following content to your hardhat.config.js file
For Atlantic Testnet
chain_name
pharos
chain_id
688689
rpc_endpoint
For Testnet
chain_name
pharos
chain_id
688688
rpc_endpoint
Run the hardhat verify command
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Insufficient Balance: Ensure your wallet has enough tokens to transfer.
Now that you’ve created and deployed your first NFT using Hardhat.
This guide provides a comprehensive introduction to to creating and deploying an NFT on the Pharos blockchain. If you encounter any issues, refer to the Troubleshooting section or consult the Hardhat documentation. Happy building! 🚀
git clone https://github.com/PharosNetwork/examples
cd examples/nft/hardhat/contractnpm installtouch contracts/Token.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract Token is ERC721 {
uint256 id;
constructor() ERC721("Token", "MTK") {}
function mint(address to) public returns (uint256) {
id += 1;
_mint(to, id);
return id;
}
}npx hardhat compilenpx hardhat testnpx hardhat vars set PRIVATE_KEYnpx hardhat ignition deploy ./ignition/modules/Token.js --network pharosrequire("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.28",
networks: {
pharos: {
url: "https://atlantic.dplabs-internal.com",
accounts: [vars.get("PRIVATE_KEY")],
},
},
etherscan: {
customChains: [
{
network: "pharos",
chainId: 688689,
urls: {
apiURL: "https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contract",
browserURL: "https://atlantic.pharosscan.xyz/",
},
},
],
apiKey: {
pharos: "Put a random string", // Note we don't need a apiKey here, just leave a random string
},
}
};require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.28",
networks: {
pharos: {
url: "https://testnet.dplabs-internal.com",
accounts: [vars.get("PRIVATE_KEY")],
},
},
etherscan: {
customChains: [
{
network: "pharos",
chainId: 688688, // Or use Pharos Atlantic Testnet chain id 688689
urls: {
apiURL: "https://api.socialscan.io/pharos-testnet/v1/explorer/command_api/contract",
browserURL: "https://testnet.pharosscan.xyz/",
},
},
],
apiKey: {
pharos: "Put a random string", // Note we don't need a apiKey here, just leave a random string
},
}
};npx hardhat verify --network pharos <contract_address> <constructor_args_params>PROS
RPC URL
Block Explorer
For the current node binary and Docker image versions, see Current Node Versions.
Network
Pharos Pacific Mainnet
ChainID
1672
Coin Symbol/Ticker
Pharos has deployed Chainlink self-managed price feeds using the Chainlink Runtime Environment (CRE). A CRE workflow periodically fetches price data from the Chainlink Data Streams API, checks deviation and heartbeat thresholds, and pushes updates on-chain to a central cache contract.
Each price pair has a dedicated proxy contract that exposes the standard Chainlink Data Feeds interface, allowing any on-chain consumer to read the latest price via latestAnswer().
Decimals
Note: Self-Managed Feeds are pull-based and do not use a round-based aggregation model. As a result:
roundIdandansweredInRoundare always0. There are no OCR aggregation rounds — prices are written directly by CRE workflows.
startedAtandupdatedAtboth return the observation timestamp of the latest report. They are identical and do not represent distinct lifecycle events of a round.
Note: When consuming price data from the cache, always verify that the returned data is sufficiently fresh for your use case.
Use
latestAnswerTimestampForFeed(orlatestReportTimestampon the proxy) to retrieve the timestamp alongside the answer.
Note: The stored timestamp reflects the observations timestamp of the report — the time at which the price was observed by the Chainlink DON — not the time the transaction was included on-chain. Compare this value against the current block timestamp and enforce a maximum acceptable staleness threshold appropriate for your application.
This guide will walk you through the process of creating and deploying your first token on the Pharos blockchain. By the end of this guide, you will have a fully functional token contract and understand how to interact with it.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Node.js: Install it from nodejs.org.
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Clone the example repo:
Install OpenZeppelin Contracts:
Create a New Solidity File:
Create a new file for your token contract:
Write the Token Contract:
Open contracts/Token.sol in your favorite text editor and add the following code:
Compile the Smart Contract:
Test the Smart Contract
Set the private key:
Deploy the Contract:
Add the following content to your hardhat.config.js file
For Atlantic Testnet
For Testnet
Run the hardhat verify command
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Insufficient Balance: Ensure your wallet has enough tokens to transfer.
Now that you’ve created and deployed your first token using Hardhat.
This guide provides a comprehensive introduction to creating and deploying a token on the Pharos blockchain using Hardhat. If you encounter any issues, refer to the Troubleshooting section or consult the Hardhat documentation. Happy building! 🚀
The Pharos MCP server brings Pharos chain data and onchain operations into any client that speaks the — Claude Desktop, Cursor, Windsurf, Codex, and custom agent runners.
Instead of writing RPC plumbing, you ask your AI assistant in plain language — "what's the latest Pharos block?", "check the PROS balance of 0x…" — and it calls the right chain method for you.
x402 is an open, internet-native payment protocol. The HTTP 402 status code was defined in the early HTTP/1.1 specification as “Payment Required.” However, for a long time, it served only as a reserved status code with few practical applications. With the maturation of blockchain and crypto-payment infrastructure, this status code has been reactivated, evolving into a lightweight, trustless mechanism for content payment.
In this model, when a client requests protected resources (such as articles, API responses, or data streams), the server does not return the content directly. Instead, it returns an HTTP 402 response accompanied by payment instructions (e.g., recipient address, amount, supported payment networks, etc.). The client initiates a micro-payment—either on-chain or off-chain—via a wallet. Once the server verifies the validity of the transaction, it unlocks and delivers the requested content. In decentralized networks like Pharos, this process is typically implemented as follows: the user sends a micro-token transfer to a specified address → the network node or server-side listens for and verifies the transaction → the original requested resource is automatically returned upon successful verification. The entire process requires no registration, login, or traditional payment gateways, truly achieving “pay-as-you-go, instant access.”
This mechanism provides native, programmable payment capabilities for AI agents, automated services, or IoT devices, serving as one of the key infrastructures for building an Autonomous Economy.
The Ethereum Virtual Machine (EVM) is the core component of the Ethereum blockchain, responsible for executing smart contracts and managing the state of the network. It acts as a decentralized computer, maintaining a global state and executing program instructions defined in smart contracts. Key features include:
State Machine: Executes transactions and changes the blockchain state.
Deterministic Execution: Given the same input, it will always produce the same output.
Isolated Execution
The official Solidity documentation is the most comprehensive and up-to-date resource for learning about the Solidity language. It covers everything from basic syntax to advanced topics and provides detailed explanations, examples, and code snippets.
Link:
CryptoZombies: An interactive online course that teaches Solidity through building a zombie game.
https://atlantic.dplabs-internal.com"
api_host
https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contract
explorer_url
https://atlantic.pharosscan.xyz/
https://testnet.dplabs-internal.com
api_host
https://api.socialscan.io/pharos-testnet/v1/explorer/command_api/contract
explorer_url
https://testnet.pharosscan.xyz/
GET /api) to the resource server for a specific protected resource.Payment Trigger Response: The server returns an HTTP 402 Payment Required status code. A Base64-encoded PaymentRequired object is carried in the response body via the PAYMENT-REQUIRED header. This object defines the payment scheme, target network, and specific payment parameters required for the current transaction.
Scheme Selection and Payment Payload Generation: The client parses the payment requirements returned by the server and selects a specific payment scheme based on its own supported payment capabilities. Subsequently, the client constructs a PaymentPayload containing the necessary metadata according to the protocol specifications of that scheme.
Secondary Request Submission: The client re-initiates the resource request and attaches the signed PaymentPayload in the PAYMENT-SIGNATURE HTTP header.
Verification Routing: Upon receiving the signed request, the resource server executes validation logic. The server can choose to perform local verification or forward the PaymentPayload and the original PaymentRequirements to the Facilitator's /verify interface for remote verification.
Compliance Check: The Facilitator verifies the payload according to the specified payment scheme and blockchain network rules, then returns a Verification Response to the resource server.
Access Control Decisions:
Verification Failed: If the verification does not pass, the resource server returns a 402 status code and error details again.
Verification Succeeded: If the verification passes, the resource server confirms the payment credential is valid and immediately initiates business logic processing (Do Work), preparing for on-chain settlement.
Transaction Broadcasting: The resource server is responsible for initiating fund settlement. This can be completed by interacting directly with a blockchain node or by delegating it to a Facilitator. If delegated, the server sends the payment credential to the Facilitator's /settle interface.
Block Inclusion: The Facilitator submits the transaction to the blockchain network (Submit tx).
Settlement Feedback: Once the on-chain transaction is confirmed, the Facilitator returns a settlement execution response (Settlement Response) containing the transaction hash (tx_hash) to the resource server.
Resource Delivery: The resource server sends an HTTP 200 OK response to the client, placing the requested resource content in the response body. Simultaneously, to provide proof of payment, the server attaches Base64-encoded settlement response information in the PAYMENT-RESPONSE response header, marking the completion of the entire "pay-before-receive" closed loop.
Pharos Support: The x402 protocol itself is chain-agnostic. On Pharos, it supports all ERC-20 tokens. Pharos’s high-performance features (30,000 TPS, sub-second finality) make it particularly suitable for x402 micro-payment scenarios.
x402 enables micro-payment and pay-per-use scenarios that were previously impossible due to economic unfeasibility. Below are some possible application scenarios:
AI Agents subscribing to content services with auto-renewal: AI agents can represent users in subscribing to content services such as industry updates, financial newsletters, or in-depth analysis reports, automatically completing periodic fee payments and renewal operations through integrated digital wallets.
Pay-per-use AI capability calls: Users or systems pay based on the actual number of uses when calling Large Language Model (LLM) inference, image generation, or other AI APIs.
Payments between agents: Supports peer-to-peer payments between multiple autonomous agents for data sharing, task collaboration, or service provision without human intervention.
Automated financial trading agents: Based on real-time market data and external information sources (e.g., paid data streams), these agents autonomously execute high-frequency trading strategies and dynamically procure necessary data resources to optimize decision-making.
AI-assisted diagnostic reports: After users upload medical images, they pay to unlock structured diagnostic suggestions.
Personalized health advice APIs: Wearable devices call health analysis models and pay-per-call to obtain deep insights.
Access to electronic medical record fragments: Third-party applications request specific medical visit records, and patients authorize and pay a small fee to share data.
AI Tutor single-question answering: Basic prompts are free, while in-depth analysis requires payment to unlock.
Professional course fragment unlocking: After a trial preview, users pay by the minute or chapter to continue learning.
Single academic paper access: No subscription required; pay per paper for instant PDF access.
Personalized recommendation engine calls: E-commerce platforms pay AI service providers based on successful conversions.
AR try-on/make-up experience: Payment is required to unlock real-time rendering before high-precision virtual trials.
Supply chain risk assessment queries: Enterprises pay-per-query for supplier risks, which can be used for procurement decision-making.
Computing power rental: Billing by the second for using GPU/CPU resources for training or inference.
IoT micro-transactions: Sensor networks pay automatically based on data points or events (e.g., environmental monitoring, smart meters).
Sustainability and carbon credits: Companies or individuals purchase carbon offsets on demand, with the API returning a 402 to trigger instant settlement.
Key Advantage: x402 transaction costs on Pharos are extremely low, making true micro-payments possible. Combined with instant finality, this enables real-time access control.

0x371Ed36aEe5bFFbED1A2aa9b7deD7F4EB30F5466
BTC/USD
0x00039d9e45394f473ab1f050a1b963e6b05351e52d71e507509ada0c95ed75b8
0x8b5DB228Fbccf588f08B073C235254aCb6791D36
ETH/USD
0x000362205e10b3a147d02792eccee483dca6c7b44ecce7012cb8c6e0b68b3ae9
0x1Ed3e8CdC173363593BfAD9bF77aa2A0e7351DB8
WETH/USD
Same as ETH/USD
Same as ETH/USD
WBTC/USD
0x00034a95e798013fd8bc6ac56707488e1c4622e5dffa9f3c2fe99f8105b4fa5b
0x518B75ab24251Db9c34c7173f62FBa6E773598CC
USDT/USD
0x0003a910a43485e0685ff5d6d366541f5c21150f0634c5b14254392d1a1c06db
0x525D92D6Caa35060B4D5e3deDa6F52BdA3804618
USDC/USD
0x00038f83323b6b08116d1614cf33a9bd71ab5e0abf0c9f1b783a74a43e7bd992
0x3f882C4F2836b1e5C5278ff2B9FccE2502A35C5d
LINK/USD
0x00036d7a1251e3f67d6658466b5e9e7fe8418af7feac9567ff322bff95cc2401
0x0644220E991a78Bf900648ce59211695cA47c480
BNB/USD
0x000335fd3f3ffa06cfd9297b97367f77145d7a5f132e84c736cc471dd98621fe
0x481Ecd48683ed2dD249d888Ab71B29D7b951B999
SOL/USD
0x0003b778d3f6b2ac4991302b89cb313f99a42467d6c9c5f96f57c29c0d2bc24f
0xdABF88CF4d4d2963631C7d69c394A83af2930983
XRP/USD
0x0003c16c6aed42294f5cb4741f6e59ba2d728f0eae2eb9e6d3f555808c59fc45
0x22AeEADe5afFbd25F9F2b40f186aCf003E478FB0
WSRUSD/RUSD
0x0007c129e624689953a2636c014531b2f0bd0c15f9a22211f53023354f309fa6
0xb5779919e2F183e59A34140331F7779cbFF1d1BC
SYZUSD/YZUSD
0x0007690a1667f3978924aa34b16a46ab12fcaae1b012c2f348905cb8299f58b7
0x36Aff5A272c80B3222c0C9138f2D43c7FAE2F372
STRUSD/TRUSD
0x00074de84889ebc1ae6505e72281365430ba0ccf73dfaea09c6036bb1199739f
0xbeD4692E83F7a2b51bDF8357085769dC87cA8E7d
0x14d427B112aD61B18E4b1ACdB2893987EB483760
BTC/USD
0x00037da06d56d083fe599397a4769a042d63aa73dc4ef57709d31e9971a5b439
0x5bcff8e4f4C02b5a3192A96DF4Eb5BB0Afdc36f0
ETH/USD
0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782
0x72A7c93b90fEbAA9fD53ded20411Fbb09eA72D03
WETH/USD
Same as ETH/USD
Same as ETH/USD
WBTC/USD
0x0003986bae710e410e6a6ec824db9ac91f97f6dd47fc5b28d028c14e825c5891
0xA5b7Fa94b22588f7E6df28f3F242b81e84DBE9Ef
USDT/USD
0x00032874077216155926e26c159c1c20a572921371d9de605fe9633e48d136f9
0xc5e5b55352c5a6b50Bc9F8e192948D9F51523649
USDC/USD
0x0003dc85e8b01946bf9dfd8b0db860129181eb6105a8c8981d9f28e00b6f60d9
0x70dcf7caD4D5C54e9b4f7286Cd5a2794b93B038a
LINK/USD
0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265
0x58DDd6596605f550db5ce1892867ecEA7Db5CDDC
BNB/USD
0x000387d7c042a9d5c97c15354b531bd01bf6d3a351e190f2394403cf2f79bde9
0x0D030427DFFe9644121429bD0CE6260EbeE5CEC6
SOL/USD
0x0003d338ea2ac3be9e026033b1aa601673c37bab5e13851c59966f9f820754d6
0xbb4b916035F28757e1fFC303CC81a1A29536F8F9
XRP/USD
0x00035e3ddda6345c3c8ce45639d4449451f1d5828d7a70845e446f04905937cd
0x731bF4Fbb91A47cf795f87b8E12148Cdd1a3FEBa
WSRUSD/RUSD
0x000706ad7092fdc2310335a82828efe1d4c0220e5bf0fc46100da431f3433743
0x045fE9F80ac65c2702985a889A6a500D9fA20897
SYZUSD/YZUSD
0x00070d19efa92f43763e29a8c444be98d19bf9c7487fb6e62ab5be7adb8ba896
0x3aca4c56afc1BA3CfF9C8f8e9911798ad82F538A
STRUSD/TRUSD
0x00074185003a58896a9e79b81bcfb4ab3212924f4dbb75629d795143ab967e06
0x9A648AC8B1bB657C64A73d9912e816973a998552
18
PriceFeed DeviationThreshold
0.5
PriceFeed Heartbeat
3600 seconds
SelfManagedFeedsCache
All feeds
0xd9F00c86ced2De92F6B7152b0492cb28edc4B5a8
PROS/USD
SelfManagedFeedsCache
All feeds
0x388c44D3B59d7e89582Caa6Fc475f282a0725480
PROS/USD
0x0003f73a1abb2eec1ecdde915c659cbb8f0cbb030156b75dd401fbfd5281e246
0x00037de4af3cb4fd0d82eed58a43ec144809f7fa7b4c6508eb628ad6d4d8b363
api_host
https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contract
explorer_url
https://atlantic.pharosscan.xyz/
api_host
https://api.socialscan.io/pharos-testnet/v1/explorer/command_api/contract
explorer_url
https://testnet.pharosscan.xyz/
chain_name
pharos
chain_id
688689
rpc_endpoint
chain_name
pharos
chain_id
688688
rpc_endpoint
https://atlantic.dplabs-internal.com"
https://testnet.dplabs-internal.com
/// @notice Get the latest answer
function latestAnswer() external view returns (int256);
/// @notice Get the latest answer
function latestTimestamp() external view returns (uint256);
/// @return feedId The Feed ID associated with this proxy
function getFeedId() external view returns (bytes32 feedId);git clone https://github.com/PharosNetwork/examples
cd examples/token/hardhat/contractnpm installtouch contracts/Token.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Token is ERC20 {
constructor(uint256 initialSupply) ERC20("Token", "MTK") {
_mint(msg.sender, initialSupply);
}
}npx hardhat compilenpx hardhat testnpx hardhat vars set PRIVATE_KEYnpx hardhat ignition deploy ./ignition/modules/Token.js --network pharosrequire("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.28",
networks: {
pharos: {
url: "https://atlantic.dplabs-internal.com",
accounts: [vars.get("PRIVATE_KEY")],
},
},
etherscan: {
customChains: [
{
network: "pharos",
chainId: 688689,
urls: {
apiURL: "https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contract",
browserURL: "https://atlantic.pharosscan.xyz/",
},
},
],
apiKey: {
pharos: "Put a random string", // Note we don't need a apiKey here, just leave a random string
},
}
};require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.28",
networks: {
pharos: {
url: "https://testnet.dplabs-internal.com",
accounts: [vars.get("PRIVATE_KEY")],
},
},
etherscan: {
customChains: [
{
network: "pharos",
chainId: 688688, // Or use Pharos Atlantic Testnet chain id 688689
urls: {
apiURL: "https://api.socialscan.io/pharos-testnet/v1/explorer/command_api/contract",
browserURL: "https://testnet.pharosscan.xyz/",
},
},
],
apiKey: {
pharos: "Put a random string", // Note we don't need a apiKey here, just leave a random string
},
}
};npx hardhat verify --network pharos <contract_address> <constructor_args_params>Authentication
None — public
Rate limit
120 requests per minute per IP
Chain ID
1672 (0x688)
Native token
PROS
Source
Add the server to your MCP client configuration:
No API key is required.
Some clients cannot connect to remote MCP servers directly. Bridge to the hosted endpoint with mcp-remote:
get_chain_info
eth_chainId, eth_blockNumber, web3_clientVersion
Chain ID, client version and latest block
get_block_number
eth_blockNumber
get_balance
eth_getBalance
Native PROS balance of an address
get_transaction_count
eth_getTransactionCount
eth_call
eth_call
Read-only contract call
estimate_gas
eth_estimateGas
send_raw_transaction
eth_sendRawTransaction
Broadcast an already-signed transaction
Tools that accept a block parameter take any of:
A named tag: latest, earliest, pending, safe, finalized
A decimal number: 13005874
A hex quantity: 0xc67432
Omitting it defaults to latest.
get_logs is capped at 1000 blocks per call, matching the upstream RPC limit. Requests spanning a wider range are rejected with a message telling you the requested size and the maximum, so page through larger ranges in 1000-block windows.
Narrow results with address and topics wherever possible — an unfiltered query over a wide range is slow for everyone.
The server is non-custodial: it never receives, stores, or asks for a private key. Signing happens entirely in your own wallet or script; the server only relays the resulting signed bytes to the network.
The flow is:
Build and sign a transaction locally with your own tooling (viem, ethers, a wallet).
Pass the resulting 0x-prefixed string to send_raw_transaction.
Use the returned transaction hash with get_transaction_receipt to confirm inclusion.
Because signing is local, an AI agent connected to this server can prepare and broadcast a transaction for you without ever having access to your key.
Run your own instance when you need a private RPC provider, a higher rate limit, or your own operational control.
Requires Node.js 20 or newer.
Point PHAROS_RPC_URL at any Pharos RPC — including a provider endpoint that carries an API key, or your own node:
Keep such a URL out of version control; it holds a credential.
PHAROS_RPC_URL
https://rpc.pharos.xyz
Upstream JSON-RPC endpoint
PHAROS_CHAIN_ID
1672
The server has no use for a private key or mnemonic — never pass one to it.
send_raw_transaction is disabled by default in self-hosted deployments. If you enable it on a publicly reachable instance, put authentication in front of it first; an open broadcast endpoint lets anyone push transactions through your node.
Set MCP_AUTH_TOKEN and terminate TLS in front of any instance you expose beyond localhost.
admin_*, debug_*, personal_* and txpool_* methods are not exposed.
Unknown MCP session
The session expired or the server restarted. Reconnect the client — a full client restart clears cached session state.
429 Too Many Requests
Rate limit reached. Back off and retry after the interval in the Retry-After header, or self-host for a higher limit.
Block range too large
Hosted endpoint
https://mcp.pharos.xyz/mcp
Transport
Streamable HTTP
{
"mcpServers": {
"pharos": {
"type": "http",
"url": "https://mcp.pharos.xyz/mcp"
}
}
}{
"mcpServers": {
"pharos": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.pharos.xyz/mcp"]
}
}
}curl https://mcp.pharos.xyz/health{"status":"ok","chain":"Pharos","chainId":1672,"rpcUrl":"https://rpc.pharos.xyz"}Your private key ──sign (locally)──▶ 0x02f86b…──▶ MCP server ──▶ Pharosgit clone https://github.com/PharosNetwork/pharos-mcp.git
cd pharos-mcp
npm install
npm run build
npm start{
"mcpServers": {
"pharos": {
"command": "node",
"args": ["/absolute/path/to/pharos-mcp/dist/src/index.js"],
"env": {
"PHAROS_RPC_URL": "https://rpc.pharos.xyz",
"PHAROS_CHAIN_ID": "1672",
"PHAROS_NATIVE_SYMBOL": "PROS"
}
}
}
}"env": {
"PHAROS_RPC_URL": "https://your-provider.example/v2/YOUR_API_KEY"
}docker build -t pharos-mcp-server .
docker run -p 3001:3001 \
-e MCP_TRANSPORT=http \
-e MCP_HOST=0.0.0.0 \
pharos-mcp-serverReferences:
Ethereum Yellow Paper: The authoritative technical document that defines the EVM and its operational mechanisms.
EVM Playground: An interactive platform for experimenting with EVM opcodes and bytecode. It provides detailed explanations and visualizations of how EVM executes opcodes.
EVM Cheatsheet: A comprehensive guide detailing EVM architecture, functionalities, and operational mechanisms. It covers:
EVM Architecture: Stack, memory, storage, and instruction set.
Bytecode and Opcodes: Detailed explanations of EVM bytecode and commonly used opcodes.
Memory vs. Storage: Differences and use cases.
Gas and Gas Management: Understanding gas costs and optimization techniques.
Link:
EVM Execution Model: Explore how EVM executes opcodes, manages memory and storage, and handles transactions.
Link:
EVM From Scratch: A project that guides you through implementing your own EVM in various programming languages. It helps in understanding the core mechanics of EVM.
Link: EVM From Scratch
EVM Puzzles: A collection of interactive puzzles designed to help you practice and understand EVM operations.
Link:
EVM Learning Manual: A curated list of resources for learning EVM, maintained by the blockchain community.
Link: EVM Learning Manual
Stack Overflow: A platform where you can find answers to common EVM-related questions and participate in discussions.
Link:
Contract Address Generation: Learn how contract addresses are generated using sender address and nonce.
Bytecode Decompilation: Understand the process of converting bytecode into opcodes and reconstructing high-level code.
Link:
Storage Management: Explore how EVM handles storage variables, including writing to and reading from storage slots.
Link:
EVM Playground: https://evm.codes
EVM Cheatsheet: https://github.com/pylaugh/evm-cheatsheet
EVM From Scratch: https://github.com/w1nt3r-eth/evm-from-scratch
EVM Puzzles: https://github.com/fvictorio/evm-puzzles
EVM Learning Manual:
Stack Overflow - EVM Tag:
Contract Address Generation:
Mythril Documentation:
Solidity Documentation - Storage:
Dapp University: A YouTube channel with over 10 hours of hands-on tutorials for both beginners and experienced Solidity developers.
Link: Dapp University
Udemy Ethereum Blockchain Developer Bootcamp With Solidity: An extensive course providing up-to-date blockchain development tools and resources.
Link: Udemy Course
Ethereum StackExchange: A platform for asking and answering questions related to Solidity and Ethereum development.
Link: Ethereum StackExchange
Solidity Language Users Chat: A chat room for Solidity developers to discuss and share knowledge.
Link:
Remix: A browser-based IDE with an integrated compiler and Solidity runtime environment.
Link: Remix IDE
Hardhat: An Ethereum development environment with local Ethereum network, debugging features, and a plugin ecosystem.
Link:
Foundry: A fast, portable, and modular toolkit for Ethereum application development written in Rust.
Link:
Visual Studio Code (VS Code): Solidity extensions for syntax highlighting, compilation, and debugging.
IntelliJ IDEA: Solidity plugin for IntelliJ IDEA and other JetBrains IDEs.
Link:
Solhint: A Solidity linter that provides security, style guide, and best practice rules for smart contract validation.
Link: Solhint
Ethlint: A linter to identify and fix style and security issues in Solidity.
Link:
Solidity by Example: A collection of example contracts to help you understand Solidity in practice.
Link: Solidity by Example
Security Considerations: A section in the Solidity documentation that provides guidance on security best practices.
Link: Security Considerations
The Solidity documentation is available in multiple languages, thanks to community volunteers.
Chinese (Simplified): Solidity Documentation (Chinese)
Spanish: Solidity Documentation (Spanish)
Russian: Solidity Documentation (Russian)
Solidity Parser for JavaScript: A Solidity parser built on top of ANTLR4 grammar for JavaScript.
Sūrya: A utility tool for smart contract systems, offering visual outputs and information about contract structure.
Link:
0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B
USDT
Tether USD
0
6
0xE7E84B8B4f39C507499c40B4ac199B050e2882d5
WBTC
Wrapped BTC
0
18
0x0c64F03EEa5c30946D5c55B4b532D08ad74638a4
WETH
Wrapped ETH
0
18
0x7d211F77525ea39A0592794f793cC1036eEaccD5
WPHRS
Wrapped PHRS
0
18
0x838800b758277CC111B2d48Ab01e5E164f8E9471
WPROS
Wrapped PROS
0x52c48d4213107b20bc583832b0d951fb9ca8f0b0
0xB197E02499e6502733C6bCE2eb39013C39A03147
USDC
USD Coin
0
6
ZAN Node offers stable and fast RPC connections to Pharos Network, while providing a range of enhanced capabilities to data retrieval API and dynamic monitoring of smart contracts and assets.
A ZAN account is required if you want to create an API key on the ZAN platform for configuring an RPC node service on Pharos. For more information, see .
If you want to configure the RPC node service network, you need to create an API key on the ZAN platform. This key is used for verification during node connection. You . After the API key is created, you can get the URLs for the Pharos Network.
After the API key is created, you can get the URLs that can be used for accessing Pharos Network. Search for Pharos in the Chain RPC section. Click Copy to the right of the HTTPS URL to quickly copy the URL.
0x8B7DdE054BE9D180c1Be7FaE0874697374A49832
USDC (Circle Deployed)
USDC
0xc879c018db60520f4355c26ed1a6d572cdac1815
LINK
Chainlink Token
0x51e2A24742Db77604B881d6781Ee16B5b8fcBE29
0x514910771AF9Ca656af840dff83E8264EcF986CA
WETH
Wrapped ETH
0x1f4b7011Ee3d53969bb67F59428a9ec0477856E9
Latest block height
get_block
eth_getBlockByNumber
Block by tag or number
get_gas_price
eth_gasPrice, eth_maxPriorityFeePerGas
Current gas price and priority fee
Address nonce
get_transaction
eth_getTransactionByHash
Transaction by hash
get_transaction_receipt
eth_getTransactionReceipt
Transaction receipt
Estimate gas without sending
get_code
eth_getCode
Contract bytecode
get_storage_at
eth_getStorageAt
Read a contract storage slot
get_logs
eth_getLogs
Event logs (max 1000 blocks per call)
Expected chain ID
PHAROS_NATIVE_SYMBOL
PROS
Native token symbol
MCP_TRANSPORT
stdio
stdio or http
MCP_HOST
127.0.0.1
Bind address; use 0.0.0.0 in containers
MCP_PORT
3001
HTTP port
MCP_AUTH_TOKEN
—
Bearer token; authentication is off when unset
ENABLE_SEND_RAW_TRANSACTION
false
Enable transaction broadcasting
RATE_LIMIT_PER_MINUTE
120
Per-IP request cap; 0 disables
TRUST_PROXY
false
Set true behind exactly one trusted proxy so rate limiting sees the real client IP
MAX_SESSIONS
500
Maximum concurrent sessions; further sessions get 503
SESSION_TIMEOUT_MINUTES
30
Idle session eviction time
get_logs was called with more than 1000 blocks. Page through the range in smaller windows.
Connection failed
The client may not support remote MCP servers. If it connects but lists no tools, this is the same cause. Bridge with mcp-remote as shown above.
Atlantic Testnet
https://api.zan.top/node/v1/pharos/testnet/{apikey}
wss://api.zan.top/node/ws/v1/pharos/testnet/{apikey}
Pacific Mainnet
https://api.zan.top/node/v1/pharos/mainnet/{api_key}
wss://api.zan.top/node/ws/v1/pharos/mainnet/{api_key}
This guide describes how to configure monitoring for your Pharos node using Prometheus Pushgateway.
Pharos nodes can push metrics to a Prometheus Pushgateway, which allows you to monitor node performance, block production, and other key metrics.
To enable monitoring, add the monitor_config section to your pharos.conf file.
Note: These configuration keys require v0.16.0 or later. In earlier versions they were named
enable_pamir_cetinaandpamir_cetina_*. Using the wrong key names for your node version will silently disable metrics pushing without any error, so make sure the names match your node version.
{
"chain": {
"monitor_config": {
"storage_enable_cetina": true,
"storage_cetina_job_name": "your_job_name",
"storage_cetina_push_address": "your_pushgateway_address",
"storage_cetina_push_interval": 5,
"storage_cetina_push_port": 9091
},
"secret_config": {
"domain_key_path": "../keys/domain.key",
"stabilizing_key_path": "../keys/stabilizing.key"
},
"startup_config": {
"init_config": {
"meta_path": "../data",
"host_ip": "127.0.0.1",
"http_port": "18100",
"rpc_port": "20000",
"tcp_port": "19000",
"ws_port": "18200"
}
}
},
"storage": {
"mygrid_conf": {},
"mygrid_env": {
"storage_data_path": "../data"
}
}
}Version note: In v0.15.0 and earlier, these keys were named
enable_pamir_cetina,pamir_cetina_job_name,pamir_cetina_push_address,pamir_cetina_push_interval, andpamir_cetina_push_portrespectively.
Ensure you have a Prometheus Pushgateway running and accessible from your node
Edit your pharos.conf file and add the monitor_config section under chain:
Replace the placeholder values:
your_job_name: A unique identifier for your node (e.g., my_validator_node)
your_pushgateway_address: Your Pushgateway hostname or IP
For Docker:
For Binary:
This guide is suitable for developers who want to start building dApps using Pharos and Rust WASM toolchains. If you are a new user of Ethereum, please consider researching the before continuing.
This guide will walk you through the process of creating and deploying your first token on the Pharos blockchain using Rust and WASM toolchains. By the end of this guide, you will have a fully functional token contract and understand how to interact with it.
Before you begin, ensure you have the following:
Git: Used for code management and obtain examples.
Python: Used to interact with the Pharos network and execute contract testing scripts.
Restart your node to apply the changes:
storage_enable_cetina
Enable/disable metrics pushing
true
storage_cetina_job_name
Job name for Prometheus metrics
"atlantic_testnet"
vim pharos.confdocker compose restart./ops stop --force
./ops start --config ./pharos.confRust: Install it from .
Pharos Devnet/Testnet Access: Access to a Pharos node (local or remote) for interacting with the blockchain.
Install the pharos wasm toolchain plugin using the Cargo tool:
Add the wasm32-unknown-unknown build target to your Rust compiler:
You should now have it available as a Cargo subcommand:
Note: Pharos built the chain execution client from scratch using C++, and we design, developed and open-sourced the VM, which combines EVM and WASM in a deeply compiled VM arch with native interoperability. The Rust toolchain and Rust SDK are forked from Stylus toolchain and SDK to facilitate easier adoption, the change here is the removal of Arbitrum ink billing and the simplification of the activation and compilation process on Pharos.
Create a new file for your token contract:
Write the Token Contract:
Open src/lib.rs in your favorite text editor and add the following code:
Compile the Smart Contract:
Use cargo to compile the contract:
Open scripts/main.py and replace your deployed contract address to the variable CONTRACT_ADDRESS:
Execute the script using python:
Contract Deployment Fails: Ensure you have enough testnet tokens to cover the deployment cost.
Interaction Issues: Verify that the contract address and ABI are correct.
Insufficient Balance: Ensure your wallet has enough tokens to transfer.
Now that you’ve created and deployed your first token using Rust. This guide provides a comprehensive introduction to creating and deploying a token on the Pharos blockchain using Rust. If you encounter any issues, refer to the Troubleshooting section or consult the Rust documentation. Happy building! 🚀
storage_cetina_push_address
Pushgateway address (without protocol)
"your_pushgateway_address"
storage_cetina_push_interval
Push interval in seconds
5
storage_cetina_push_port
Pushgateway port
9091
cargo install --git https://github.com/PharosNetwork/pharos-cargo-stylusrustup target add wasm32-unknown-unknowncargo stylus --help
Cargo subcommand for developing Pharos Stylus projectstouch src/lib.rs// Only run this as a WASM if the export-abi feature is not set.
#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
extern crate alloc;
// Modules and imports
mod erc20;
use crate::erc20::{Erc20, Erc20Error, Erc20Params};
use alloy_primitives::{Address, U256};
use stylus_sdk::{msg, prelude::*};
/// Immutable definitions
struct StylusTestTokenParams;
impl Erc20Params for StylusTestTokenParams {
const NAME: &'static str = "StylusTestToken";
const SYMBOL: &'static str = "STTK";
const DECIMALS: u8 = 18;
}
// Define the entrypoint as a Solidity storage object. The sol_storage! macro
// will generate Rust-equivalent structs with all fields mapped to Solidity-equivalent
// storage slots and types.
sol_storage! {
#[entrypoint]
struct StylusTestToken {
// Allows erc20 to access StylusTestToken's storage and make calls
#[borrow]
Erc20<StylusTestTokenParams> erc20;
}
}
#[public]
#[inherit(Erc20<StylusTestTokenParams>)]
impl StylusTestToken {
/// Mints tokens
pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> {
self.erc20.mint(msg::sender(), value)?;
Ok(())
}
/// Mints tokens to another address
pub fn mint_to(&mut self, to: Address, value: U256) -> Result<(), Erc20Error> {
self.erc20.mint(to, value)?;
Ok(())
}
/// Burns tokens
pub fn burn(&mut self, value: U256) -> Result<(), Erc20Error> {
self.erc20.burn(msg::sender(), value)?;
Ok(())
}
}cargo stylus check --endpoint=<PHAROS_RPC_URL>cargo stylus deploy --private-key=<YOUR_PRIVATE_KEY> --endpoint=<PHAROS_RPC_URL>from web3 import Web3
# Replace Pharos RPC URL
RPC_URL = "<Pharos RPC URL>"
# Note: Replace this address to your contract address
CONTRACT_ADDRESS = Web3.to_checksum_address("0x8ca06d75562be39f645b3b1ead98b15ddfd8c06f")
PRIVATE_KEY = "Replace your private key"
contract_abi = [
{
"inputs": [],
"name": "name",
"outputs": [{"internalType": "string", "name": "", "type": "string"}],
"stateMutability": "pure",
"type": "function",
},
{
"inputs": [],
"name": "symbol",
"outputs": [{"internalType": "string", "name": "", "type": "string"}],
"stateMutability": "pure",
"type": "function",
},
{
"inputs": [],
"name": "decimals",
"outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}],
"stateMutability": "pure",
"type": "function",
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function",
},
{
"inputs": [{"internalType": "address", "name": "owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function",
},
{
"inputs": [{"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "transfer",
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
"stateMutability": "nonpayable",
"type": "function",
},
{
"inputs": [{"internalType": "address", "name": "from", "type": "address"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "transferFrom",
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
"stateMutability": "nonpayable",
"type": "function",
},
{
"inputs": [{"internalType": "address", "name": "spender", "type": "address"}, {"internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "approve",
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
"stateMutability": "nonpayable",
"type": "function",
},
{
"inputs": [{"internalType": "address", "name": "owner", "type": "address"}, {"internalType": "address", "name": "spender", "type": "address"}],
"name": "allowance",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function",
},
{
"inputs": [{"internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
},
{
"inputs": [{"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "mintTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
},
{
"inputs": [{"internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function",
},
]
w3 = Web3(Web3.HTTPProvider(RPC_URL))
print(f"Connected to Ethereum: {w3.is_connected()}")
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=contract_abi)
your_address = w3.eth.account.from_key(PRIVATE_KEY).address
print(f"Your Address: {your_address}")
print(f"Your Balance: {contract.functions.balanceOf(your_address).call()}")python3 -m pip install web3 && python3 scripts/main.pyA developer guide on how to get started with x402 on Pharos Atlantic Testnet
For Express applications, you can use the middleware pattern:
// Load .env environment variables
import { config } from "dotenv";
config();
// Import required modules
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
// === Environment Configuration ===
const payToAddress = process.env.PAY_TO_ADDRESS as `0x${string}`;
if (!payToAddress) {
console.error("❌ Please set the PAY_TO_ADDRESS environment variable");
console.error('Example: export PAY_TO_ADDRESS=0xYourAddressHere');
process.exit(1);
}
const facilitatorUrl = process.env.FACILITATOR_URL; // Facilitator service URL
const port = parseInt(process.env.PORT || "4021", 10); // Listening port
// USDC Configuration
const usdcAddress = process.env.USDC_ADDRESS; // USDC contract address on Pharos
const usdcName = process.env.USDC_NAME || "USDC";
// === Initialize Client and Resource Server ===
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
const resourceServer = new x402ResourceServer(facilitatorClient);
// === Create EVM Payment Scheme and Register Custom Amount Parser ===
const evmScheme = new ExactEvmScheme();
// Convert USD amount to USDC (6 decimals precision)
evmScheme.registerMoneyParser(async (amount: number, network: string) => {
if (network === "eip155:688689") {
return {
amount: Math.round(amount * 1e6).toString(), // Convert to integer string (e.g., 0.01 → "10000")
asset: usdcAddress, // USDC contract address
extra: {
token: usdcName,
name: usdcName,
version: "2",
},
};
}
return null;
});
// Register Pharos Atlantic Testnet (Chain ID: 688689)
resourceServer.register("eip155:688689", evmScheme);
// === Create Express App ===
const app = express();
// Use payment middleware to protect routes
app.use(
paymentMiddleware(
{
// Premium endpoint: $0.01
"GET /data": {
accepts: {
scheme: "exact",
price: "0.01",
network: "eip155:688689",
payTo: payToAddress,
},
description: "Paid data endpoint",
mimeType: "application/json",
},
// Low-cost endpoint: $0.005
"GET /api/info": {
accepts: {
scheme: "exact",
price: "0.005",
network: "eip155:688689",
payTo: payToAddress,
},
description: "Low-cost info endpoint",
mimeType: "application/json",
},
},
resourceServer
)
);
// === Protected Business Endpoints ===
app.get("/data", (req, res) => {
res.json({ message: "Hello, paid user!", timestamp: Date.now() });
});
app.get("/api/info", (req, res) => {
res.json({
name: "Pharos x402 Server",
network: "Pharos Atlantic Testnet",
chainId: 688689,
timestamp: Date.now(),
});
});
// === Health Check (No Payment Required) ===
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
// === Start Server ===
app.listen(port, () => {
console.log(`✅ Server started, listening on http://localhost:${port}`);
console.log(`📡 Network: Pharos Atlantic Testnet (Chain ID: 688689)`);
console.log(`💰 Payment receiver address: ${payToAddress}`);
console.log(`🪙 USDC contract: ${usdcAddress}`);
});
// Load environment variables
import { config } from "dotenv";
config();
// Import required modules
import { wrapFetchWithPayment, x402Client, decodePaymentResponseHeader } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import fs from "fs";
// === Get EVM private key (prefer environment variable, fall back to .private_key file) ===
const privateKey =
process.env.EVM_PRIVATE_KEY ||
(fs.existsSync(".private_key") ? fs.readFileSync(".private_key", "utf-8").trim() : null);
if (!privateKey) {
console.error("❌ Please set the EVM_PRIVATE_KEY environment variable, or create a .private_key file");
process.exit(1);
}
// === Initialize EVM account and x402 client ===
const signer = privateKeyToAccount(privateKey as `0x${string}`);
const client = new x402Client();
// Register payment scheme for Pharos Atlantic Testnet (Chain ID: 688689)
client.register("eip155:688689", new ExactEvmScheme(signer));
// Wrap fetch to enable automatic payments
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
// === Get target URL (from command-line argument, defaulting to local /data endpoint) ===
const url = process.argv[2] || "http://localhost:4021/data";
console.log(`📡 Request URL: ${url}`);
console.log(`💳 Payer account: ${signer.address}`);
// === Make a request with automatic payment ===
try {
const response = await fetchWithPayment(url);
const data = await response.json();
console.log("✅ Request succeeded!");
console.log("📦 Response data:", JSON.stringify(data, null, 2));
// If the response includes payment info, decode and print transaction details
const paymentResponseHeader = response.headers.get("PAYMENT-RESPONSE");
if (paymentResponseHeader) {
const paymentResponse = decodePaymentResponseHeader(paymentResponseHeader);
console.log("💰 Transaction hash:", paymentResponse.transaction);
console.log("🌐 Network:", paymentResponse.network);
console.log("📤 Payer address:", paymentResponse.payer);
}
} catch (error) {
console.error("❌ Request failed:", error);
process.exit(1);
}
import dotenv from "dotenv";
import express from "express";
import { privateKeyToAccount } from "viem/accounts";
import { createWalletClient, http, publicActions, defineChain } from "viem";
import { x402Facilitator } from "@x402/core/facilitator";
import { toFacilitatorEvmSigner } from "@x402/evm";
import { ExactEvmScheme } from "@x402/evm/exact/facilitator";
dotenv.config();
// Check for EVM private key
if (!process.env.EVM_PRIVATE_KEY) {
console.error("❌ Please set EVM_PRIVATE_KEY");
process.exit(1);
}
// Define Pharos Atlantic Testnet (ID: 688689)
const pharos = defineChain({
id: 688_689,
name: "Pharos",
nativeCurrency: { name: "PHRS", symbol: "PHRS", decimals: 18 },
rpcUrls: { default: { http: ["https://atlantic.dplabs-internal.com/"] } }, // Pharos RPC endpoint
testnet: true,
});
// Create wallet client
const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: pharos,
transport: http(undefined, { timeout: 30_000 }),
}).extend(publicActions);
// Create EVM signer required by the Facilitator
const signer = toFacilitatorEvmSigner({
address: account.address,
getCode: (args) => client.getCode(args),
readContract: (args) => client.readContract({ ...args, args: args.args || [] }),
verifyTypedData: (args) => client.verifyTypedData(args as any),
writeContract: (args) => client.writeContract({ ...args, args: args.args || [] }),
sendTransaction: (args) => client.sendTransaction(args),
waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
});
// Initialize Facilitator and register Pharos chain
const facilitator = new x402Facilitator();
facilitator.register("eip155:688689", new ExactEvmScheme(signer, { deployERC4337WithEIP6492: true }));
// Create Express app
const app = express();
app.use(express.json());
// Verify payment
app.post("/verify", async (req, res) => {
try {
const { paymentPayload, paymentRequirements } = req.body;
const result = await facilitator.verify(paymentPayload, paymentRequirements);
res.json(result);
} catch (e) {
res.status(500).json({ error: (e as Error).message });
}
});
// Settle payment on-chain
app.post("/settle", async (req, res) => {
try {
const { paymentPayload, paymentRequirements } = req.body;
const result = await facilitator.settle(paymentPayload, paymentRequirements);
res.json(result);
} catch (e) {
res.status(500).json({ error: (e as Error).message });
}
});
// Return supported payment schemes
app.get("/supported", (req, res) => {
res.json(facilitator.getSupported());
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`✅ Facilitator is running on port: ${PORT}`);
console.log(`⛓️ Supported network: eip155:688689 (Pharos Atlantic)`);
});
Skill Repository: https://github.com/PharosNetwork/examples/tree/main/skills/x402-pharos
This skill facilitates using the x402 SDK to construct the Pharos x402 server and the corresponding test client. For Atlantic Testnet, configure chain ID 688689 and the test USDC token address (0xE0BE08c77f415F577A1B3A9aD7a1Df1479564ec8), which is not an official address. Ensure the generated server and client use these Atlantic Testnet settings.
The following demonstrates building a server and client demo using this skill, taking opencode as an example.
Create Server: Enter the prompt: "Help me create a pharos-x402 server." It will ask for corresponding parameters (recipient address, Facilitator URL, etc.) and automatically generate the corresponding code files and configuration files.
During the process, you can ask it to generate simple example code through conversation, including /data and /premium endpoints.
The service can then be started.
Next, you can ask it to write a test client.
Once the private key is configured, you can test the payment process.
Please note: never reveal your private key to large models. Configure it via environment variables or file writing.
Ultra-low transaction costs: Pharos's high throughput and efficient consensus minimize transaction costs, making micro-payments truly economically viable.
Instant Finality: Sub-second finality means users can access content immediately after payment without waiting for multiple block confirmations.
EVM Compatibility: Fully compatible with Ethereum tools (ethers.js, viem, Hardhat, etc.); existing developers do not need to learn new tools.
Temporary Access Authorization based on JWT: After the user completes payment verification, the server can issue a short-term valid JSON Web Token (JWT) as a credential for subsequent resource access. This token should have a reasonable expiration time to avoid repeated payments while reducing the overhead of frequent on-chain verification by the server.
Security Management of Sensitive Information: All confidential data such as private keys and API keys should be injected into the application through environment variables or dedicated secret management services. Hardcoding them in source code or configuration files is strictly prohibited to prevent accidental leakage.
Transaction Idempotency and Retry Support: The client should be able to initiate a retry request based on the original transaction hash after a network interruption or request failure. The server must implement idempotency processing logic to ensure that the same on-chain transaction is not billed multiple times or granted permissions repeatedly.
Pharos Official Website: https://www.pharos.xyz/
Pharos Documentation: https://docs.pharos.xyz/
x402 Specification: https://x402.org/
A developer guide on how to get started with x402 on Pharos Pacific Mainnet
For Express applications, you can use the middleware pattern:
// Load .env environment variables
import { config } from "dotenv";
config();
// Import required modules
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
// === Environment Configuration ===
const payToAddress = process.env.PAY_TO_ADDRESS as `0x${string}`;
if (!payToAddress) {
console.error("❌ Please set the PAY_TO_ADDRESS environment variable");
console.error('Example: export PAY_TO_ADDRESS=0xYourAddressHere');
process.exit(1);
}
const facilitatorUrl = process.env.FACILITATOR_URL; // Facilitator service URL
const port = parseInt(process.env.PORT || "4021", 10); // Listening port
// USDC Configuration
const usdcAddress = process.env.USDC_ADDRESS; // USDC contract address on Pharos
const usdcName = process.env.USDC_NAME || "USDC";
// === Initialize Client and Resource Server ===
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
const resourceServer = new x402ResourceServer(facilitatorClient);
// === Create EVM Payment Scheme and Register Custom Amount Parser ===
const evmScheme = new ExactEvmScheme();
// Convert USD amount to USDC (6 decimals precision)
evmScheme.registerMoneyParser(async (amount: number, network: string) => {
if (network === "eip155:1672") {
return {
amount: Math.round(amount * 1e6).toString(), // Convert to integer string (e.g., 0.01 → "10000")
asset: usdcAddress, // USDC contract address
extra: {
token: usdcName,
name: usdcName,
version: "2",
},
};
}
return null;
});
// Register Pharos Pacific Mainnet (Chain ID: 1672)
resourceServer.register("eip155:1672", evmScheme);
// === Create Express App ===
const app = express();
// Use payment middleware to protect routes
app.use(
paymentMiddleware(
{
// Premium endpoint: $0.01
"GET /data": {
accepts: {
scheme: "exact",
price: "0.01",
network: "eip155:1672",
payTo: payToAddress,
},
description: "Paid data endpoint",
mimeType: "application/json",
},
// Low-cost endpoint: $0.005
"GET /api/info": {
accepts: {
scheme: "exact",
price: "0.005",
network: "eip155:1672",
payTo: payToAddress,
},
description: "Low-cost info endpoint",
mimeType: "application/json",
},
},
resourceServer
)
);
// === Protected Business Endpoints ===
app.get("/data", (req, res) => {
res.json({ message: "Hello, paid user!", timestamp: Date.now() });
});
app.get("/api/info", (req, res) => {
res.json({
name: "Pharos x402 Server",
network: "Pharos Pacific Mainnet",
chainId: 1672,
timestamp: Date.now(),
});
});
// === Health Check (No Payment Required) ===
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
// === Start Server ===
app.listen(port, () => {
console.log(`✅ Server started, listening on http://localhost:${port}`);
console.log(`📡 Network: Pharos Pacific Mainnet (Chain ID: 1672)`);
console.log(`💰 Payment receiver address: ${payToAddress}`);
console.log(`🪙 USDC contract: ${usdcAddress}`);
});
// Load environment variables
import { config } from "dotenv";
config();
// Import required modules
import { wrapFetchWithPayment, x402Client, decodePaymentResponseHeader } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import fs from "fs";
// === Get EVM private key (prefer environment variable, fall back to .private_key file) ===
const privateKey =
process.env.EVM_PRIVATE_KEY ||
(fs.existsSync(".private_key") ? fs.readFileSync(".private_key", "utf-8").trim() : null);
if (!privateKey) {
console.error("❌ Please set the EVM_PRIVATE_KEY environment variable, or create a .private_key file");
process.exit(1);
}
// === Initialize EVM account and x402 client ===
const signer = privateKeyToAccount(privateKey as `0x${string}`);
const client = new x402Client();
// Register payment scheme for Pharos Pacific Mainnet (Chain ID: 1672)
client.register("eip155:1672", new ExactEvmScheme(signer));
// Wrap fetch to enable automatic payments
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
// === Get target URL (from command-line argument, defaulting to local /data endpoint) ===
const url = process.argv[2] || "http://localhost:4021/data";
console.log(`📡 Request URL: ${url}`);
console.log(`💳 Payer account: ${signer.address}`);
// === Make a request with automatic payment ===
try {
const response = await fetchWithPayment(url);
const data = await response.json();
console.log("✅ Request succeeded!");
console.log("📦 Response data:", JSON.stringify(data, null, 2));
// If the response includes payment info, decode and print transaction details
const paymentResponseHeader = response.headers.get("PAYMENT-RESPONSE");
if (paymentResponseHeader) {
const paymentResponse = decodePaymentResponseHeader(paymentResponseHeader);
console.log("💰 Transaction hash:", paymentResponse.transaction);
console.log("🌐 Network:", paymentResponse.network);
console.log("📤 Payer address:", paymentResponse.payer);
}
} catch (error) {
console.error("❌ Request failed:", error);
process.exit(1);
}
import dotenv from "dotenv";
import express from "express";
import { privateKeyToAccount } from "viem/accounts";
import { createWalletClient, http, publicActions, defineChain } from "viem";
import { x402Facilitator } from "@x402/core/facilitator";
import { toFacilitatorEvmSigner } from "@x402/evm";
import { ExactEvmScheme } from "@x402/evm/exact/facilitator";
dotenv.config();
// Check for EVM private key
if (!process.env.EVM_PRIVATE_KEY) {
console.error("❌ Please set EVM_PRIVATE_KEY");
process.exit(1);
}
// Define Pharos mainnet (ID: 1672)
const pharos = defineChain({
id: 1672,
name: "Pharos",
nativeCurrency: { name: "PROS", symbol: "PROS", decimals: 18 },
rpcUrls: { default: { http: ["https://rpc.pharos.xyz"] } }, // Pharos RPC endpoint
testnet: false,
});
// Create wallet client
const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: pharos,
transport: http(undefined, { timeout: 30_000 }),
}).extend(publicActions);
// Create EVM signer required by the Facilitator
const signer = toFacilitatorEvmSigner({
address: account.address,
getCode: (args) => client.getCode(args),
readContract: (args) => client.readContract({ ...args, args: args.args || [] }),
verifyTypedData: (args) => client.verifyTypedData(args as any),
writeContract: (args) => client.writeContract({ ...args, args: args.args || [] }),
sendTransaction: (args) => client.sendTransaction(args),
waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
});
// Initialize Facilitator and register Pharos chain
const facilitator = new x402Facilitator();
facilitator.register("eip155:1672", new ExactEvmScheme(signer, { deployERC4337WithEIP6492: true }));
// Create Express app
const app = express();
app.use(express.json());
// Verify payment
app.post("/verify", async (req, res) => {
try {
const { paymentPayload, paymentRequirements } = req.body;
const result = await facilitator.verify(paymentPayload, paymentRequirements);
res.json(result);
} catch (e) {
res.status(500).json({ error: (e as Error).message });
}
});
// Settle payment on-chain
app.post("/settle", async (req, res) => {
try {
const { paymentPayload, paymentRequirements } = req.body;
const result = await facilitator.settle(paymentPayload, paymentRequirements);
res.json(result);
} catch (e) {
res.status(500).json({ error: (e as Error).message });
}
});
// Return supported payment schemes
app.get("/supported", (req, res) => {
res.json(facilitator.getSupported());
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`✅ Facilitator is running on port: ${PORT}`);
console.log(`⛓️ Supported network: eip155:1672 (Pharos Pacific)`);
});
Skill Repository: https://github.com/PharosNetwork/examples/tree/main/skills/x402-pharos
This skill facilitates using the x402 SDK to construct the Pharos x402 server and the corresponding test client. This skill targets the Pharos Pacific Mainnet with chain ID 1672. Configure the USDC token address (0xc879c018db60520f4355c26ed1a6d572cdac1815) with the official token contract address you intend to use for payments.
The following demonstrates building a server and client demo using this skill, taking opencode as an example.
Create Server: Enter the prompt: "Help me create a pharos-x402 server." It will ask for corresponding parameters (recipient address, Facilitator URL, etc.) and automatically generate the corresponding code files and configuration files.
During the process, you can ask it to generate simple example code through conversation, including /data and /premium endpoints.
The service can then be started.
Next, you can ask it to write a test client.
Once the private key is configured, you can test the payment process.
Please note: never reveal your private key to large models. Configure it via environment variables or file writing.
Ultra-low transaction costs: Pharos's high throughput and efficient consensus minimize transaction costs, making micro-payments truly economically viable.
Instant Finality: Sub-second finality means users can access content immediately after payment without waiting for multiple block confirmations.
EVM Compatibility: Fully compatible with Ethereum tools (ethers.js, viem, Hardhat, etc.); existing developers do not need to learn new tools.
Temporary Access Authorization based on JWT: After the user completes payment verification, the server can issue a short-term valid JSON Web Token (JWT) as a credential for subsequent resource access. This token should have a reasonable expiration time to avoid repeated payments while reducing the overhead of frequent on-chain verification by the server.
Security Management of Sensitive Information: All confidential data such as private keys and API keys should be injected into the application through environment variables or dedicated secret management services. Hardcoding them in source code or configuration files is strictly prohibited to prevent accidental leakage.
Transaction Idempotency and Retry Support: The client should be able to initiate a retry request based on the original transaction hash after a network interruption or request failure. The server must implement idempotency processing logic to ensure that the same on-chain transaction is not billed multiple times or granted permissions repeatedly.
Pharos Official Website: https://www.pharos.xyz/
Pharos Documentation: https://docs.pharos.xyz/
x402 Specification: https://x402.org/
This document provides detailed configuration guidance for Pharos nodes, including node types, configuration files, RPC tuning, and node pool scheduling strategies.
Pharos supports the following node types:
Validator Node
Joins the consensus network and participates in block production through consensus.
All node types share the same base configuration. Pre-built configuration files are available:
Fullnode / Validator:
Full Cache Node:
Tracedb Node:
The base configuration for fullnode/validator:
To accelerate state data queries (e.g., getBalance, getCode, getStorageAt), enable the flat state KV database on top of the base configuration:
This adds a secondary key-value index alongside the Merkle state database, providing O(1) lookups for account and storage data across all historical blocks. Recommended for RPC nodes serving high-volume state queries.
To support debug_traceTransaction, debug_traceBlockByHash, debug_traceBlockByNumber, and trace_filter, enable trace persistence on top of the base configuration:
This persists transaction execution traces to a dedicated database, enabling trace replay without re-executing transactions. Recommended for nodes serving debug/trace API requests.
Both features can be enabled simultaneously for maximum query capability:
Note: Enabling both features increases disk usage significantly. Plan for additional storage capacity beyond the base 5 TB requirement.
Since RPC nodes primarily handle state queries, block queries, and simulated transaction execution, they require strong JSON-RPC layer processing capabilities. The following configurations can be used to tune the JSON-RPC layer processing logic.
Recommendations for node pool eth JSON-RPC API scheduling:
When fullnode enables state data pruning and only retains (latest - pruning_window, latest], please note the state data availability range and request range in scheduling strategy.
Affinity-related requests need to be scheduled to the same node:
Transaction submission group: eth_sendRawTransaction and related queries eth_getTransactionCount, eth_getTransactionByHash, eth_estimateGas/eth_call, especially pending state-based queries.
Filter group: eth_newFilter, eth_newBlockFilter
Modular Architecture: Through SPN (Specialized Processing Network), the execution environment can be customized for specific payment scenarios.
Introducing Facilitator Service to Decouple Blockchain Interaction: Design a middleware service (Facilitator) to encapsulate complex logic such as blockchain node communication, transaction listening, and verification, simplifying the main business code. However, note that this component could become a performance bottleneck or a single point of failure; it is recommended to combine redundant deployment, health checks, multi-account strategies, and downgrade policies to improve system resilience.
TPS
30,000
Usually < 5,000
Block Time
< 1 Second

Modular Architecture: Through SPN (Specialized Processing Network), the execution environment can be customized for specific payment scenarios.
Introducing Facilitator Service to Decouple Blockchain Interaction: Design a middleware service (Facilitator) to encapsulate complex logic such as blockchain node communication, transaction listening, and verification, simplifying the main business code. However, note that this component could become a performance bottleneck or a single point of failure; it is recommended to combine redundant deployment, health checks, multi-account strategies, and downgrade policies to improve system resilience.
TPS
30,000
Usually < 5,000
Block Time
< 1 Second

eth_newPendingTransactionFiltereth_getFilterChangeseth_subscribeFullnode
Similar to validator node, but not included in the consensus node set. Serves as a regular RPC node or as a failover backup for validators.
Full Cache Node
A fullnode with an additional flat state database to accelerate state data queries.
Tracedb Node
A fullnode with an additional trace database to accelerate trace-related request responses.
{
"chain": {
"secret_config": {
"domain_key_path": "../keys/domain.key",
"stabilizing_key_path": "../keys/stabilizing.key"
},
"startup_config": {
"init_config": {
"meta_path": "../data",
"host_ip": "127.0.0.1",
"http_port": "18100",
"rpc_port": "20000",
"tcp_port": "19000",
"ws_port": "18200"
}
}
},
"storage": {
"mygrid_conf": {},
"mygrid_env": {
"storage_data_path": "../data"
}
}
}{
"storage": {
"mygrid_conf": {
"config": {
"mygrid": {
"expert_flags": {
"mygrid_client_enable_full_state_kv_db": true
}
}
}
}
}
}{
"chain": {
"startup_config": {
"parameters": {
"/GlobalFlag/enable_persist_trace": "true"
}
}
}
}{
"chain": {
"startup_config": {
"parameters": {
"/GlobalFlag/enable_persist_trace": "true"
}
}
},
"storage": {
"mygrid_conf": {
"config": {
"mygrid": {
"expert_flags": {
"mygrid_client_enable_full_state_kv_db": true
}
}
}
}
}
}cache_query_worker_count
16
Handles getBlock*/getTx*/getReceipt* and other cache queries
High: Insufficient workers cause queuing and increased latency
max_session
102400
Maximum session count
Needs to be large enough for multi-client scenarios
cache_depth
2048
Number of recent blocks cached (header/body/tx/receipt)
getBlock*/getTx*/getReceipt* cache hit avoids disk read, increasing can significantly improve hit rate and reduce latency
Tracedb Node
trace_filter can only be served by tracedb node. debug_traceBlockByHash, debug_traceBlockByNumber, debug_traceTransaction should be preferentially scheduled to tracedb node.
RPC Node
Interfaces other than trace/debug can be scheduled to regular RPC nodes.
Fullnode
cache_query_worker_queue_size
1000
Pending queue for above workers
Queue full at high QPS will reject or block
db_query_worker_count
4
Handles getBalance/getCode/getStorageAt and other state queries
High: Easy to become bottleneck when state queries are frequent
db_query_worker_queue_size
1000
Queue for above
Can increase appropriately
heavy_task_worker_count
4
Handles getLogs, feeHistory
Medium: getLogs is time-consuming, need more workers when concurrent
heavy_task_worker_queue_size
1000
Queue for above
Can increase when heavy tasks are frequent
eth_default_worker_count
4
blockNumber, syncing, chainId, gasPrice, subscribe, etc.
Medium: Can slightly increase when connection count is high
sim_tx_worker_count
16
eth_call, estimateGas, createAccessList
Medium-High: Easy to become bottleneck when dApps have many eth_call requests
raw_tx_worker_count
10
sendRawTransaction
Low: Can reduce if node doesn't primarily receive transactions
max_batch_request_size
20
Single batch JSON-RPC request count
Increasing can reduce connections and round-trips
enable_http_long_connection
true
HTTP long connection
Recommend keeping true to reduce connection overhead
session_idle_timeout_for_http
400000
HTTP session idle timeout (ms)
Reduces invalid connections
session_idle_timeout_for_ws
360000
WebSocket session idle timeout (ms)
Reduces invalid connections
socket_thread_count
16
Accept connections, read socket
Insufficient when connection count is high will limit throughput
Can be scheduled as a regular RPC node.
Validator Node
Do not schedule any JSON-RPC requests, only handle transactions forwarded from other RPC nodes.
Usually 2 - 15 Seconds
Finality Time
Sub-second
Usually several minutes
Micro-payment Viability
Extremely High
Low - High




Usually 2 - 15 Seconds
Finality Time
Sub-second
Usually several minutes
Micro-payment Viability
Extremely High
Low - High




SPV (in the context of this repository) refers to: On a Merkle state tree of a certain data version, for a key provided by the user, the server constructs proof data, allowing the verifier to independently verify "whether the key exists, and if so, what the value is" or "that the key does not exist", given that the verifier only knows the root hash (or equivalent commitment). In this implementation, the state is organized as a nibble Trie, with node hashes cryptographically bound to keys/values. The Letus-side entry point is MerkleIndex::GetSPV, and the key-based downward traversal follows TrieNode::NodeRead (TreeOpType::kGetSPV).
In upper layers such as MyGrid, the state is sharded by MSU (Multi-Shard Unit): it can be understood as first routing keys to one of 256 slots based on the key, with each slot corresponding to a Letus Merkle subtree. The global root hash is obtained by concatenating "the root hashes of each MSU subtree" and then hashing them once more; on the client side, the 256×32-byte subtree roots are concatenated into a "large internal node" as the first hop of the proof chain, see GetMerkleProofGridReq::handleAllPartIOSuccess.
RootHash = Hash( MSU[0] || MSU[1] || ... || MSU[255] ) (each MSU[i] is a 32-byte subtree root hash)
MSU[0] MSU[1] MSU[255]
| | |
+--- Letus +--- Letus +--- ... (nibble Trie)
subtree subtree
(nibble Trie) (nibble Trie)Within a single MSU is Letus's 16-ary nibble Trie (each layer takes one nibble 0~F of the key_hash). Below is a minimal schematic diagram using ASCII art (showing only some slots and two layers):
[ Root_internal ]
/ | \\ ... (up to 16 child slots)
/ | \\
v v v
slot_3 slot_7 slot_A
| | |
[Inter] (Leaf K1) [Inter]
| \\
v v
(Leaf K2) (Leaf K3)Internal nodes: Up to 16 child slots, each slot corresponding to a 32-byte subtree hash (empty slots are often all zeros).
Leaf nodes: Store key_hash and value_hash, corresponding one-to-one with the existence of the queried key.
Additionally, note that to more efficiently handle the case where there are many empty slots in intermediate nodes in the Trie, Letus chooses to skip empty slots when computing hashes. The relevant logic is similar to the following code:
Like other Merkle tree implementations, Letus's SPV is the reverse process of computing the root hash. Specifically, the process is as follows:
Perform ComputeKeyHash on the user key to obtain a fixed-length NibbleHash.
Starting from the MSU root (and the Grid layer above), sequentially take key_hash[d] as the slot index 0~15 for depth d = 0,1,2…, enter the corresponding subtree on the internal node, until reaching a leaf or terminating at some layer where the slot is empty.
These obtained slots (slot_idx) will be used later to verify the SPV return results layer by layer.
Each element in the chain is a TreeProofNode, which may correspond to an intermediate node or a leaf node.
For an intermediate node TreeProofNode: It contains the proof_node with hex-encoded node content, and next_begin_offset and next_end_offset that indicate "where the current subtree root hash falls within the parent node's byte string". A sound verifier MUST independently derive these offsets from the key and treat the prover-supplied values as advisory cross-check inputs only. The derivation differs by layer:
Verifier input: root_hash (committed root), key, returned proofs[] (and optional sibling sub-proofs, see below).
Existence: The last hop is a leaf, and the key_hash in the leaf is consistent with ComputeKeyHash(key), and the value can be verified.
Non-existence has two typical forms (consistent with the "instance" numbering below):
Case 1: The last hop is a leaf, but the key_hash in the leaf is inconsistent with the queried key (the path leads to "someone else's leaf") — typically without sibling sub-proofs.
Case 2: At an internal node, the next nibble corresponding to the query is an empty slot, while other slots at the same layer have data — often accompanied by sibling_leftmost_leaf_proofs (sub-chains to the leftmost leaf of each sibling slot); the information of these sibling slots is also used to assist in non-existence proofs.
Consistent with the implementation: The sha256(key) in the pseudocode below and the hexadecimal strings in the comments (such as "12e2aef") are only for illustrative purposes to facilitate comparison with the diagrams; actual verification should be based on the NibbleHash obtained from ComputeKeyHash and the node encoding rules.
Scenario: key = K, (K, V) exists in the tree, the query path goes from the MSU root along the nibble of hash(K) all the way to a leaf, and that leaf is the storage for K.
Instance:
As shown in the figure above, a total of three diagrams illustrate an actual example of SPV verification.
The leftmost diagram shows the structure of the Trie, and the hash corresponding to the key we expect to verify, i.e., "12e2aef"
The middle diagram shows the response given by SPV, and the correspondence between the response content and each layer of the Trie
The rightmost diagram shows the SPV verification flow, verifying from the leaf node to the state root hash. We focus on explaining the SPV verification flow shown on the rightmost side:
Meaning: The proof chain ends with a leaf node, but the key_hash stored in that leaf is inconsistent with the key_hash obtained by applying the same hash algorithm to the queried key — that is, following the query path leads to the leaf of another existing key (the path prefix overlaps with another, and the last hop lands on "someone else's leaf").
As shown in the figure above, this is an example of a non-existence proof: the hash corresponding to the key we expect to verify is "12e2111".
Obviously, from the leftmost diagram, we can see that according to our expected key_hash, a leaf node of the Merkle tree is still found. However, the key corresponding to this leaf node is not the same as the key we expect, which indicates that the key we expect for SPV does not exist. Next, let's look at how to verify this through SPV.
In summary: "key_hash verification fails" && "the entire proof can be verified using the target key's offset" yields a valid non-existence proof.
Meaning: At a certain internal node, the next nibble of the query path corresponds to an empty slot (the subtree is not created, or an all-zero empty slot proof is provided), and the path ends at an intermediate node rather than a leaf node.
As shown in the figure above, this is an example of a non-existence proof: the hash corresponding to the key we expect to verify is "12221".
In this case, the verification process is more complex. Returning only the main path of the target key (tree_proofs) is not sufficient; we also need sibling_leftmost_leaf_proofs corresponding to each non-empty sibling slot: each sibling item contains slot_index, leftmost_leaf_key (the original key of the leftmost leaf in that slot's subtree), and proof_path (the sequence of nodes from a certain layer's internal node down to that leaf along the leftmost path). The reason is explained in the "Non-Existence Proof Case Discussion - Case 2" section below. During verification, it can be understood in a manner consistent with the implementation: the main chain first verifies to the "empty slot internal"; then for each sibling sub-proof, concatenate "main chain prefix + sibling path" to form a complete chain, and verify to the root using the existence proof method (using the sibling's leftmost leaf key to walk the nibble path).
Summary: Case 2 = Main chain (terminating at empty slot internal) + Several sibling chains (each being an "existence-style" sub-proof of the leftmost leaf of a certain slot); sibling chains share the same root_hash with the main chain, ensuring branch completeness under the parent node's 16-slot semantics.
This section explains: Given that the root_hash root_hash is trustworthy (or has been anchored to a trusted state), why the verification steps above are semantically sufficient to support the conclusion of "existence / non-existence". The text first explains SkipEmpty and the overall security goals, then discusses existence, non-existence (leaf key mismatch), and non-existence (internal empty slot + sibling sub-proofs) in three sections.
Prerequisite — Key-Derived Slot Offsets at Every Layer (including MSU Root). All correctness arguments below presume that the verifier derives the slot offset at each layer of the proof chain from the queried key itself, not from prover-supplied fields. For internal nodes inside an MSU subtree the slot index is the corresponding nibble of ComputeKeyHash(key); for the MSU Root layer the slot index is fixed by the network's MSU routing function applied to the key (the last byte of the key for the Pharos network). If a verifier accepts the prover-supplied next_begin_offset for the MSU Root layer, the Case 2 non-existence argument below collapses: a malicious prover can redirect the chain to a sibling MSU that genuinely does not contain the key, present an honest non-existence path inside that wrong MSU, and the sibling-leftmost-leaf subchains will still close to the unchanged root_hash. The 16-slot pinning argument constrains internal layers within an MSU but does not by itself constrain which MSU subtree was descended into. The key→MSU-slot derivation is therefore load-bearing and must be enforced before any other check.
Letus skips committed empty slots when computing hashes for internal nodes (see the loop illustration earlier), therefore:
The same root_hash may correspond to multiple "logical tree shapes" (which slots are empty, how nodes are laid out), and the proof_node byte layout seen by the verifier from the SPV may not necessarily correspond one-to-one with the "complete 16-ary expanded diagram".
However, this does not weaken the SPV's goal: The verifier only cares about "whether the path of this key is consistent with the root commitment under the current hash rules". Skipping empty slots changes "how to compress and encode child hashes", but does not change the commitment relationship that "non-empty subtree hashes are rolled into the root by the parent node and ancestors all the way".
Under this premise, when the hash function satisfies the expected cryptographic properties (collision resistance, preimage resistance; corresponding to the algorithms used in ComputeKeyHash, node hashes, etc. in the implementation), it can be considered that:
An attacker cannot tamper with a valid existence proof for a certain key and turn it into a valid non-existence proof for the same key;
Nor can they tamper with a valid non-existence proof and turn it into a valid existence proof for the same key.
(If hashes could be arbitrarily collided or preimages could be found, the above conclusions would no longer hold; the arguments below all assume "ideal" hash behavior.)
An existence proof must simultaneously satisfy: rolling hashes from bottom to top are consistent with root_hash, and the key_hash in the leaf is consistent with the hash computed from the queried key using the same rules.
The key points of reliability can be broken down into three layers:
Leaf Layer Binding The leaf node encoding contains key_hash (and value-side commitments). The verifier explicitly checks that the key_hash in the leaf is consistent with ComputeKeyHash(query key), which is equivalent to requiring that "this existence claim is bound to this key", not some other preimage.
Path and Slot Consistency In each layer's internal node, the 32-byte subtree root hash of the child slot must be consistent with the "current subtree root" being verified; and the slot_idx used by the verifier to select slots is derived entirely from the nibble path of the same key_hash. Therefore, an attacker cannot disguise a "subtree hash pointing elsewhere" as "the one that should be seen when going down along the query key", unless they can break the binding between the parent node hash and the child slot content (which boils down to hash collision or forging node encoding).
In summary: The three-layer checks of leaf—path—root together ensure that the "claimed existing key" aligns with the "actual leaf content on the Merkle path" and the "global state commitment".
Case 1: Following the nibble path of the query key leads to a leaf, but the key_hash in the leaf ≠ ComputeKeyHash(query key) (reaching "someone else's leaf" — the path prefix overlaps with another, and the last hop lands on an already existing different key).
Argument:
Path Authenticity If the proof chain can close from bottom to top to root_hash under the slot_idx (and next_begin_offset consistency) at each layer derived from the query key, it indicates: On the slot sequence that the key claims to follow, the Merkle structure is self-consistent, not randomly assembled isolated nodes.
Uniqueness Semantics of Leaf Content The key_hash in the leaf is obtained from the key at storage time using the same hash rules. In the sense of preimage resistance, it is impossible to forge a value for the "query key" that is the same as the key_hash already written in the leaf, unless by chance of collision or it is the original key. What is written in the leaf is the hash of "another key", so the query key is not the key bound by that leaf.
Conclusion Chain verification passes ⇒ There is indeed a leaf hanging at the end of the query path on the tree; the key bound in the leaf is determined and is not the query key ⇒ In that version of the state, one cannot claim that the query key exists using that leaf as evidence. This is the semantics of "non-existence" in Case 1.
Case 2: At a certain internal node in the query path, the next nibble corresponds to an empty slot, the main chain tree_proofs ends at the internal node without passing through a leaf.
When only providing node material for "walking to a certain layer's internal node along the query key, with the next slot being empty", the verifier can only confirm: On the chain provided by the attacker, the sub-slot in the query direction appears to be empty. However, the SkipEmpty and sparse Trie encoding methods make the "appearance of the intermediate" less transparent in the proof than the "entire subtree commitment": A malicious Prover could theoretically attempt to construct a kind of local view — pretending the slot is empty on the query nibble, while concealing or forging the subtrees that actually exist on other slots at the same layer, as long as those slots' real paths and leaf bindings below have not been required to be shown.
In other words: Relying solely on the main chain of "empty slot termination" is not sufficient to prove that "the subtree hashes on other non-empty slots at the same layer are also consistent with the current parent node, ancestors, and root"; without constraints on the "side branches", the "emptiness" could be fabricated.
The figure above illustrates this kind of tampering/concealment: For example, the attacker lets the query path stop at an empty slot in the narrative, but does not synchronously provide sibling branch information consistent with the real state.
For each non-empty sibling slot that needs to be declared as "indeed having data at the same layer", the Prover additionally provides sibling_leftmost_leaf_proofs: for the key of the leftmost leaf in that slot's subtree, and a sub-chain from a certain layer's internal node (sharing a prefix with the main chain) down to that leaf along the "leftmost path" (see the combined in the instance pseudocode earlier).
During verification:
The main chain still verifies according to the query key's slot_idx to the internal node where "empty slot termination" occurs, and closes with root_hash.
Each sibling sub-proof, after concatenating "the main chain minus the last section" with proof_path, rolls to the same root_hash in the manner of an existence proof, recalculating the slot indices at each layer using leftmost_leaf_key.
In this way, under the same parent node (and ancestors), the "empty slot" in the query direction and the "non-empty subtree down to a certain leaf" in the sibling direction are simultaneously constrained by the same root commitment: The subtree hashes of each non-empty slot in the parent internal must be able to support both the empty slot narrative on the main chain and the sibling chains going all the way down to real leaf nodes; both must be consistent with the parent hash computed under the node encoding and SkipEmpty rules.
The verification of sibling chains uses the own nibble path of leftmost_leaf_key, not the path of the query key. Therefore:
One cannot arbitrarily take "any other leaf on the tree" to impersonate the "leftmost leaf" of that slot, unless going down from the leftmost path along leftmost_leaf_key, the next_begin_offset / slot_idx at each layer are consistent with the encoding and close to the root — this is equivalent to performing a complete existence-style verification for that key.
One cannot simply reuse "another key" as leftmost_leaf_key without regard to the path: If that key is not the leftmost leaf in that slot's subtree, or if its slot positions at deeper layers are inconsistent with the structure implied by proof_path, the verification will fail at some layer.
Under the assumptions of preimage resistance and collision resistance, forging a sibling chain that can pass verification means forging node content and leaf bindings compatible with the real root_hash, which is computationally infeasible.
Summary (Correctness Discussion): Existence relies on leaf key binding + path slots + root; Case 1 relies on path authenticity + leaf bound to non-query key; Case 2 uses sibling leftmost leaf existence sub-chains in addition to the empty slot main chain to pin non-empty branches at the same layer to the same root_hash, thereby plugging the gap of "only fabricating a segment of internal". All three are compatible with Letus's SkipEmpty encoding: not pursuing "one-to-one correspondence between proof and unique tree shape", but pursuing "semantic consistency under root commitment, and types cannot be mutually tampered".
MSU Root layer (the first proof node, 256 × 32-byte flat slot array, no header): next_begin_offset = slot_idx * 32, where slot_idx is derived from the queried key via the network's MSU routing function — slot_idx = key[len(key) - 1] for the Pharos mainnet/testnet — not from any prover-supplied data.
Internal node layer (every subsequent non-leaf node): next_begin_offset = 3 + 32 * slot_idx, where slot_idx is the nibble of ComputeKeyHash(key) at the corresponding trie depth. The leading 3 bytes are node metadata.
next_end_offset = next_begin_offset + 32 in both cases.
If the prover-supplied next_begin_offset disagrees with the key-derived value at any layer — especially at the MSU Root — the proof must be rejected. See the Correctness Discussion below for why this is load-bearing for non-existence soundness.
For a leaf node TreeProofNode: It contains the proof_node with hex-encoded node content, representing the concatenation of the key's hash and the value's hash (specific encoding depends on the implementation).
tmp_hash == root_hash pins the entire path to that version of the state. If the root comes from a trusted source, then the meaning of "exists (K, V)" under that version is fixed jointly by the tree and encoding rules.Explanation:
1. The + in the formulas below indicates string concatenation
2. RootHash represents the root hash of the state DB
3. MSU[0] represents the root hash of MSU slot 0
4. Inter_0 represents the hash of the intermediate node corresponding to slot 0 on the Merkle tree
5. Leaf_0 represents the hash of the leaf node corresponding to slot 0 on the Merkle tree
RootHash = Hash( MSU[0] + MSU[1] + ... + MSU[255] )
MSU[0] = Hash( Inter_0 + Inter_1 + ... + Inter_e )
Inter_x = Hash( Leaf_0 + Leaf_1 + ... + Leaf_e )
Leaf_x = Hash( key + value )std::string hash_val = "";
for (int i = 0; i < kInternalSlotCount; i++) {
if (node_impl->IsSlotCommittedEmpty(i)) {
continue;
}
hash_val = hash_update(hash_val, node_impl->GetSlotHash[i]);
}cur = Starting from the last node (deepest) in proofs, compute "the subtree root hash of that node" according to the node type
for i = len(proofs)-2 .. 0:
parent = proofs[i]
# Independently derive the expected slot offset from the queried key — do not
# trust the prover-supplied next_begin_offset for slicing.
if parent is the MSU Root layer (i == 0):
expected_offset = key[len(key) - 1] * 32 # 256-slot flat array
else:
slot_idx = nibble_at_depth(ComputeKeyHash(key), trie_depth_of(parent))
expected_offset = 3 + slot_idx * 32 # internal node layout
Assert parent.next_begin_offset == expected_offset # cross-check
H_slot = parent.proof_node[expected_offset : expected_offset + 32]
Assert H_slot == cur
cur = Hash(parent.proof_node) // Or the parent node hash rule agreed upon by the implementation
Assert cur == root_hash (or consistent with the root after concatenation with the MSU/Grid chain)# Step 1: Get slot_idx at each layer
key_hash = sha256(key) # key_hash = "12e2aef"
slot_idx[0] = 1
slot_idx[1] = 2
...
# Step 2: Verify leaf node
check: proof[-1][1] == key_hash # proof[-1] consists of three elements, proof[-1][1] represents key_hash
tmp_hash = sha256(proof[-1])
# Step 3: Verify the intermediate node one layer above the leaf node
check: proof[-2][slot_idx[-1]] == tmp_hash
# Additionally, the correspondence between next_begin_offset and slot_idx can be checked
tmp_hash = sha256(proof[-2])
# Step 4: Verify the intermediate node one layer above that
check: proof[-3][slot_idx[-2]] == tmp_hash
# Additionally, the correspondence between next_begin_offset and slot_idx can be checked
tmp_hash = sha256(proof[-3])
...
# Final step: Verify the root hash matches the expected state DB root hash
check: tmp_hash == root_hash# Step 1: Get slot_idx at each layer
key_hash = sha256(key) # key_hash = "12e2111"
slot_idx[0] = 1
slot_idx[1] = 2
...
# Step 2: Verify leaf node
check: proof[-1][1] != key_hash # This step indicates that this verification is a non-existence proof!
tmp_hash = sha256(proof[-1])
# Step 3: Verify the intermediate node one layer above the leaf node
check: proof[-2][slot_idx[-1]] == tmp_hash
# Additionally, the correspondence between next_begin_offset and slot_idx can be checked
tmp_hash = sha256(proof[-2])
# Step 4: Verify the intermediate node one layer above that
check: proof[-3][slot_idx[-2]] == tmp_hash
# Additionally, the correspondence between next_begin_offset and slot_idx can be checked
tmp_hash = sha256(proof[-3])
...
# Final step: Verify the root hash matches the expected state DB root hash
check: tmp_hash == root_hash# The main chain is denoted as proof (i.e., tree_proofs); siblings are sibling_leftmost_leaf_proofs.
# The last section is an internal node, not a leaf.
# Step 1: Get slot_idx at each layer for the queried key
key_hash = sha256(key) # key_hash = "12221"
slot_idx[0] = 1
slot_idx[1] = 2
...
# Step 2: Verify the last section internal node: the query path falls on an empty slot at the last layer
check: proof[-1][slot_idx[-1]] == 0
# Step 3: Compute hash upward from the last section internal node
tmp_hash = sha256(proof[-1])
# Step 4: Verify the intermediate node one layer above
check: proof[-2][slot_idx[-2]] == tmp_hash
# Additionally, the correspondence between next_begin_offset and slot_idx can be checked
tmp_hash = sha256(proof[-2])
# Step 5: One layer above that
check: proof[-3][slot_idx[-3]] == tmp_hash
# Additionally, the correspondence between next_begin_offset and slot_idx can be checked
tmp_hash = sha256(proof[-3])
...
# Final step: Verify the root hash matches the expected state DB root hash
check: tmp_hash == root_hash
# For each sibling sub-proof in siblings (where proof_path is non-empty):
# Remove the last section from the main chain, append that item's proof_path,
# then roll to the root using the same steps as "existence proof"
for sib in siblings:
if not sib.proof_path:
continue
combined = proof[0 : len(proof)-1] + sib.proof_path
key_hash = sha256(sib.leftmost_leaf_key)
slot_idx[0] = 1
slot_idx[1] = 2
...
check: combined[-1][1] == key_hash
tmp_hash = sha256(combined[-1])
check: combined[-2][slot_idx[-1]] == tmp_hash
tmp_hash = sha256(combined[-2])
check: combined[-3][slot_idx[-2]] == tmp_hash
tmp_hash = sha256(combined[-3])
...
check: tmp_hash == root_hashSuppose a malicious full node wants to forge a proof of "key K does not exist" to a light client, but K actually exists in the Trie:
1. Layout of the Real Internal Node (depth=d):
slot[2] = H_child_a
slot[7] = H_child_k - Key K's path key_hash[d]=7 leads here (non-empty, indicating K exists)
slot[11] = H_child_c
All other slots are empty (all zeros)
2. Forged Internal Node Constructed by the Attacker:
slot[2] = H_child_a
slot[7] = all zeros // "Vacate" this slot
slot[9] = H_child_k // Move H_child_k to another empty slot
slot[11] = H_child_c
All other slots are empty (all zeros)
3. The Hashes of Both Nodes Are Identical, Because:
Real: SHA256(type || padding || H_child_a || H_child_k || H_child_c)
Forged: SHA256(type || padding || H_child_a || H_child_k || H_child_c)
The concatenation order of non-empty hashes remains unchanged (it's the order encountered when scanning slots 0-15); only the positions have changed.
4. Verifier checks slot[7] and finds all zeros, concluding K does not exist
5. Verifier executes HashInternalNodeSkipEmpty and obtains the same hash as the real node
6. Tracing up the path to the root, the child hash recorded in the parent node matches
7. Result: The light client is deceived - it believes K does not exist, but K actually exists!


This guide walks you through publishing your Skill as a hosted Service Agent on Anvita Flow, so other Agents on the network can discover and call it.
By the end of this guide your Skill will be live as a published Service Agent on Anvita Flow — discoverable and callable by any other Agent on the network.
Overall flow:
Submit Skill
↓
Hosted runtime runs it
↓
Service Agent identity & service profile generated
↓
Published to Marketplace
↓
Discovered and called by other AgentsBefore starting, understand the distinction:
This guide is about publishing a Service Agent.
Once your Service Agent is published, this is the call flow:
You can also call your own Service Agent directly to verify it: in Anvita On, tell your Steward Agent "go find [your service agent name] to do [task] for me".
Before publishing, make sure you have:
A completed Pharos Skill package with SKILL.md at its root
An Anvita Flow account — register at
Access to the Developer Console — if you see a no-permission message, contact the team to be added to the allowlist
One account covers both roles. The same Anvita Flow account works for both ordinary users (Steward Agent) and developers (Service Agent publishing). You don't need a separate account.
A Skill package is a folder packaged as a .zip for upload. It must follow three rules to pass the platform's structure check.
SKILL.mdmust be uppercase. Non-standard names likeskill.mdwill not pass the structure check.
Zip the entire your-skill/ folder, not the files inside it. The top level of the zip must be the folder itself:
SKILL.md must begin with a frontmatter block containing name and description. The name must match the package folder name exactly, including case:
A Service Agent currently hosts a single Skill package. If your repo contains multiple Skills, split them into separate packages and submit each one individually.
1. Register an account
Go to and register. One account covers both user and developer roles.
2. Open the Developer Console
Two ways to get there:
Go directly to
- Or open [https://flow.anvita.xyz/agent/chat](https://flow.anvita.xyz/agent/chat) and click **Developer Portal** in the lower-left profile area
The Add Agent button on the chat page creates a personal Steward Agent — it is unrelated to publishing a Service Agent. Ignore it.
3. Start the creation flow
On the Service Agents page, click Create A Managed Service Agent to begin the three-step flow below.
Fill in the three sections of the creation form:
Upload your zipped package. You can only proceed after it passes the parsing check. If it fails, revisit and confirm the folder structure, zip method, and frontmatter.
Describe how the Service Agent should:
Understand incoming requests
Decide when to ask follow-up questions
Confirm inputs and delivery scope before starting work
Before submitting for publishing, run at least one complete debug session:
From a client Agent's perspective, send a request to your Service Agent
Confirm it returns a correct response
One successful end-to-end run is sufficient to proceed
💡 Use your own Steward Agent to test. In Anvita On, tell it "go find [your service agent name] to do [task] for me" this simulates exactly how other users will call your Agent after publishing.
Complete the public-facing Agent Card. This is what the Marketplace shows to Steward Agents when they are searching for a Service Agent to fulfill a user request.
Once complete, submit for review. After approval it is published.
Success indicator: when the Service Agents list shows Published / Running, your Service Agent has joined Anvita Flow and is live in the Marketplace.
Once published, your Service Agent enters the Anvita Flow Marketplace. When a user makes a request to their Steward Agent in Anvita On (), the Steward Agent discovers and calls a suitable Service Agent from the Marketplace, which performs the task and returns the result.
You can also call your own Service Agent: in the Anvita On chat, just tell your Steward Agent "go find xxx service agent to do … for me" to call it directly and verify the result.
Your Service Agent can earn for you by setting a unit price per call.
How pricing works:
Pricing is per-call and fixed
Set the unit price at publishing time to either Free or X.XX USDC/call
The price is shown publicly in the Marketplace
Go to and enable your wallet. Once enabled, an Agent wallet is generated automatically for every Service Agent you publish.
The earnings feature is still in beta. To avoid call failures, set the public price to Free for now.
Service Agent
Wraps your Skill. Provides a service that other Agents can discover and call.
You, using this guide
Steward Agent (Anvita On)
A user's personal assistant. Receives user requests and finds suitable Service Agents in the Marketplace to fulfill them.
End users via Anvita On
User makes a request in Anvita On (https://flow.anvita.xyz/agent/chat)
↓
Steward Agent searches the Marketplace for a suitable Service Agent
↓
Steward Agent calls your Service Agent
↓
Your Service Agent runs the Skill and returns the result
↓
Steward Agent delivers the result back to the useryour-skill/ ← Package name: replace with your own skill name
├── SKILL.md ← Required — must be uppercase
├── scripts/ ← Optional
├── references/ ← Optional
├── assets/ ← Optional
└── ... ← Optional: any other files your skill needs# ✓ Correct — zip the folder itself
your-skill.zip
└── your-skill/
├── SKILL.md
└── ...# ✗ Wrong — SKILL.md is at the zip root, folder is missing
your-skill.zip
├── SKILL.md
└── ...---
name: your-skill
description: One sentence describing what this Skill does and when it should be called.
---Max concurrent sessions
How many client Agents the Service Agent can serve simultaneously
Max single execution time
The task is automatically interrupted if it exceeds this duration
Agent name
Shown in search results and the Marketplace listing
One-sentence introduction
Helps client Agents quickly judge if this Service Agent fits their request
Capability description
Prepared the Skill package with correct structure and frontmatter
your-skill/SKILL.md
Zipped the folder correctly for upload
your-skill.zip
Created the Service Agent with runtime config and service strategy
Upload fails structure check
Wrong zip method or missing SKILL.md
Zip the folder itself, not the files inside it
SKILL.md not recognized
Filename is lowercase (skill.md)
Which tasks it can complete using public information
Example tasks (min. 2)
One example request per line that a Steward Agent might send
Information required from the customer
Key inputs needed before the task can start
Deliverables
The type and composition of the final result
Range not supported
Data or request types it explicitly refuses
Estimated execution duration
A rough estimate of how long a single task takes
Unit price for charges
See below
Anvita Flow Developer Console
Ran a debug session to confirm end-to-end function
Anvita Flow Debug tab
Completed the Agent Card and submitted for review
Anvita Flow Publish tab
Enabled wallet for earnings (optional)
Rename to uppercase SKILL.md
Frontmatter name mismatch
name field doesn't match folder name
Make them match exactly, including case
No-permission message in console
Account not on the allowlist
Contact the Pharos team to be added
Service Agent not appearing in Marketplace
Still under review or not yet approved
Wait for approval email and check the Service Agents list
Call failures from other Agents
Unit price set to a paid amount during beta
Set price to Free until beta ends
This guide walks through building a time-locked native token vault (SimpleVault/PiggyBank) as a Pharos Skill — from setting up the Skill Engine in VS Code, writing the contract and reference file inside the Skill Engine folder, updating SKILL.md, to testing everything through Claude in your VS Code terminal.
By the end of this guide you will have a published Pharos skill that lets anyone ask an AI to:
Deploy a Simple Vault (piggy bank) contract on Pharos
Deposit PHRS into the vault
Check how much time remains before withdrawal is allowed
Withdraw funds after the time lock expires
Query the full on-chain history of deposits and withdrawals
Along the way, you will learn three core Solidity/EVM concepts:
A Pharos Skill is a folder of files that teaches an AI (Claude) how to interact with the Pharos blockchain. When you ask the AI something like "Deploy a vault on Pharos", it reads your skill files to know exactly which commands to run.
The AI is smart but it does not automatically know your contract. You teach it by writing instruction files in plain Markdown. The AI reads those files and follows them precisely.
A reference file is a Markdown file the AI reads at runtime. It is not code and not meant for humans to execute directly. It tells the AI:
Which cast or forge command to run
What each parameter in the command means
What the terminal output means
Every section in a reference file follows the same 4-part structure:
Plus an > Agent Guidelines block that gives the AI an ordered checklist to follow.
Foundry — compiles and deploys Solidity contracts
Verify installation:
Both should print a version number. If either fails, re-run foundryup.
Claude Code — the AI CLI that runs your skill
Verify:
Create an EVM-compatible wallet (MetaMask or any EVM wallet)
Add the Pharos Atlantic Testnet to your wallet:
Network Name: Pharos Atlantic Testnet
⚠️ Security Warning: Never share your private key. Never commit it to git. Always use the
$PRIVATE_KEYenvironment variable — never paste it directly into commands you share with others.
Before you start, your skill folder should look like this:
After completing this guide it will look like this:
You are adding 2 new files and making 2 small edits to SKILL.md.
Create a new folder and file at this exact path inside the Skill Engine folder:
You should see Compiler run successful. If you see errors, check that the Solidity version in foundry.toml is ^0.8.20 or higher.
Create a new file at:
This is the instruction manual the AI reads when a user asks about the vault. Without this file the AI has no idea your vault contract exists or how to use it.
Paste this entire block into references/vault.md:
You need to make two small edits to SKILL.md.
Find the end of the description field. It currently ends with:
Change it to:
Why: This controls when the AI triggers your skill. Adding these keywords means the AI will load your skill whenever someone asks about vaults or time locks.
Find the last row of the Capability Index table. It currently ends with:
Add these 5 rows directly after it:
Why: This table is how the AI navigates the skill. When a user asks to deposit PHRS, the AI scans this table, finds the matching row, and opens the reference file linked there.
Set your private key in the terminal:
Open Claude Code inside the skill folder:
Run these prompts in order inside Claude Code:
Deploy
Deploy a Simple Vault on Pharos with a 5 minute lock
The AI will ask you to confirm your wallet address and network, check your balance, generate the deploy script, and run it. You will get back a vault address like 0xAbc123....
Deposit
Deposit 0.1 PHRS into vault at 0xYourVaultAddress
The AI runs cast send with --value 0.1ether. You will see a transaction hash.
Check Time Remaining
How much time is left on my vault at 0xYourVaultAddress
The AI runs timeLeft() and tells you something like "4 minutes 32 seconds remaining."
Try Early Withdrawal — Should Fail Gracefully
Withdraw from my vault at 0xYourVaultAddress
The AI checks timeLeft() first, sees the lock has not expired, and tells you to wait. It will not send the transaction.
Withdraw After Lock Expires
Wait 5 minutes, then repeat the withdraw prompt. This time the AI will execute withdraw() and your PHRS comes back to your wallet.
Query Events
Show me the deposit history for vault 0xYourVaultAddress
The AI runs cast logs and shows you the Deposited event with the amount, timestamp, and transaction link.
Circle’s Cross-Chain Transfer Protocol (CCTP) enables secure transfer of USDC across blockchains using a native burn-and-mint mechanism.
On Pharos, CCTP allows developers to move USDC between supported networks without relying on traditional bridges or wrapped tokens.
Unlike lock-and-mint bridges, CCTP burns USDC on the source chain and mints native USDC on the destination chain, ensuring a 1:1 transfer with no liquidity fragmentation.
CCTP smart contract addresses for Pharos. For contract interfaces and method signatures, see .
Full contract source code is .
https://atlantic.dplabs-internal.comChain ID: 688689
Symbol: PHRS
Get testnet PHRS from the Pharos faucet or directly from the team.
Export your private key — you will need it when deploying
deposit()
Marked payable — means it can receive PHRS. Requires msg.value > 0.
withdraw()
Checks caller is owner AND current time is past unlockTime. Sends all PHRS back.
timeLeft()
Read-only. Returns seconds remaining. Returns 0 if unlocked.
getBalance()
Read-only. Returns how much PHRS is currently in the vault.
Deposited event
Logged on-chain every time someone deposits. Queryable with cast logs.
Withdrawn event
Logged on-chain when owner withdraws. Queryable with cast logs.
Must send PHRS
Missing --value flag
Add --value 0.1ether to the deposit command
Nothing to withdraw
Vault is empty
Deposit PHRS first
insufficient funds
Not enough PHRS for gas
Get testnet PHRS from the faucet
connection refused
Missing --rpc-url
Always pass --rpc-url explicitly in every command
compiler error
Solidity compilation failed
Check Foundry is up to date with foundryup
contract not found during verify
Explorer not indexed yet
Wait 10–15 seconds and retry verification
--value flag
Sending native PHRS with a function call
block.timestamp
Setting and enforcing a time lock
Event querying
owner
Stores the deployer's address. Only this address can withdraw.
unlockTime
The Unix timestamp after which withdrawal is allowed. Set once at deploy.
constructor(_lockDurationSeconds)
Still locked
Too early to withdraw
Run timeLeft() and wait
Not owner
Wrong wallet
Reading on-chain history with cast logs
Runs once at deploy. Sets unlockTime = now + duration.
Check $PRIVATE_KEY matches the deployer address
User says "Deploy a Simple Vault with a 1 hour lock"
↓
AI reads SKILL.md → finds "Deploy Simple Vault" row in Capability Index
↓
AI reads references/vault.md → finds the Deploy section
↓
AI follows the Agent Guidelines step by step
↓
AI copies SimpleVault.sol to your project, generates deploy script, runs it
↓
Contract is live on Pharos. AI shows you the address and explorer link.pharos-skill-engine/
│
├── SKILL.md ← Entry point. Tells the AI what this skill does
│ and maps user needs to reference files.
│
├── assets/ ← Files the AI uses directly
│ ├── networks.json ← RPC URLs, chain IDs, explorer URLs for Pharos
│ ├── erc20/ ← Built-in ERC20 contract template
│ ├── airdrop/ ← Built-in airdrop contracts
│ └── vault/ ← Your vault contract lives here
│
└── references/ ← Instruction manuals written for the AI
├── query.md ← Teaches AI how to check balances
├── transaction.md ← Teaches AI how to send transactions
├── contract.md ← Teaches AI how to deploy contracts
└── vault.md ← Your new file — teaches AI about the vault1. Command Template — the actual command with <placeholders>
2. Parameters table — what each <placeholder> means
3. Output Parsing — what fields to look for in the output
4. Error Handling — what can go wrong and how to respondcurl -L https://foundry.paradigm.xyz | bash
source ~/.zshenv && foundryupcast --version
forge --versionnpm install -g @anthropic-ai/claude-codeclaude --versionpharos-skill-engine-0.1.0/
├── SKILL.md
├── assets/
│ ├── networks.json
│ ├── tokens.json
│ ├── erc20/
│ │ └── StandardERC20.sol
│ ├── airdrop/
│ │ └── (airdrop contracts)
│ └── templates/
│ └── (script templates)
└── references/
├── contract.md
├── query.md
├── script-gen.md
└── transaction.mdpharos-skill-engine-0.1.0/
├── SKILL.md ← edited
├── assets/
│ └── vault/
│ └── SimpleVault.sol ← new
└── references/
└── vault.md ← newassets/vault/SimpleVault.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleVault {
address public owner;
uint256 public unlockTime;
event Deposited(address indexed sender, uint256 amount, uint256 unlockTime);
event Withdrawn(address indexed owner, uint256 amount);
constructor(uint256 _lockDurationSeconds) {
owner = msg.sender;
unlockTime = block.timestamp + _lockDurationSeconds;
}
function deposit() external payable {
require(msg.value > 0, "Must send PHRS");
emit Deposited(msg.sender, msg.value, unlockTime);
}
function withdraw() external {
require(msg.sender == owner, "Not owner");
require(block.timestamp >= unlockTime, "Still locked");
uint256 balance = address(this).balance;
require(balance > 0, "Nothing to withdraw");
payable(owner).transfer(balance);
emit Withdrawn(owner, balance);
}
function getBalance() external view returns (uint256) {
return address(this).balance;
}
function timeLeft() external view returns (uint256) {
if (block.timestamp >= unlockTime) return 0;
return unlockTime - block.timestamp;
}
}cd ~/Desktop
forge init vault-test
cd vault-test
cp /path/to/pharos-skill-engine-0.1.0/assets/vault/SimpleVault.sol src/SimpleVault.sol
forge buildreferences/vault.md**# Vault Operation Instructions**
This file contains detailed instructions for deploying and interacting with the
SimpleVault (Piggy Bank) contract on Pharos. It teaches three key concepts:
sending native tokens with `--value`, using `block.timestamp` for time locks,
and querying on-chain events.
> **Network Configuration**: The `<rpc>` parameter in all commands is read from
> the corresponding network's `rpcUrl` field in `assets/networks.json`.
> Defaults to the Atlantic testnet.
>
> **Private Key Configuration**: All write operations must explicitly pass the
> private key via the `--private-key` parameter.
> Recommended: `--private-key $PRIVATE_KEY`.
---
**## Deploy SimpleVault**
**### Overview**
SimpleVault is a piggy bank contract. The deployer sets a time lock duration in
seconds at deployment. PHRS deposited into the vault cannot be withdrawn until
the lock expires.
**### Step 1: Generate Deployment Script**
The Agent generates `script/DeploySimpleVault.s.sol` in the user's project:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "forge-std/console.sol";
import "";
contract DeploySimpleVault is Script {
function run() external {
uint256 lockDurationSeconds = ;
vm.startBroadcast();
SimpleVault vault = new SimpleVault(lockDurationSeconds);
console.log("=== Deploy Result ===");
console.log("Vault address:", address(vault));
console.log("Owner:", vault.owner());
console.log("Unlock time (unix):", vault.unlockTime());
console.log("Lock duration (seconds):", lockDurationSeconds);
console.log("Deployer:", msg.sender);
vm.stopBroadcast();
}
}
```
**### Step 2: Execute Deployment**
**Command Template**
```bash
forge script script/DeploySimpleVault.s.sol:DeploySimpleVault \
--rpc-url \
--private-key $PRIVATE_KEY \
--broadcast
```
**Parameters**
|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `lockDurationSeconds` | uint256 | Yes | Lock duration in seconds. `3600` = 1 hour, `86400` = 1 day |
| `--rpc-url` | string | Yes | RPC endpoint URL, read from `assets/networks.json` |
| `--private-key` | string | Yes | Deployer private key via `$PRIVATE_KEY` |
**Output Parsing**
|** Field **|** Description **|
|---|---|
| `Vault address:` | Deployed contract address — save this for all future interactions |
| `Owner:` | Address that can withdraw funds |
| `Unlock time (unix):` | Unix timestamp when withdrawal becomes allowed |
**Error Handling**
|** Error **|** Cause **|** Fix **|
|---|---|---|
| `compiler error` | Contract compilation failed | Check source file path and Foundry setup |
| `insufficient funds` | Not enough balance for gas | Check balance with `cast balance` |
| `connection refused` | Missing `--rpc-url` or RPC unreachable | Confirm `--rpc-url` is passed |
> **Agent Guidelines:**
> 1. Complete "Write Operation Pre-checks" (see SKILL.md)
> 2. Ask user for `lockDurationSeconds` (suggest `3600` for 1 hour as default)
> 3. Copy `assets/vault/SimpleVault.sol` to the user's project at `src/vault/SimpleVault.sol`
> 4. Check deployer balance: `cast balance <deployer> --rpc-url <rpc> --ether`
> 5. Generate `script/DeploySimpleVault.s.sol` with the lock duration filled in
> 6. Read `rpcUrl` from `assets/networks.json`
> 7. Execute `forge script` with `--rpc-url`, `--private-key $PRIVATE_KEY`, `--broadcast`
> 8. Extract vault address from output, show block explorer link: `<explorerUrl>/address/<vaultAddress>`
> 9. Ask if user wants to verify the contract. If yes, wait ~10 seconds then verify.
---
**## Verify SimpleVault**
**Command Template**
```bash
forge verify-contract src/vault/SimpleVault.sol:SimpleVault \
--chain-id \
--verifier-url /v1/explorer/command_api/contract \
--verifier blockscout \
--constructor-args $(cast abi-encode "constructor(uint256)" )
```
**Parameters**
|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `<vault_address>` | string | Yes | Deployed vault contract address |
| `<chain_id>` | number | Yes | Read from `chainId` in `assets/networks.json` |
| `<explorer_api_url>` | string | Yes | Read from `explorerApiUrl` in `assets/networks.json` |
| `<lockDurationSeconds>` | uint256 | Yes | Same value used during deployment |
**Error Handling**
|** Error **|** Cause **|** Fix **|
|---|---|---|
| `contract not found` | Contract not indexed yet | Wait 10–15 seconds and retry |
| `verification failed` | Source mismatch | Check compiler version matches |
| `constructor arguments mismatch` | Wrong args encoding | Confirm `lockDurationSeconds` value |
---
**## Deposit PHRS into the Vault**
**Command Template**
```bash
cast send "deposit()" \
--value ether \
--private-key $PRIVATE_KEY \
--rpc-url
```
**Parameters**
|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `<vault_address>` | string | Yes | Vault contract address from deployment |
| `<amount>` | number | Yes | Amount of PHRS to deposit. Example: `0.1ether`, `1ether` |
| `--value` | flag | Yes | Sends native PHRS with the call. Without it the call sends 0 PHRS and reverts |
| `--private-key` | string | Yes | Sender private key via `$PRIVATE_KEY` |
| `--rpc-url` | string | Yes | RPC endpoint URL from `assets/networks.json` |
**Output Parsing**
|** Field **|** Description **|
|---|---|
| `status` | `1` = success, `0` = failed |
| `transactionHash` | Use to look up the Deposited event on the explorer |
**Error Handling**
|** Error **|** Cause **|** Fix **|
|---|---|---|
| `execution reverted: Must send PHRS` | `--value` flag missing or 0 | Add `--value <amount>ether` to the command |
| `insufficient funds` | Wallet balance too low | Check with `cast balance <address> --rpc-url <rpc> --ether` |
| `invalid address` | Wrong vault address format | Confirm address is `0x` + 40 hex chars |
> **Agent Guidelines:** Complete "Write Operation Pre-checks" (see SKILL.md).
> Check wallet balance before sending. After successful deposit, show transaction
> link `<explorerUrl>/tx/<txHash>` and remind user of the unlock time.
---
**## Check Vault Balance**
**Command Template**
```bash
cast call "getBalance()(uint256)" --rpc-url
```
**Output Parsing**
- Returns raw balance in wei
- Convert to PHRS: `cast --from-wei <value>`
**Error Handling**
|** Error **|** Cause **|** Fix **|
|---|---|---|
| Empty return value | No contract at that address | Confirm vault address is correct |
| `invalid address` | Wrong address format | Check address is `0x` + 40 hex characters |
---
**## Check Time Remaining Before Withdrawal**
**Command Template**
```bash
cast call "timeLeft()(uint256)" --rpc-url
```
**Output Parsing**
- Returns seconds remaining as a number
- `0` means the vault is unlocked — withdrawal is allowed
- Any positive number means that many seconds remain
**Error Handling**
|** Error **|** Cause **|** Fix **|
|---|---|---|
| Empty return value | No contract at that address | Confirm vault address is correct |
> **Agent Guidelines:** Convert seconds to human-readable format
> (e.g. "59 minutes 2 seconds remaining" or "Vault is unlocked — you can withdraw now").
> Also show `unlockTime` with:
> `cast call <vault_address> "unlockTime()(uint256)" --rpc-url <rpc>`
---
**## Withdraw PHRS from the Vault**
**Command Template**
```bash
cast send "withdraw()" \
--private-key $PRIVATE_KEY \
--rpc-url
```
**Parameters**
|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `<vault_address>` | string | Yes | Vault contract address |
| `--private-key` | string | Yes | Must be the owner's key — only the deployer can withdraw |
| `--rpc-url` | string | Yes | RPC endpoint URL from `assets/networks.json` |
**Output Parsing**
|** Field **|** Description **|
|---|---|
| `status` | `1` = success, `0` = failed |
| `transactionHash` | Use to look up the Withdrawn event on the explorer |
**Error Handling**
|** Error **|** Cause **|** Fix **|
|---|---|---|
| `execution reverted: Still locked` | Time lock not expired yet | Run `timeLeft()` to see remaining time, wait and retry |
| `execution reverted: Not owner` | Wrong private key | Confirm `$PRIVATE_KEY` belongs to the deployer wallet |
| `execution reverted: Nothing to withdraw` | Vault balance is 0 | Deposit PHRS first |
| `insufficient funds` | Not enough gas | Check wallet balance |
> **Agent Guidelines:** Always run `timeLeft()` before attempting withdrawal.
> If result > 0, tell the user how long to wait and do NOT execute `withdraw()`.
> Only proceed when `timeLeft()` returns `0`.
> After success, show `<explorerUrl>/tx/<txHash>`.
---
**## Query Events (Deposit and Withdrawal History)**
**### Query Deposit History**
**Command Template**
```bash
cast logs \
--rpc-url \
--address \
"Deposited(address,uint256,uint256)"
```
**Output Parsing**
|** Field **|** Description **|
|---|---|
| `topics[1]` | Depositor address (indexed) |
| `data[0]` | Amount deposited in wei — divide by 10^18 for PHRS |
| `data[1]` | Unlock time as Unix timestamp |
| `blockNumber` | Block where the deposit happened |
| `transactionHash` | Deposit transaction hash |
**### Query Withdrawal History**
**Command Template**
```bash
cast logs \
--rpc-url \
--address \
"Withdrawn(address,uint256)"
```
**Output Parsing**
|** Field **|** Description **|
|---|---|
| `topics[1]` | Owner address (indexed) |
| `data[0]` | Amount withdrawn in wei — divide by 10^18 for PHRS |
**Error Handling**
|** Error **|** Cause **|** Fix **|
|---|---|---|
| Empty result | No events emitted yet | Inform user no activity has occurred on this vault |
| `invalid address` | Wrong vault address | Confirm address format |
| Connection timeout | RPC unreachable | Check network connection |
> **Agent Guidelines:** Convert wei to PHRS (divide by 10^18). Convert unix
> timestamps to readable dates. Include transaction links
> `<explorerUrl>/tx/<txHash>` for each event. If no logs returned, clearly
> state no activity has occurred yet.or generate Web3 scripts targeting Pharos Chain / Pharos Network. Do not attempt...or generate Web3 scripts targeting Pharos Chain / Pharos Network, or deploy a
vault, piggy bank, time lock, lock PHRS, or deposit and withdraw with a
time-locked contract. Do not attempt...| Generate contract interaction scripts ... | → `references/script-gen.md` || Deploy Simple Vault / Piggy Bank (time-locked PHRS deposit) | `forge script` + built-in Vault template | → `references/vault.md` |
| Deposit PHRS into vault | `cast send` with `--value` | → `references/vault.md#deposit-phrs-into-the-vault` |
| Check vault time lock / time remaining | `cast call timeLeft()` | → `references/vault.md#check-time-remaining-before-withdrawal` |
| Withdraw from vault after time lock expires | `cast send withdraw()` | → `references/vault.md#withdraw-phrs-from-the-vault` |
| Query vault deposit and withdrawal events | `cast logs` | → `references/vault.md#query-events-deposit-and-withdrawal-history` |export PRIVATE_KEY=0xYourPrivateKeyHerecd /path/to/pharos-skill-engine-0.1.0claudeAddress
Pharos
31
Blockchain
Address
Pharos
31
Blockchain
Address
Pharos
31
Blockchain
Address
Pharos
31
Blockchain
Address
Pharos Testnet
31
Blockchain
Address
Pharos Testnet
31
Blockchain
Address
Pharos Testnet
31
Blockchain
Address
Pharos Testnet
31
Cross-Chain Transfer Protocol (CCTP) uses generalized message passing to facilitate the native burning and minting of USDC across supported blockchains, also known as domains. Message passing is a three-step process:
An onchain component on the source domain emits a message.
Circle’s offchain attestation service signs the message.
The onchain component at the destination domain receives the message, and forwards the message body to the specified recipient.
The relationship between CCTP’s onchain components and Circle’s offchain Attestation Service is illustrated below for a burn-and-mint of USDC between EVM-compatible domains:
On Pharos, the onchain component for crosschain burning and minting is called TokenMessengerV2, which is built on top of MessageTransmitterV2, an onchain component for generalized message passing.
In the diagram, a token depositor calls the TokenMessengerV2#depositForBurn function to deposit a native token (such as USDC), which delegates to the TokenMinterV2 contract to burn the token. The TokenMessengerV2 contract then sends a message via the MessageTransmitterV2#sendMessage function. After sufficient block confirmations, Circle’s offchain attestation service, Iris, signs the message. An API consumer must query this attestation and submits it onchain to the destination domain’s MessageTransmitterV2#receiveMessage function.
To send an arbitrary message, directly call MessageTransmitterV2#sendMessage. The message recipient must implement the following methods to handle messages based on their finality threshold:
Implement IMessageHandlerV2#handleReceiveFinalizedMessage to receive messages with finalityThresholdExecuted ≥ 2000.
Implement IMessageHandlerV2#handleReceiveUnfinalizedMessage to receive messages with finalityThresholdExecuted < 2000.
This distinction allows the recipient to control the level of finality it requires before accepting a message.
The top-level message header format is standard for all messages passing through CCTP.
version
0
uint32
4
A CCTP nonce is a unique identifier for a message that can only be used once on the destination domain. Circle assigns CCTP nonces offchain. The nonce for each message in a transaction can be queried through the GET /v2/messages endpoint, using the transaction hash as a query parameter.
The message format includes a dynamically sized messageBody field, used for application-specific messages. For example, TokenMessengerV2 defines a BurnMessageV2 with data related to crosschain transfers.
version
0
uint32
4
CCTP provides a set of API hosts and endpoints to manage messages, attestations, and transaction details for your crosschain USDC transfers.
Testnet
https://iris-api-sandbox.circle.com
Mainnet
https://iris-api.circle.com
CCTP endpoints enable advanced capabilities such as fetching attestations for Standard Transfer or Fast Transfer burn events, verifying public keys across versions, accessing transaction details, querying fast transfer allowances and fees, and initiating re-attestation processes. Below is an overview of the CCTP public endpoints. Click on any endpoint for its API reference.
Returns public keys for validating attestations across all supported CCTP versions.
Retrieve public keys to verify attestation authenticity for crosschain transactions.
Retrieves messages and attestations for a given transaction or nonce, supporting messages for all CCTP versions.
CCTP has the concept of a finality threshold, which is a chain-agnostic representation of the confirmation level required before an attestation is issued. This allows integrators to specify how many confirmations are needed based on their risk tolerance or use case.
In CCTP, each message specifies a minFinalityThreshold. This threshold indicates the minimum level of confirmation required for Circle's attestation service (Iris) to attest to the message. Iris will not attest to a message at a confirmation level below the specified minimum threshold. This allows applications to enforce a desired level of finality before acting on an attestation on the destination chain.
CCTP V2 defines the following finality thresholds:
Confirmed
1000
Finalized
2000
Messages with a minFinalityThreshold of 1000 or lower are considered Fast messages. These messages are eligible for fast attestation at the confirmed level by Iris.
Messages with a minFinalityThreshold of 2000 are considered Standard messages. These messages are attested to at the finalized level by Iris.
For information about CCTP transfer fees, including fee tables by blockchain, the maxFee parameter, and Standard Transfer fee switch support, see CCTP Fees.
Hooks in CCTP V2 are metadata that can be attached to a burn message, allowing integrators to execute custom logic at the destination chain. Hook execution is left entirely to the integrator, offering maximum flexibility and enabling broader crosschain compatibility without altering the core CCTP protocol.
CCTP does not implement hook execution in the core protocol. Instead, hooks are treated as opaque metadata passed along with the burn message. This design allows integrators to define and control how hooks are processed on the destination chain, based on their own infrastructure and trust model.
Maximum flexibility for integrators
Determine execution timing: pre-mint or post-mint
Implement custom recovery or error-handling strategies if hook execution fails
Choose any execution environment (EVM or non-EVM); even non-EVM chains can support Hooks as data passed into a function call.
Improved Compliance and Security Separation
Compliance: By delegating hook execution to the integrator, the protocol maintains a clear boundary between CCTP's core message-passing capabilities and application-specific logic. This modular approach helps integrators meet their own compliance requirements with greater flexibility.
Security: By keeping hook execution outside the core protocol, CCTP maintains a smaller and more focused security surface, while allowing integrators to manage their own execution environments independently.
Blockchain
Version identifier - use 1 for CCTP
sourceDomain
4
uint32
4
Source domain ID
destinationDomain
8
uint32
4
Destination domain ID
nonce
12
bytes32
32
Unique message nonce (see )
sender
44
bytes32
32
Address of MessageTransmitterV2 caller on source domain
recipient
76
bytes32
32
Address to handle message body on destination domain
destinationCaller
108
bytes32
32
Address permitted to call MessageTransmitterV2 on destination domain, or bytes32(0) if message can be received by any address
minFinalityThreshold
140
uint32
4
Minimum finality threshold before allowed to attest (see )
finalityThresholdExecuted
144
uint32
4
Actual finality threshold executed from source chain (see )
messageBody
148
bytes
dynamic
App-specific message to be handled by recipient
Version identifier - use 1 for CCTP
burnToken
4
bytes32
32
Address of burned token on source domain
mintRecipient
36
bytes32
32
Address to receive minted tokens on destination domain
amount
68
uint256
32
Amount of burned tokens
messageSender
100
bytes32
32
Address of caller of depositForBurn (or depositForBurnWithCaller) on source domain
maxFee
132
uint256
32
Maximum fee to pay on the destination domain, specified in units of burnToken
feeExecuted
164
uint256
32
Actual fee charged on the destination domain, specified in units of burnToken (capped by maxFee)
expirationBlock
196
uint256
32
An expiration block 24 hours in the future is encoded in the message before signing by attestation service, and is respected on the destination chain. If the burn expires, it must be re-signed. Expiration acts as a safety mechanism against problems with finalization, such as a stuck sequencer.
hookData
228
bytes
dynamic
Arbitrary data to be included in the depositForBurn on source domain and to be executed on destination domain
Fetch attestation status and transaction details.
Re-attests a soft finality V2 message to achieve finality or revive expired Fast Transfer burns.
Handle edge cases requiring updated attestations or finalize transactions with stricter rules.
Retrieves the current USDC Fast Transfer allowance remaining.
Monitor available allowance for Fast Transfer burns in real-time.
Returns the fees for USDC transfers between specified source and destination domains.
Calculate transaction costs before initiating a Fast or Standard Transfer.

This guide describes how to enable automatic data pruning on your Pharos node to manage disk space.
If you deployed your node using the v0.11 deployment guide (Binary or Docker with the new structure), follow these steps.
Check if the node is running:
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:18100For Binary Deployment:
cd /data/$WORKSPACE/bin
./pharos_cli prune --c ../pharos.conf --enable_auto_pruneFor Docker Deployment:
docker exec -it pharos-node /bin/bash
cd /data/bin
./pharos_cli prune --c ../pharos.conf --enable_auto_pruneFor Binary Deployment:
cd /data/$WORKSPACE/bin
./pharos_cli prune --c ../pharos.conf --getFor Docker Deployment:
docker exec -it pharos-node /bin/bash
cd /data/bin
./pharos_cli prune --c ../pharos.conf --getExpected output:
PruneInfo:
enable_auto_prune: 1
auto_prune_reserved_window: 100000
prune_reserved_version: xxxxxx
flushed_version: xxxxxx
real_prune_reserved_version: xxxxxxxIf enable_auto_prune is 1, auto pruning is enabled.
If you deployed your node using the old Python ops tool, follow these steps.
Enter the container and enable auto-pruning:
Expected output:
If enable_auto_prune is 1, auto pruning is enabled.
curl 127.0.0.1:18100/ \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}'docker exec -it pharos-testnet /bin/bash
cd /data/pharos-node/domain/client/bin/
./pharos_cli prune --enable_auto_prunedocker exec -it pharos-testnet /bin/bash
cd /data/pharos-node/domain/client/bin/
./pharos_cli prune --getPruneInfo:
enable_auto_prune: 1
auto_prune_reserved_window: 100000
prune_reserved_version: xxxxxx
flushed_version: xxxxxx
real_prune_reserved_version: xxxxxxxPharos Network is a fully EVM-equivalent Layer 1 blockchain, supporting a JSON-RPC interface for seamless blockchain interactions. This RPC API is fully compatible with existing Ethereum JSON-RPC API while also providing richer semantics and enhanced services.
Returns the current gas price in Wei.
Input: None
Output: String - Gas price in Wei
Example:
// Request
{
"jsonrpc": "2.0",
"method": "eth_gasPrice",
"params": [],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x2540be400"
}Returns the priority fee needed to be included in a block (EIP-1559).
Input: None
Output: String - Priority fee in Wei
Example:
Returns historical gas information for fee estimation (EIP-1559).
Input:
String - Number of blocks in the requested range
String - Highest block of the requested range
Example:
Returns the number of the most recent block.
Input: None
Output: String - Block number in hexadecimal
Example:
Returns the chain ID of the current network.
Input: None
Output: String - Chain ID as a hexadecimal string
Example:
Returns the sync status of the node.
Input: None
Output: Object|Boolean - Sync status object, or false if not syncing
Example:
Returns the balance of an address at a given block.
Input:
String - Address to query
String - Block number or tag (latest, earliest, pending
Example:
Returns the number of transactions sent from an address (nonce).
Input:
String - Address to query
String - Block number or tag
Example:
Returns the value from a storage position at a given address.
Input:
String - Address of the contract
String - Position in storage
Example:
Returns the contract code at a given address.
Input:
String - Address to query
String - Block number or tag
Example:
Returns the account and storage values with Merkle proof (EIP-1186). Pharos supports using these proofs.
Input:
String - Address
Array - Array of storage keys
Example:
Submits a signed transaction to the network. Supports legacy, EIP-2930 (access list), and EIP-1559 transaction types.
Input:
String - Signed transaction data
Output: String - Transaction hash
Example:
Executes a message call without creating a transaction. Useful for reading contract state.
Input:
Object - Transaction call object (from, to, gas, gasPrice, value, data)
String - Block number or tag
Example:
Estimates the gas needed to execute a transaction.
Input:
Object - Transaction call object
String - Block number or tag (optional)
Example:
Creates an EIP-2930 access list for a transaction.
Note: This method is still under debugging and may return null values.
Input:
Object - Transaction call object
String - Block number or tag
Example:
Returns information about a block by its hash.
Input:
String - Block hash
Boolean - If true, returns full transaction objects; if false, returns transaction hashes
Example:
Returns information about a block by its number.
Input:
String - Block number or tag
Boolean - If true, returns full transaction objects
Example:
Returns all transaction receipts for a given block.
Input:
String - Block number or tag
Output: Array - List of transaction receipts
Example:
Returns the number of transactions in a block by block hash.
Input:
String - Block hash
Output: String - Transaction count as hexadecimal
Example:
Returns the number of transactions in a block by block number.
Input:
String - Block number or tag
Output: String - Transaction count as hexadecimal
Example:
Returns information about a transaction by its hash.
Input:
String - Transaction hash
Output: Object - Transaction details
Example:
Returns a transaction by block hash and index position.
Input:
String - Block hash
String - Transaction index (hexadecimal)
Example:
Returns a transaction by block number and index position.
Input:
String - Block number or tag
String - Transaction index (hexadecimal)
Example:
Returns the receipt of a transaction by transaction hash.
Input:
String - Transaction hash
Output: Object - Transaction receipt
Example:
Returns logs matching a given filter object.
Input:
Object - Filter parameters (fromBlock, toBlock, address, topics)
Output: Array - List of log entries
Note: Block range is limited to 100 blocks when rate limiting is enabled, or 10,000 blocks as a fallback cap.
Example:
Creates a subscription for real-time events (WebSocket only).
Input:
String - Subscription type: newHeads or logs
Object - Filter parameters (for logs
Note: Only available over WebSocket connections. HTTP calls will fail.
Example:
Cancels an existing subscription (WebSocket only).
Input:
String - Subscription ID
Output: Boolean - true if successfully unsubscribed
Example:
Returns the current network ID.
Input: None
Output: String - Network ID
Example:
Returns whether the node is actively listening for network connections.
Input: None
Output: Boolean - Returns true if the node is listening for connections (hardcoded to true in current implementation)
Note: This method is not available on the public RPC endpoint.
Returns the number of connected peers.
Input: None
Output: String - Number of peers as hexadecimal
Note: This method is not available on the public RPC endpoint.
Returns the current client version.
Input: None
Output: String - Client version string
Example:
Returns the Keccak-256 hash of the given data.
Input:
String - Data to hash (hexadecimal)
Output: String - Keccak-256 hash
Note: This method is not available on the public RPC endpoint.
Returns the execution trace of a transaction.
Input:
String - Transaction hash
Object - Tracer options (optional). Currently only "callTracer" is supported.
Example:
Executes a call and returns the execution trace without creating a transaction.
Input:
Object - Transaction call object
String - Block number or tag
Example:
Returns traces for all transactions in a block by block hash.
Input:
String - Block hash
Object - Tracer options (optional). Currently only "callTracer" is supported as the tracer type.
Example:
Returns traces for all transactions in a block by block number.
Input:
String - Block number or tag
Object - Tracer options (optional). Currently only "callTracer" is supported as the tracer type.
Example:
Returns the RLP-encoded block data.
Input:
String - Block number, block hash, or tag
Output: String - RLP-encoded block data
Example:
Returns the RLP-encoded block header.
Input:
String - Block number, block hash, or tag
Output: String - RLP-encoded header data
Example:
Returns the raw receipts for a block.
Input:
String - Block number, block hash, or tag
Output: Array - Raw receipt data
Example:
Returns the raw transaction data by hash.
Input:
String - Transaction hash
Output: String - Raw transaction data
Example:
Returns detailed protocol and version information.
Input: None
Output: Object - Version information including:
binaryVersion - Node binary version
Note: This method is not available on the public RPC endpoint.
Returns traces matching a given filter.
Input:
Object - Filter parameters:
fromBlock - Start block (hexadecimal)
Note: Block range is limited to 500 blocks.
Example:
Returns the next nonce for an address from the transaction pool.
Input:
String - Address
Output: String - Nonce as hexadecimal
Note: This method is not available on the public RPC endpoint.
Returns account information at a given address (EIP-7587).
Input:
String - Address to query
String - Block number or tag
Example:
Returns the block proposer for a given transaction.
Input:
String - Transaction hash
Output: Object - Proposer information
Note: This method is not available on the public RPC endpoint.
Returns the list of proposers for a given block.
Input:
String - Block number or tag
Output: Array - List of proposer addresses
Note: This method is not available on the public RPC endpoint.
These are Pharos-specific debug methods that extend the standard debug namespace.
Returns validator information for the consensus layer.
Input:
String - Block number or tag (optional)
Output: Object - Validator details
Example:
Returns the block proof data for BLS signature verification. See the for usage examples.
Input:
String - Block number or tag
Output: Object - Block proof including BLS signatures
Example:
Returns the read state set for a block.
Input:
String - Block number or tag
Output: Object - Read state data
Note: This method is only available on internal debug endpoints.
Returns the write state set for a block.
Input:
String - Block number or tag
Output: Object - Write state data
Note: This method is only available on internal debug endpoints.
Returns the write set keys for a block.
Input:
String - Block number or tag
Output: Array - Write set keys
Note: This method is only available on internal debug endpoints.
These methods exist for Ethereum compatibility but return placeholder values since Pharos uses PoS consensus.
The following methods are declared but not implemented. Calling them returns METHOD_NOT_FOUND:
eth_batchGetBlockByNumber
eth_batchGetTransactionByHash
eth_batchGetTransactionReceipt
WebSocket: eth_subscribe and eth_unsubscribe require a WebSocket connection. Supported subscription types: newHeads, logs.
Block Tags: Supported values: latest, earliest, pending
Array - Reward percentiles to sample from each blockOutput: Object - Fee history data including baseFeePerGas, gasUsedRatio, and reward
safefinalizedOutput: String - Balance in Wei as hexadecimal
String - Transaction count as hexadecimalString - Block number or tagOutput: String - Storage value as hexadecimal
String - Contract bytecode as hexadecimalString - Block number or tagOutput: Object - Account proof including balance, codeHash, nonce, storageHash, and proofs
String - Return data as hexadecimalString - Estimated gas as hexadecimalObject - Access list and estimated gasOutput: Object - Block information
Output: Object - Block information
Object - Transaction detailsObject - Transaction detailsOutput: String - Subscription ID
Output: Object - Execution trace
Object - Tracer options (optional). Currently only "callTracer" is supported.Output: Object - Execution trace
Output: Array - Array of execution traces
Output: Array - Array of execution traces
currentSpecVersion - Current spec versioncurrentStableBlock - Current stable block number
higherSpecVersions - Available higher spec versions
protocolVersion - Protocol version string
toBlock - End block (hexadecimal)fromAddress - Array of sender addresses (optional)
toAddress - Array of receiver addresses (optional)
count - Maximum number of traces to return (optional)
after - Offset for pagination (optional)
Output: Array - Matching traces
Object - Account details including balance, nonce, codeHash, and storageRooteth_batchGetBlockReceiptssafefinalizedEIP Support: EIP-1559 (fee market), EIP-2930 (access lists), EIP-1186 (eth_getProof) are fully supported.
Rate Limiting: eth_getLogs range is limited to 100 blocks (with rate limiting) or 10,000 blocks (fallback). trace_filter is limited to 500 blocks.
// Request
{
"jsonrpc": "2.0",
"method": "eth_maxPriorityFeePerGas",
"params": [],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x0"
}// Request
{
"jsonrpc": "2.0",
"method": "eth_feeHistory",
"params": [
"0x5",
"latest",
[
25,
75
]
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"baseFeePerGas": [
"0x2540be400",
"0x2540be400",
"..."
],
"gasUsedRatio": [
0.0,
0.0,
"..."
],
"oldestBlock": "0x54ace7",
"reward": [
[
"0x0",
"0x0"
],
[
"0x0",
"0x0"
],
"..."
]
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}
// Response
{
"result": "0x54aceb",
"id": 1,
"jsonrpc": "2.0"
}// Request
{
"jsonrpc": "2.0",
"method": "eth_chainId",
"params": [],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x688"
}// Request
{
"jsonrpc": "2.0",
"method": "eth_syncing",
"params": [],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": false
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": [
"0x407d73d8a49eeb85d32cf465507dd71d507100c1",
"latest"
],
"id": 1
}
// Response
{"jsonrpc":"2.0","id":1,"result":"0x0234c8a3397aab58"}// Request
{
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [
"0xfaC0718D882E5f07e1972D617bd430CD72A55D84",
"latest"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "0x2"
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getStorageAt",
"params": [
"0x4100000000000000000000000000000000000000",
"0x0",
"latest"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "0x0000000000000000..."
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getCode",
"params": [
"0x4100000000000000000000000000000000000000",
"latest"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "0x6080604052600a6012565b6022565b5f90565b6018600e565b50601f60b5565b..."
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getProof",
"params": [
"0x4100000000000000000000000000000000000000",
[
"0x0"
],
"latest"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"accountProof": [
{
"nextBeginOffset": 0,
"nextEndOffset": 32,
"proofNode": "0xe02388eb3621238f..."
},
{
"nextBeginOffset": 387,
"nextEndOffset": 419,
"proofNode": "0x0000005846b1739f..."
},
"..."
],
"address": "0x4100000000000000000000000000000000000000",
"balance": "0x34F086F3B33B684000000",
"codeHash": "0xa556a9adc4732609...",
"isExist": true,
"nonce": "0x0",
"rawValue": "0xef808b034f086f3b...",
"storageHash": "0xd96ef1a22d793063...",
"storageProof": [
{
"isExist": false,
"key": "0x0000000000000000...",
"proof": [
{
"nextBeginOffset": 0,
"nextEndOffset": 32,
"proofNode": "0xe02388eb3621238f..."
},
{
"nextBeginOffset": 323,
"nextEndOffset": 355,
"proofNode": "0x0000005846b1739f..."
},
{
"nextBeginOffset": 163,
"nextEndOffset": 195,
"proofNode": "0x000000186be0bf31..."
},
{
"nextBeginOffset": 259,
"nextEndOffset": 291,
"proofNode": "0x00000036e93a5eef..."
},
{
"nextBeginOffset": 259,
"nextEndOffset": 291,
"proofNode": "0x0000000000000000..."
}
],
"siblingLeftmostLeafProofs": [
{
"leftmostLeafKey": "0x32ec8cb089301315...",
"proofPath": [
{
"nextBeginOffset": 0,
"nextEndOffset": 0,
"proofNode": "0x015aa0b752210382..."
}
],
"slotIndex": 0
},
{
"leftmostLeafKey": "0x319d86c4583accd6...",
"proofPath": [
{
"nextBeginOffset": 0,
"nextEndOffset": 0,
"proofNode": "0x015a63a0b7cfd891..."
}
],
"slotIndex": 3
},
{
"leftmostLeafKey": "0x76c9cf548b4179f8...",
"proofPath": [
{
"nextBeginOffset": 0,
"nextEndOffset": 0,
"proofNode": "0x015aa46483878fad..."
}
],
"slotIndex": 4
},
{
"leftmostLeafKey": "0xf1dcf78b36c5848c...",
"proofPath": [
{
"nextBeginOffset": 227,
"nextEndOffset": 259,
"proofNode": "0x0000000000000000..."
},
{
"nextBeginOffset": 0,
"nextEndOffset": 0,
"proofNode": "0x015a758f88ba6af0..."
}
],
"slotIndex": 5
},
{
"leftmostLeafKey": "0x76c9cf548b4179f8...",
"proofPath": [
{
"nextBeginOffset": 0,
"nextEndOffset": 0,
"proofNode": "0x015a690bf928e548..."
}
],
"slotIndex": 9
},
{
"leftmostLeafKey": "0xc7f7e0e747d90acb...",
"proofPath": [
{
"nextBeginOffset": 0,
"nextEndOffset": 0,
"proofNode": "0x015a8b0d031d5cab..."
}
],
"slotIndex": 11
},
{
"leftmostLeafKey": "0xf428e3a2624c4aac...",
"proofPath": [
{
"nextBeginOffset": 0,
"nextEndOffset": 0,
"proofNode": "0x015a0ff2163c136f..."
}
],
"slotIndex": 15
}
],
"value": "0x"
}
]
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": [
"0xf86c0a8502540be400825208944bbeeb066ed09b7aed07bf39eee0460dfa261520880de0b6b3a7640000801ca0f3ae..."
],
"id": 1
}
// Response
{"jsonrpc":"2.0","id":1,"result":"0xe670ec6434177160..."}// Request
{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x4100000000000000000000000000000000000000",
"data": "0x76671808"
},
"latest"
],
"id": 1
}
// Response
{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000..."}// Request
{
"jsonrpc": "2.0",
"method": "eth_estimateGas",
"params": [
{
"to": "0x4100000000000000000000000000000000000000",
"data": "0x76671808"
}
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "0x9d4b"
}// Request
{
"jsonrpc": "2.0",
"method": "eth_createAccessList",
"params": [
{
"to": "0x4100000000000000000000000000000000000000",
"data": "0x76671808"
},
"latest"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"accessList": [
{
"address": "0x0000000000000000000000000000000000000000",
"storageKeys": []
},
{
"address": "0x0000000000000000000000000000000000000001",
"storageKeys": []
},
"..."
],
"gasUsed": "0x9d4b"
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getBlockByHash",
"params": [
"0xfa15419fc3de49e27f62eff7be1f344a3fd156e332ea9a78337c73a0b2b6573a",
false
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"baseFeePerGas": "0x2540be400",
"difficulty": "0x0",
"extraData": "0x0a0000010000003c...",
"gasLimit": "0x3b9aca00",
"gasUsed": "0x0",
"hash": "0xfa15419fc3de49e2...",
"logsBloom": "0x0000000000000000...",
"miner": "0x0000000000000000000000000000000000000000",
"mixHash": "0x0000000000000000...",
"nonce": "0x0000000000000000",
"number": "0x2710",
"parentHash": "0x1ff0db61e7c714a9...",
"receiptsRoot": "0x56e81f171bcc55a6...",
"sha3Uncles": "0x1dcc4de8dec75d7a...",
"size": "0x257",
"stateRoot": "0xc5914a0db9663398...",
"timestamp": "0x693c2fd1",
"totalDifficulty": "0x0",
"transactions": [],
"transactionsRoot": "0x56e81f171bcc55a6...",
"uncles": []
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getBlockByNumber",
"params": [
"0x2710",
false
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"baseFeePerGas": "0x2540be400",
"difficulty": "0x0",
"extraData": "0x0a0000010000003cc91a139b010000e3b0c44298fc1c149afbf4c8996fb92427...",
"gasLimit": "0x3b9aca00",
"gasUsed": "0x0",
"hash": "0xfa15419fc3de49e2...",
"logsBloom": "0x0000000000000000000000000000000000000000000000000000000000000000...",
"miner": "0x0000000000000000000000000000000000000000",
"mixHash": "0x0000000000000000...",
"nonce": "0x0000000000000000",
"number": "0x2710",
"parentHash": "0x1ff0db61e7c714a9...",
"receiptsRoot": "0x56e81f171bcc55a6...",
"sha3Uncles": "0x1dcc4de8dec75d7a...",
"size": "0x257",
"stateRoot": "0xc5914a0db9663398...",
"timestamp": "0x693c2fd1",
"totalDifficulty": "0x0",
"transactions": [],
"transactionsRoot": "0x56e81f171bcc55a6...",
"uncles": []
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getBlockReceipts",
"params": [
"0x2710"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": []
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getBlockTransactionCountByHash",
"params": [
"0xfa15419fc3de49e27f62eff7be1f344a3fd156e332ea9a78337c73a0b2b6573a"
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x0"
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getBlockTransactionCountByNumber",
"params": [
"0x2710"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "0x0"
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getTransactionByHash",
"params": [
"0xee093bb802e09fe6119e83d7fe35004464a98e69000bcd5672ac73b32172c997"
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"blockHash": "0x1d13424e6a78ab6d...",
"blockNumber": "0x4ff5de",
"chainId": "0x688",
"from": "0x7565f60988b83e577cb1a670ce577667311b11ae",
"gas": "0x1380a5",
"gasPrice": "0x2540be400",
"hash": "0xee093bb802e09fe6...",
"input": "0x6a76120200000000...",
"nonce": "0x66",
"r": "0xd7f06b78d1c679b4...",
"s": "0x74b4c81773e6d4f0...",
"to": "0xcce192445aa8118117e73554773e8be8617f31d0",
"transactionIndex": "0x0",
"type": "0x0",
"v": "0xd34",
"value": "0x0"
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getTransactionByBlockHashAndIndex",
"params": [
"0x1d13424e6a78ab6d9263e6e5f8c9026588d8ba6d2709127a241fbe53a57fd9d9",
"0x0"
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"blockHash": "0x1d13424e6a78ab6d...",
"blockNumber": "0x4ff5de",
"chainId": "0x688",
"from": "0x7565f60988b83e577cb1a670ce577667311b11ae",
"gas": "0x1380a5",
"gasPrice": "0x2540be400",
"hash": "0xee093bb802e09fe6...",
"input": "0x6a76120200000000...",
"nonce": "0x66",
"r": "0xd7f06b78d1c679b4...",
"s": "0x74b4c81773e6d4f0...",
"to": "0xcce192445aa8118117e73554773e8be8617f31d0",
"transactionIndex": "0x0",
"type": "0x0",
"v": "0xd34",
"value": "0x0"
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getTransactionByBlockNumberAndIndex",
"params": [
"0x4ff5de",
"0x0"
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"blockHash": "0x1d13424e6a78ab6d...",
"blockNumber": "0x4ff5de",
"chainId": "0x688",
"from": "0x7565f60988b83e577cb1a670ce577667311b11ae",
"gas": "0x1380a5",
"gasPrice": "0x2540be400",
"hash": "0xee093bb802e09fe6...",
"input": "0x6a76120200000000...",
"nonce": "0x66",
"r": "0xd7f06b78d1c679b4...",
"s": "0x74b4c81773e6d4f0...",
"to": "0xcce192445aa8118117e73554773e8be8617f31d0",
"transactionIndex": "0x0",
"type": "0x0",
"v": "0xd34",
"value": "0x0"
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getTransactionReceipt",
"params": [
"0xee093bb802e09fe6119e83d7fe35004464a98e69000bcd5672ac73b32172c997"
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"blockHash": "0x1d13424e6a78ab6d...",
"blockNumber": "0x4ff5de",
"contractAddress": null,
"cumulativeGasUsed": "0xea376",
"effectiveGasPrice": "0x2540be400",
"from": "0x7565f60988b83e577cb1a670ce577667311b11ae",
"gasUsed": "0xea376",
"logs": [
{
"address": "...",
"blockHash": "...",
"blockNumber": "...",
"blockTimestamp": "...",
"data": "...",
"logIndex": "...",
"removed": "...",
"topics": "...",
"transactionHash": "...",
"transactionIndex": "..."
},
{
"address": "...",
"blockHash": "...",
"blockNumber": "...",
"blockTimestamp": "...",
"data": "...",
"logIndex": "...",
"removed": "...",
"topics": "...",
"transactionHash": "...",
"transactionIndex": "..."
},
{
"address": "...",
"blockHash": "...",
"blockNumber": "...",
"blockTimestamp": "...",
"data": "...",
"logIndex": "...",
"removed": "...",
"topics": "...",
"transactionHash": "...",
"transactionIndex": "..."
}
],
"logsBloom": "0x0000000040000000...",
"status": "0x1",
"to": "0xcce192445aa8118117e73554773e8be8617f31d0",
"transactionHash": "0xee093bb802e09fe6...",
"transactionIndex": "0x0",
"type": "0x0"
}
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getLogs",
"params": [
{
"fromBlock": "0x1",
"toBlock": "0x64",
"address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"topics": [
"0xddf252ad1be2c89b..."
]
}
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"address": "0x742d...f44e",
"topics": [
"0xddf252...b3ef",
"0x000000...sender",
"0x000000...receiver"
],
"data": "0x0000000000000000...",
"blockNumber": "0x50",
"transactionHash": "0xabc123...",
"logIndex": "0x0"
}
]
}// Request (newHeads subscription)
{
"jsonrpc": "2.0",
"method": "eth_subscribe",
"params": ["newHeads"],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x1"
}
// Subsequent notification
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x1",
"result": {
"number": "0x2710",
"hash": "0xfa15419fc3de49e2...",
"parentHash": "0x1ff0db61e7c714a9...",
"timestamp": "0x693c2fd1"
}
}
}// Request (WebSocket only)
{
"jsonrpc": "2.0",
"method": "eth_unsubscribe",
"params": ["0x1"],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": true
}// Request
{
"jsonrpc": "2.0",
"method": "net_version",
"params": [],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "1672"
}// Request
{
"jsonrpc": "2.0",
"method": "web3_clientVersion",
"params": [],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "portal/1.0.0"
}// Request
{
"jsonrpc": "2.0",
"method": "debug_traceTransaction",
"params": [
"0xabc123...",
{
"tracer": "callTracer"
}
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"type": "CALL",
"from": "0x...",
"to": "0x...",
"value": "0x0",
"gas": "0x...",
"gasUsed": "0x...",
"input": "0x...",
"output": "0x..."
}
}// Request
{
"jsonrpc": "2.0",
"method": "debug_traceCall",
"params": [
{
"from": "0xfaC0718D882E5f07e1972D617bd430CD72A55D84",
"to": "0x4100000000000000000000000000000000000000",
"data": "0x76671808"
},
"latest",
{
"tracer": "callTracer"
}
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"failed": false,
"gas": "0x9d4b",
"returnValue": {
"failed": false,
"gas": "0x9d4b",
"returnValue": null
},
"structLogs": {
"depth": 1,
"gas": "0x1c97138",
"gasCost": "0x3",
"memSize": 0,
"op": 96,
"opName": "PUSH1",
"pc": 0,
"refund": 0,
"stack": []
}
}
}// Request
{
"jsonrpc": "2.0",
"method": "debug_traceBlockByHash",
"params": [
"0x1d13424e6a78ab6d9263e6e5f8c9026588d8ba6d2709127a241fbe53a57fd9d9",
{
"tracer": "callTracer"
}
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"result": {
"calls": "...",
"from": "...",
"gas": "...",
"gas_used": "...",
"input": "...",
"output": "...",
"to": "...",
"type": "...",
"value": "..."
},
"txHash": "0xee093bb802e09fe6..."
}
]
}// Request
{
"jsonrpc": "2.0",
"method": "debug_traceBlockByNumber",
"params": [
"0x4ff5de",
{
"tracer": "callTracer"
}
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"result": {
"calls": "...",
"from": "...",
"gas": "...",
"gas_used": "...",
"input": "...",
"output": "...",
"to": "...",
"type": "...",
"value": "..."
},
"txHash": "0xee093bb802e09fe6..."
}
]
}// Request
{
"jsonrpc": "2.0",
"method": "debug_getRawBlock",
"params": [
"0x2710"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "0xf90252f9024da01ff0db61e7c714a91d7e3528c7f659e43b1ddfddb8b20848c8..."
}// Request
{
"jsonrpc": "2.0",
"method": "debug_getRawHeader",
"params": [
"0x2710"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": "0xf9024fa01ff0db61e7c714a91d7e3528c7f659e43b1ddfddb8b20848c87775af..."
}// Request
{
"jsonrpc": "2.0",
"method": "debug_getRawReceipts",
"params": [
"0x2710"
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": []
}// Request
{
"jsonrpc": "2.0",
"method": "debug_getRawTransaction",
"params": [
"0xee093bb802e09fe6119e83d7fe35004464a98e69000bcd5672ac73b32172c997"
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0xf9036d668502540b..."
}// Request
{
"jsonrpc": "2.0",
"method": "trace_filter",
"params": [
{
"fromBlock": "0x4ff5de",
"toBlock": "0x4ff5de"
}
],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"action": {
"callType": "...",
"from": "...",
"gas": "...",
"input": "...",
"to": "...",
"value": "..."
},
"blockHash": "0x1d13424e6a78ab6d...",
"blockNumber": "0x4ff5de",
"result": {
"gas_used": "...",
"output": "..."
},
"subtraces": "0x1",
"traceAddress": [],
"transactionHash": "0xee093bb802e09fe6...",
"transactionPosition": "0x0",
"type": "CALL"
}
]
}// Request
{
"jsonrpc": "2.0",
"method": "eth_getAccount",
"params": [
"0xfaC0718D882E5f07e1972D617bd430CD72A55D84",
"latest"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"balance": "0x425F2B5D87BCB400",
"codeHash": "0xc5d2460186f7233c...",
"nonce": "0x2",
"storageRoot": "0x0000000000000000..."
}
}// Request
{
"jsonrpc": "2.0",
"method": "debug_getValidatorInfo",
"params": [
"0x2710"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"blockNumber": "0x2710",
"validatorSet": [
{
"blsKey": "943fd788d6e2d5d7a7520fa92594cd7349169672a4074ea6ad5be564ebd261186c4da4c70a44283f8afc74c1443882a8",
"identityKey": "04837f833999de7d9ede38c540180e2e7c193cc7a91e40c36355ae348c627687ee8f8274a3462728f46deceaa5f9d76c9224577d87dd975e610749a5d9f367bfab",
"staking": "0xD3C21BCECCEDA1000000",
"validatorID": "aa6ddf1a61d8e4dce4b63737de9c188413c489a4b3e080dface9eca672650d62"
},
{
"blsKey": "aa7793ce126340dde2ccaa9c90320e9639006de133629ebf36c21a71a935789def7890446b7181df3e63ba25c8fdffbc",
"identityKey": "042348d764c8679511b696c15e5f65c3e6e96720a80915def4895c92b7a626e1c9e22aad9e01d4204257feaae538c52ebbd74f0b05897c9d36cf5659f5f91a9b6c",
"staking": "0xD3C21BCECCEDA1000000",
"validatorID": "6bdae2537d2ef6508a36b437a28ef7ce7cdfd8581ae81d11f193579896159e62"
},
"..."
]
}
}// Request
{
"jsonrpc": "2.0",
"method": "debug_getBlockProof",
"params": [
"0x2710"
],
"id": 1
}
// Response
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"blockNumber": "0x2710",
"blockProofHash": "0xfa15419fc3de49e2...",
"blsAggregatedSignature": "ac312817ac4b5a58695d54da42deedfc581063348a9f2e239e5233684fb43b47d5...",
"signedBlsKeys": [
"8281cd60dbf3e67d84a1dcd88f6f95ecf0309ed2c8226eb61d498e3b4453fe309d818f6f02db16b6c610db7b828ef1b5",
"aa7793ce126340dde2ccaa9c90320e9639006de133629ebf36c21a71a935789def7890446b7181df3e63ba25c8fdffbc",
"943fd788d6e2d5d7a7520fa92594cd7349169672a4074ea6ad5be564ebd261186c4da4c70a44283f8afc74c1443882a8"
]
}
}eth_coinbase
"0x0"
No mining reward address in PoS
eth_mining
false
Validators produce blocks, not miners
eth_hashrate
"0x0"
No hashrate in PoS
eth_accounts
[]
Node does not manage accounts
eth_pendingTransactions
[]
Returns empty array
eth_getCompilers
[]
Deprecated
eth_getUncleCountByBlockHash
null
Uncles are a PoW concept
eth_getUncleCountByBlockNumber
null
Uncles are a PoW concept
eth_getUncleByBlockHashAndIndex
null
Uncles are a PoW concept
eth_getUncleByBlockNumberAndIndex
null
Uncles are a PoW concept
Like Ethereum Merkle Proof verification, Pharos provides Simplified Payment Verification (SPV) to allow light clients to verify the existence — or non-existence — of accounts and storage values at a given block height, without possessing the full state.
Pharos implements the storage state trie using a hexary hash tree with the SHA-256 hash algorithm. The trie consists of three node types:
The first-level subtree (MSU Root) contains 256 slots, with each slot representing a shard (partition) of the storage. The actual Merkle proof begins from the second-level subtree. Starting from the second-level subtree, each Internal node value starts with 3 bytes of metadata followed by 16 child node hashes. The hashes of child nodes at the corresponding offset positions in the parent node can be verified recursively, allowing us to determine whether the given root node hash can ultimately be computed, thus justifying the storage data at the specified block height.
For more details of how Pharos implements SPV, you can check
Returns the account information and Merkle proof for a given account address (and optionally specific storage keys) at a specified block height.
Parameters:
curl Example — account proof only:
curl Example — account proof + storage proof:
Unlike Ethereum (which returns RLP-encoded hex strings), each Pharos proof node is a JSON object:
proofNode: The hex-encoded binary data of the trie node (MSU Root, Internal, or Leaf).
nextBeginOffset / nextEndOffset: The byte offset range within this node's binary data that points to the next node in the proof chain. These offsets are advisory only — a sound verifier MUST independently compute the expected offset from the key and reject the proof on mismatch. See .
For each non-leaf node in the proof chain, the verifier must independently derive the offset of the child hash slot from the queried key. Trusting the prover-supplied nextBeginOffset without this independent derivation breaks non-existence soundness — an attacker could redirect the chain to traverse a different subtree that genuinely does not contain the key.
There are two distinct cases:
(1) MSU Root layer (the first node in accountProof / storageProof.proof)
The MSU Root is a flat array of 256 × 32-byte slots. The slot index for a given key is the last byte of the key, and the byte offset is:
Where key is:
For accountProof: the 20-byte account address.
For storageProof[i].proof: address (20 bytes) || storageKey (32 bytes) = 52 bytes total.
The verifier MUST assert proofNode[0].nextBeginOffset == expected_offset before reading the slot, otherwise reject the proof.
(2) Internal node layer (every subsequent non-leaf node)
Internal nodes inside an MSU subtree have a 3-byte header followed by 16 × 32-byte child slots. The slot index is the nibble of SHA-256(key) at the corresponding trie depth:
Where depth is the layer's position in the proof chain (the first internal node has depth = 0).
The verifier should likewise cross-check nextBeginOffset == expected_offset at each internal layer.
Each entry in storageProof has the following structure:
When the queried account (or storage key) exists, the response contains a complete proof chain from the MSU Root node down to the Leaf node. The isExist field is true.
Leaf verification: The last proof node is a Leaf. Verify that SHA256(key) == leaf.key_hash and SHA256(value) == leaf.value_hash.
Hash chain: Compute current_hash = SHA256(leaf_node). Walk upward through the proof chain. At each parent, independently derive the expected slot offset from the key (see ) — do not read offsets blindly from nextBeginOffset. Confirm:
When the queried account (or storage key) does not exist, the proof chain terminates at an Internal node where the target slot is all-zero (empty). The isExist field is false.
In an existence proof, the Leaf node's key hash and value hash provide a self-contained anchor: the verifier can compute hashes upward through the chain and confirm the root. However, for a non-existence proof, the chain terminates at an Internal node with an empty slot — and an Internal node filled with only zeros would itself hash to zero, which could be trivially forged.
To ensure the Internal node's integrity, the response includes sibling leftmost leaf proofs (siblingLeftmostLeafProofs). These provide proof paths from the non-empty sibling slots to their leftmost leaf nodes, enabling the verifier to:
Independently recompute each sibling slot's hash.
Reconstruct the full Internal node (including the empty target slot).
Verify the hash chain upward to the stateRoot.
This is a deliberate trade-off: the proof structure is more complex, but it enables compact storage optimization in the underlying trie engine. The verification semantics are complete — a non-existence proof fully demonstrates that the queried key is absent from the state at the given block height, and is sufficient for all practical use cases such as cross-chain bridge verification.
When isExist is false and the proof terminates at an Internal node with an empty slot, the response includes:
Each entry represents a non-empty sibling slot of the terminating Internal node:
Main chain: Walk the proof chain as usual. Slot offsets at every layer (including the MSU Root) MUST be derived from the key, not read from nextBeginOffset — see . The last node is an Internal node — verify that the target slot (determined by the key hash nibble at the corresponding depth) is all-zero.
Sibling verification: For each entry in siblingLeftmostLeafProofs:
Why MSU Root slot derivation is load-bearing for non-existence. The 16-slot semantics of internal nodes are bound to the key via SHA-256 nibbles, so an attacker cannot redirect those hops. The MSU Root layer is the only layer not bound to the key by the trie structure itself — if a verifier accepts the prover-supplied
nextBeginOffsetthere, an attacker can prove non-existence for any key by pointing the chain at any of the 255 MSU subtrees that genuinely don't contain it (an honest path within the wrong subtree will validate against the unchangedstateRoot). Deriving the MSU Root slot from the key closes this gap.
A Python verification script is provided to verify both existence and non-existence proofs. It handles:
Automatic stateRoot computation from the MSU Root node (SHA-256) if not present in the JSON.
Both existence proofs (leaf-terminated) and non-existence proofs (empty-slot-terminated with sibling verification).
Key-derived MSU Root slot offset (see ), enforced as a soundness invariant.
Earlier versions of this script trusted
nextBeginOffsetfor the MSU Root layer. Integrators using a downstream fork of this script should ensure their copy includes the key-derived slot check; without it, non-existence proofs are forgeable.
The full script source is available at the .
For teams familiar with Ethereum's eth_getProof (), the key differences are:
Why are non-existence proofs different?
In Ethereum's MPT, nodes are RLP-encoded and self-describing — an empty branch is simply an empty string in the RLP list, and the verifier can decode and confirm it directly. In Pharos's hexary trie, Internal nodes are fixed-size binary structures where an empty slot is 32 zero-bytes. To prevent trivial forgery of all-zero Internal nodes, the non-existence proof includes sibling subtree proofs that allow the verifier to independently reconstruct and validate the Internal node's hash. This is a trade-off: proof verification is more complex, but the underlying storage engine benefits from significant optimizations. The verification semantics are complete and fully meet practical use case requirements.
For users who're interested in the implementation details of such proof and how it works, you can refer to
The slot at the key-derived offset in the parent equals current_hash.
The parent's nextBeginOffset matches the key-derived offset (cross-check). Then recompute current_hash = SHA256(parent_node).
Root verification: The hash of the MSU Root node must equal the trusted stateRoot from the block header.
proofPathVerify that this combined chain is a valid existence proof for leftmostLeafKey. Slot offsets within the combined chain are derived from leftmostLeafKey, including the MSU Root slot — which, for honest data, must resolve to the same MSU as the main key.
Root verification: Confirm the MSU Root node hashes to the trusted stateRoot.
stateRootMSU Root
8192
256 × 32-byte SHA-256 hashes. The SHA-256 of this node equals the stateRoot in the block header.
Internal
515
3-byte header + 16 × 32-byte child hashes.
Leaf
65
1-byte type + 32-byte key hash + 32-byte value hash.
1
string
Account address (20-byte hex, 0x-prefixed)
2
string[]
curl -X POST http://<node-rpc-endpoint> \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getProof",
"params": [
"0x4100000000000000000000000000000000000000",
[],
"latest"
]
}'curl -X POST http://<node-rpc-endpoint> \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getProof",
"params": [
"0x4100000000000000000000000000000000000000",
["0x0000000000000000000000000000000000000000000000000000000000000001"],
"latest"
]
}'balance
string
Account balance in hex. "0x0" if account does not exist.
nonce
string
{
"proofNode": "0x<hex-encoded binary node data>",
"nextBeginOffset": <int>,
"nextEndOffset": <int>
}msu_slot_index = key[len(key) - 1] // 0..255
expected_offset = msu_slot_index * 32key_hash = SHA-256(key)
slot_idx = nibble_at_depth(key_hash, depth) // 0..15
expected_offset = 3 + slot_idx * 32{
"key": "0x<storage-key>",
"value": "0x<storage-value>",
"isExist": true,
"proof": [
{ "proofNode": "0x...", "nextBeginOffset": N, "nextEndOffset": M },
...
]
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"balance": "0xD3C21BCECCEDA1000000",
"nonce": "0x0",
"codeHash": "0x415b1764c41b466152a1b0830e26f2e60c391b0dfe1e06eacf28522115f9041d",
"storageHash": "0xccc11c21e5f76a72d68abcc8a72d3c50446342e12fd7b8cfe611955ed11baa24",
"isExist": true,
"rawValue": "0xee808ad3c21bcecceda100000080a0415b...",
"accountProof": [
{ "proofNode": "0x<MSU Root: 8192 bytes>", "nextBeginOffset": 1024, "nextEndOffset": 1056 },
{ "proofNode": "0x<Internal node: 515 bytes>", "nextBeginOffset": 35, "nextEndOffset": 67 },
{ "proofNode": "0x<Internal node: 515 bytes>", "nextBeginOffset": 259, "nextEndOffset": 291 },
{ "proofNode": "0x<Leaf node: 65 bytes>", "nextBeginOffset": 0, "nextEndOffset": 0 }
],
"storageProof": [
{
"key": "0x0000000000000000000000000000000000000000000000000000000000000001",
"value": "0x<storage-value>",
"isExist": true,
"proof": [
{ "proofNode": "0x...", "nextBeginOffset": 35, "nextEndOffset": 67 },
...
]
}
]
}
}{
"siblingLeftmostLeafProofs": [
{
"slotIndex": 0,
"leftmostLeafKey": "0x<key of the leftmost leaf in slot 0's subtree>",
"proofPath": [
{ "proofNode": "0x<Internal node in slot 0's subtree>", "nextBeginOffset": N, "nextEndOffset": M },
{ "proofNode": "0x<Leaf node>", "nextBeginOffset": 0, "nextEndOffset": 0 }
]
},
{
"slotIndex": 2,
"leftmostLeafKey": "0x<key of the leftmost leaf in slot 2's subtree>",
"proofPath": [
{ "proofNode": "0x<Leaf node>", "nextBeginOffset": 0, "nextEndOffset": 0 }
]
}
]
}slotIndex
int
The slot index (0–15) of the sibling in the parent Internal node.
leftmostLeafKey
string
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"balance": "0x0",
"nonce": "0x0",
"codeHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"storageHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"isExist": false,
"rawValue": "0x",
"accountProof": [
{ "proofNode": "0x<MSU Root: 8192 bytes>", "nextBeginOffset": 1024, "nextEndOffset": 1056 },
{ "proofNode": "0x<Internal node: 515 bytes>", "nextBeginOffset": 259, "nextEndOffset": 291 },
{ "proofNode": "0x<Internal: target slot is all-zero>", "nextBeginOffset": 35, "nextEndOffset": 67 }
],
"siblingLeftmostLeafProofs": [
{
"slotIndex": 0,
"leftmostLeafKey": "0x<sibling leaf key>",
"proofPath": [
{ "proofNode": "0x<Internal>", "nextBeginOffset": 35, "nextEndOffset": 67 },
{ "proofNode": "0x<Leaf>", "nextBeginOffset": 0, "nextEndOffset": 0 }
]
},
{
"slotIndex": 7,
"leftmostLeafKey": "0x<sibling leaf key>",
"proofPath": [
{ "proofNode": "0x<Leaf>", "nextBeginOffset": 0, "nextEndOffset": 0 }
]
}
],
"storageProof": []
}
}# Basic verification (no RPC cross-check):
python3 spv_verify.py proof.json --address 0x<account-address> --no-rpc
# With stateRoot cross-check against RPC:
python3 spv_verify.py proof.json --address 0x<account-address> \
--block 0x<block-number> --rpc-url http://127.0.0.1:18100
# If the JSON already contains 'address' and 'stateRoot':
python3 spv_verify.py proof.json --no-rpcHash algorithm
Keccak-256
SHA-256
Trie structure
Merkle Patricia Trie (MPT)
Array of storage keys to prove (each 32-byte hex, 0x-prefixed). Pass [] if only the account proof is needed.
3
string
Block number (hex 0x-prefixed) or block tag ("latest", "earliest")
Account nonce in hex. "0x0" if account does not exist.
codeHash
string
SHA-256 hash of the account's contract code. All-zero if none.
storageHash
string
Root hash of the account's storage trie.
isExist
bool
true if the account exists at the given block, false otherwise.
accountProof
object[]
Array of proof nodes from MSU Root to the target account. See .
storageProof
object[]
Array of storage proof entries (one per requested storage key). See .
rawValue
string
Raw RLP-encoded account value (hex). Empty "0x" if account does not exist.
The key of the leftmost leaf node reachable from this sibling slot.
proofPath
object[]
Proof path from the sibling slot down to the leftmost leaf. Same format as accountProof entries.
Hexary hash tree with 256-slot MSU Root
Node encoding
RLP-encoded hex strings
Fixed-size binary objects
Proof format
accountProof: string[]
accountProof: object[] (with nextBeginOffset/nextEndOffset)
Non-existence proof
Implicit (path terminates naturally)
Explicit siblingLeftmostLeafProofs


LayerZero provides omnichain messaging infrastructure that allows smart contracts to communicate across different blockchain networks.
On Pharos, LayerZero enables developers to build applications that send messages and transfer assets across multiple chains, with configurable security through Decentralized Verifier Networks (DVNs).
LayerZero is available on Pacific Mainnet with the following configuration:
Chain Key
LayerZero is typically used for:
Sending messages across chains
Building omnichain applications
Coordinating contract logic across multiple networks
Use cases requiring flexible security configuration via DVNs
Use Endpoint V2 contracts to send and receive cross-chain messages
Configure DVNs based on your application's security requirements
Suitable for applications that require customizable cross-chain messaging
LayerZero provides metadata APIs to query deployments and configuration details.
Deployments: https://metadata.layerzero-api.com/v1/metadata/deployments
DVN Providers: https://metadata.layerzero-api.com/v1/metadata/dvns
Example (Ethereum DVNs): https://metadata.layerzero-api.com/v1/metadata/dvns?chainNames=ethereum
Configure your contracts to interact with LayerZero endpoints
Select appropriate DVNs for your use case
Test cross-chain messaging flows across supported networks
pharos
Stage
mainnet
Endpoint ID (EID)
30407
endpointV2
0x6F475642a6e85809B1c36Fa62763669b1b48DD5B
sendUln302
0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7
receiveUln302
0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043
blockedMessageLib
0xc1ce56b2099ca68720592583c7984cab4b6d7e7a
executor
0x4208D6E27538189bB48E603D6123A94b8Abe0A0b
Goldsky is a high-performance data indexing provider that makes it easy to extract, transform, and load on-chain data to power both application and analytics use cases.
Goldsky is Web3’s realtime data platform, giving developers access to world-class data infrastructure to power their onchain applications.
Seamlessly access the data you need with lightning-fast indexing, resilient subgraphs, and flexible data streaming pipelines. Spend less time on the complexities of infra maintenance and more time building incredible user experiences.
Scalable Infrastructure: Scalable and resilient infrastructure, handling data challenges as your application grows.
Developer-Friendly Tools: Easily integrate and iterate without being slowed down by complex data engineering.
Enterprise Support: The Goldsky team is available 24/7 to help if things go wrong.
Subgraphs let you efficiently access blockchain data relevant to your application.
Fast Queries: Optimized infrastructure means faster query responses and better performance.
Flexible Integration: Customize your queries and adapt indexing to suit your application's unique needs.
Tagging & Organization: Use tagging to organize your data, making it easier to manage and access.
Use Cases: dApps, NFT marketplaces, gaming, DAOs – any scenario where you need reliable, real-time blockchain data.
Mirror gives you full control over your blockchain data. Instead of just querying, you can stream onchain data directly into your own database.
Realtime Streaming: Stream onchain data directly to your existing systems, enabling fast and interactive user experiences.
Custom Data Handling: Sync data into your own internal databases and combine it with other onchain or offchain data.
Automatic Data Updates: Goldsky ensures that your data stays accurate with continuous synchronization and updates.
Use Cases: Complex data queries, integration into existing databases, real-time analytics, points systems, leaderboards, user progress tracking, loyalty programs, and custom metrics dashboards.
For more details, check out the . Start building smarter, faster, and focus on what really matters: delivering a great experience to your users.
Chainlink provides secure and decentralized oracle services used across the blockchain ecosystem.
On Pharos, Chainlink Data Streams enables developers to access high-frequency off-chain data with low latency, making it suitable for advanced financial and data-intensive applications.
Chainlink Data Streams is available on Pacific Mainnet with the following configuration:
Pacific Mainnet
Chainlink Data Streams is typically used for:
High-frequency price data and market feeds
Low-latency applications such as trading and derivatives
Advanced DeFi protocols requiring real-time data updates
Use cases where data freshness and speed are critical
Designed for applications that require faster and more frequent data updates compared to traditional oracle models
Ensure your contracts are configured to use the correct verifier proxy
Suitable for performance-sensitive and data-intensive use cases
For detailed integration guides and implementation details:
https://docs.chain.link/data-streams
Integrate Data Streams into your smart contracts
Validate data flows and performance for your use case
Combine with other infrastructure such as RPC and indexing for full application workflows
0xa094978891512268f4a4a4641B8da1A2a3E3BEB7
v0.1.0 · Atlantic Testnet & Pacific Mainnet
The Pharos Skill Engine is a packaged developer toolkit that lets an AI agent perform any on-chain operation on the Pharos — check balances, send transactions, deploy and verify Solidity contracts, run batch airdrops, and generate Web3 interaction scripts — entirely through CLI commands.
It is not an SDK, not an API, and not a smart contract framework. It is a self-contained knowledge and tooling package that a developer installs once and then hands to an AI agent (like Claude). The agent reads the package, understands what Pharos operations are available, and executes them by running the correct cast and forge commands.
Natural language in → on-chain action out. A developer says "deploy an ERC20 token called Pharos Gold with symbol PGD" and the agent reads the Skill Engine, finds the one-click ERC20 template, generates the deploy script, runs it, and returns the contract address — all without the developer writing a single line of Solidity or a single CLI command by hand.
Understanding how the Skill Engine works helps both users who want to use it and developers who want to build new skills. The system has three layers.
When a developer asks the AI agent to do something on Pharos, the agent's first action is to read SKILL.md. This file does two things:
Declares the prerequisites the agent must satisfy (Foundry installed, private key configured)
Provides a Capability Index — a table that maps every possible user intent to the correct capability and points to detailed instructions in a reference file
For example: if a developer says "check my wallet balance", the agent reads the Capability Index, finds the row for balance queries, and then reads references/query.md for the exact cast balance command template, parameter spec, and output parsing rules.
The four reference files are detailed machine-readable specs. Each one covers a domain of operations:
Every section in every reference file has the same structure: command template, parameter table, output parsing guide, error handling table, and agent guidelines. This consistency means the agent always knows where to find what it needs.
The assets/ folder contains everything the agent copies into the user's project or reads directly:
Every operation that sends a transaction (transfer, deploy, airdrop, contract write) must pass four checks before the command runs. This is enforced by SKILL.md and cannot be skipped.
⚠️ Foundry does NOT read env vars automatically.
castandforgedo not pick up$PRIVATE_KEYfrom the environment on their own. The agent must always pass it explicitly as--private-key $PRIVATE_KEYin every command. This is a common source of confusion.
Developer makes a request in natural language
Agent checks if Foundry is installed (which cast). If not, install it before proceeding
Agent reads SKILL.md → scans the Capability Index for the matching intent
Foundry is mandatory. The Skill Engine has no fallback — do not try curl/JSON-RPC workarounds.
⚠️ Private Key Safety. Never hardcode your private key in a script file or commit it to git. Always use the
$PRIVATE_KEYenv var pattern. The Skill Engine will never auto-read it — you must pass it explicitly.
Publishing a new skill means extending the Skill Engine, so an AI agent can perform a new on-chain operation — for example, interacting with a vault contract, a governance contract, a staking protocol, etc. The process has three stages.
The contract is the source of truth. Every public function and event you write becomes a command in the reference. Write the contract first — the reference is derived from it.
Good contracts for skills have:
Clear public or external functions with descriptive names
Events emitted on every state change (so the agent can show cast logs output)
View functions for reading state (getBalance, timeLeft
Copy the contract into your project:
The reference file is what the agent reads when a user invokes your skill. It must be precise and complete — the agent runs commands exactly as written.
Every section in the reference follows this template:
Note: When writing your reference file, replace
<exact cast or forge command>with the actual command formatted as abashcode block.
Open SKILL.md and add rows to the Capability Index table for every user intent your skill handles.
Write for how users actually speak. The "User Need" column should capture how a non-technical user would phrase the request — not just the function name. Include synonyms. The agent matches intents, not exact phrases.
Add a row in the Capability Index for every public write function in your contract
Add a row for every public read function (if useful to expose)
Add a row for event querying if your contract emits events
Add a row for contract verification
Agent reads assets/networks.json to get the correct RPC URL and chain ID
For write operations: agent runs all four pre-checks (private key, address, network, balance)
Agent executes the cast or forge command
Agent parses the output per the reference file's output parsing rules
Agent shows result with block explorer link
ownerRevert messages that are human-readable ("Still locked", "Not owner", "Must send PHRS")
Include common natural language phrasings as synonyms in the User Need column
Point each row to the exact anchor in your reference file
SKILL.md
The entry point — read by the AI agent first
Defines what capabilities exist and where to find detailed instructions for each
assets/networks.json
Network configuration
RPC URLs, chain IDs, explorer URLs, native token symbols for both Atlantic testnet and mainnet
pharos-skill-engine-0.1.0/
│
├── SKILL.md ← AI agent entry point
│ ├── Prerequisites
│ ├── Network Configuration
│ ├── Capability Index ← maps intents to reference sections
│ ├── General Error Handling
│ ├── Security Reminders
│ └── Write Operation Pre-checks
│
├── assets/
│ ├── networks.json ← RPC URLs, chain IDs, explorer URLs
│ ├── tokens.json ← known ERC20 addresses + decimals
│ ├── erc20/StandardERC20.sol ← built-in ERC20 template
│ ├── airdrop/ ← batch airdrop infrastructure
│ │ ├── AirdropHelper.sol ← mode selection + CSV parsing
│ │ ├── BatchAirdrop.s.sol ← native token multi-batch script
│ │ ├── BatchAirdropERC20.s.sol ← ERC20 multi-batch script
│ │ ├── NativeDistributor.sol ← native token distributor contract
│ │ └── ERC20Distributor.sol ← ERC20 distributor contract
│ └── templates/ ← script generation templates
│ ├── template_read.js.tpl
│ ├── template_write.js.tpl
│ ├── template_read.ts.tpl
│ ├── template_write.ts.tpl
│ ├── template_read.py.tpl
│ └── template_write.py.tpl
│
└── references/
├── query.md ← balance, tx, contract read specs
├── transaction.md ← send, gas, airdrop specs
├── contract.md ← deploy, verify, ERC20 specs
└── script-gen.md ← JS/TS/Python script generation specsunzip pharos-skill-engine-0.1.0.zip
cd pharos-skill-engine-0.1.0
# Explore the structure
ls
# → SKILL.md assets/ references/# Check if already installed
which cast
# If not found, install Foundry
curl -L https://foundry.paradigm.xyz | bash
source ~/.zshenv && foundryup
# Verify both tools are available
cast --version
forge --version# Export your private key as an environment variable
export PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE
# Confirm the address it derives
cast wallet address --private-key $PRIVATE_KEY
# Set convenience aliases
export RPC=https://atlantic.dplabs-internal.com
export DEPLOYER=$(cast wallet address --private-key $PRIVATE_KEY)# Native PHRS balance
cast balance $DEPLOYER --rpc-url $RPC --ether
# ERC20 token balance (e.g. USDC on testnet)
cast call 0xE0BE08c77f415F577A1B3A9aD7a1Df1479564ec8 "balanceOf(address)(uint256)" \
$DEPLOYER --rpc-url $RPC
# Transaction status
cast tx 0xYOUR_TX_HASH --rpc-url $RPC
cast receipt 0xYOUR_TX_HASH --rpc-url $RPC
# Read any contract method
cast call <contract> "<method()(<returnType>)>" --rpc-url $RPC# Send native PHRS to an address
cast send <recipient> --value 0.1ether --private-key $PRIVATE_KEY --rpc-url $RPC
# Call a contract write method
cast send <contract> "<method(paramType)>" <arg> --private-key $PRIVATE_KEY --rpc-url $RPC
# Call a payable method (sending PHRS with the call)
cast send <contract> "<method()>" --value 0.01ether --private-key $PRIVATE_KEY --rpc-url $RPC# Estimate gas for a native transfer
cast estimate <recipient> --value 0.1ether --rpc-url $RPC
# Estimate gas for a contract call
cast estimate <contract> "<method(paramType)>" <arg> --rpc-url $RPC
# Get current gas price
cast gas-price --rpc-url $RPC# 1. Pre-checks
cast wallet address --private-key $PRIVATE_KEY
cast balance $DEPLOYER --rpc-url $RPC --ether
# 2. Run the deploy script
forge script script/DeployMyContract.s.sol:DeployMyContract \
--rpc-url $RPC \
--private-key $PRIVATE_KEY \
--broadcast
# 3. Wait 10 seconds, then verify
sleep 10
forge verify-contract <contract_address> src/MyContract.sol:MyContract \
--chain-id 688689 \
--verifier-url https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contract \
--verifier blockscout# Convention used by the Skill Engine
cp MySkillContract.sol assets/<skill>/MySkillContract.sol
cp MySkillContract.sol src/<skill>/MySkillContract.sol
# Compile to confirm no errors
forge build
# <Skill Name> Operation Instructions
> Network Configuration: <rpc> is read from assets/networks.json.
> Private Key: Pass explicitly via --private-key $PRIVATE_KEY.
---
## <Operation Name>
### Overview
<One paragraph explaining what this operation does.>
### Command Template
<exact cast or forge command>
### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| <param> | <type> | Yes | <description> |
### Output Parsing
| Field | Description |
|-------|-------------|
| <field> | <how to interpret it> |
### Error Handling
| Error Signature | Cause | Suggested Action |
|----------------|-------|----|
| <error text> | <why> | <fix> |
> **Agent Guidelines**:
> 1. Complete Write Operation Pre-checks (see SKILL.md)
> 2. <next step>
> 3. <next step>Deploy contract
Always
Include script generation step
Verify contract
Always
| User Need | Capability | Detailed Instructions |
|-----------|------------|----------------------|
| Deploy MySkill / [synonyms] | forge script + built-in MySkill template | → references/<skill>.md |
| Call myAction on MySkill | cast send myAction() | → references/<skill>.md#section |
| Check MySkill state | cast call getState() | → references/<skill>.md#section |
| Query MySkill events | cast logs | → references/<skill>.md#section |
Contract compiles
forge build succeeds with no errors or warnings
Contract deployed on testnet
Confirmed transaction hash and contract address on atlantic.pharosscan.xyz
Contract verified
invalid address
Address format is wrong
Confirm address is 0x + 40 hex characters (42 chars total)
transaction not found
TX hash doesn't exist or node hasn't synced
# ── SETUP ──────────────────────────────────────────────────────────
curl -L https://foundry.paradigm.xyz | bash && source ~/.zshenv && foundryup
export PRIVATE_KEY=0x...
export RPC=https://atlantic.dplabs-internal.com
export DEPLOYER=$(cast wallet address --private-key $PRIVATE_KEY)
# ── QUERY (free — no gas) ──────────────────────────────────────────
cast balance $DEPLOYER --rpc-url $RPC --ether
cast call <token> "balanceOf(address)(uint256)" $DEPLOYER --rpc-url $RPC
cast tx <tx_hash> --rpc-url $RPC
cast receipt <tx_hash> --rpc-url $RPC
cast call <contract> "<method()(<returns>)>" --rpc-url $RPC
# ── TRANSACTIONS ───────────────────────────────────────────────────
cast send <to> --value 0.1ether --private-key $PRIVATE_KEY --rpc-url $RPC
cast send <contract> "<method(type)>" <arg> --private-key $PRIVATE_KEY --rpc-url $RPC
cast send <contract> "<payable()>" --value 0.01ether --private-key $PRIVATE_KEY --rpc-url $RPC
# ── GAS ────────────────────────────────────────────────────────────
cast estimate <to> --value 0.1ether --rpc-url $RPC
cast gas-price --rpc-url $RPC
# ── DEPLOY ─────────────────────────────────────────────────────────
forge script script/Deploy<Name>.s.sol:<Name> \
--rpc-url $RPC --private-key $PRIVATE_KEY --broadcast
# ── VERIFY ─────────────────────────────────────────────────────────
sleep 10
forge verify-contract <addr> src/<path>:<Name> \
--chain-id 688689 \
--verifier-url https://api.socialscan.io/pharos-atlantic-testnet/v1/explorer/command_api/contract \
--verifier blockscout
# ── EVENTS ─────────────────────────────────────────────────────────
cast logs --from-block 0 --address <contract> "<Event(types)>" --rpc-url $RPC
# ── AIRDROP (large scale) ──────────────────────────────────────────
CSV_PATH=airdrop.csv BATCH_SIZE=200 \
forge script assets/airdrop/BatchAirdrop.s.sol:BatchAirdrop \
--rpc-url $RPC --private-key $PRIVATE_KEY --broadcastassets/tokens.json
Known token registry
Pre-populated ERC20 addresses and decimals for both networks
assets/erc20/StandardERC20.sol
Built-in ERC20 contract template
Ready-to-deploy OpenZeppelin-based ERC20 with configurable name, symbol, decimals, supply
assets/airdrop/
Built-in airdrop infrastructure
Distributor contracts and batch scripts; supports up to 6000+ addresses via CSV
assets/templates/
Script generation templates
JS (ethers.js v6), TypeScript (viem), Python (web3.py) templates for contract interaction
references/query.md
Query command reference
Full cast call / cast balance / cast tx command specs
references/transaction.md
Transaction command reference
Full cast send / cast estimate / airdrop specs
references/contract.md
Contract command reference
Full forge script / forge verify-contract / ERC20 deploy specs
references/script-gen.md
Script generation reference
Rules for generating JS/TS/Python interaction scripts from ABI or source
Include --constructor-args if needed
Write functions
All public ones
One section per function
Read functions
Useful ones
Skip trivial getters
Events
All emitted events
Include cast logs query
Error cases
All revert strings
Map each to a suggested fix
Source code visible on Pharos Scan with green verified badge
Reference file complete
Every public function has a command template, parameter table, output parsing, and error handling section
Agent Guidelines written
Each section has numbered steps the agent follows in order
Capability Index updated
SKILL.md has rows for every operation, with natural language phrasings
10-second delay documented
Verification section in reference file mentions the sleep 10 indexer delay
Assets copied
Contract template in both assets/<skill>/ and src/<skill>/ if applicable
Error messages match
Revert messages in error handling table match exact strings in the Solidity contract
Double-check the hash; wait and retry if the node is still syncing
Empty return value from cast call
No contract code at the target address
Confirm the contract address and that you're on the right network
execution reverted
Contract call reverted
Extract the revert reason from the error message; check business logic
insufficient funds
Wallet balance too low for amount + gas
Run cast balance --ether and compare against required amount
nonce too low
Nonce conflict — previous transaction still pending
Wait for prior transaction to confirm, or manually set --nonce
connection refused
Missing --rpc-url; Foundry defaulted to localhost:8545
Always pass --rpc-url $RPC explicitly
assets/networks.json unreadable
Config file missing or malformed JSON
Confirm you are running commands from inside the pharos-skill-engine directory
psycopg2 / SQL error on verify
forge verify-contract run too soon after deployment
Add sleep 10 between deployment and verification
PRIVATE_KEY not set
export PRIVATE_KEY not run in current shell
Run: export PRIVATE_KEY=0x...
forge/cast: command not found
Foundry not installed
Run: `curl -L https://foundry.paradigm.xyz