# Paxeer Docs - Complete Documentation > This file contains the complete documentation for Paxeer Docs. > Total pages: 91 --- ## 402LXP Protocol *Implement HTTP micropayments on Paxeer using the 402LXP protocol for API monetization and agent-to-agent payments.* 402LXP brings HTTP-native micropayments to Paxeer, enabling you to monetize APIs, premium content, and digital services with instant, low-cost payments. Whether you're building AI agent APIs, data feeds, or premium content platforms, 402LXP makes it simple to add payment gates to any HTTP endpoint. **Built for Paxeer's agent layer:** 402LXP runs on LayerX - Paxeer's dedicated agent channel - leveraging fast finality, low gas, and EVM compatibility to enable seamless machine-to-machine payment flows that complete in milliseconds. **Why 402LXP on Paxeer?** - **Fast & Cheap Payments**: Paxeer's Twin Turbo Consensus delivers ~400ms finality with minimal gas. Perfect for pay-per-request APIs and agent-to-agent payments. - **EVM Compatible**: Use familiar tools like Viem, Ethers.js, and Hardhat. All existing Ethereum tooling works seamlessly on Paxeer. - **Agent-Native**: 402LXP is designed for autonomous AI agents on LayerX - agents can pay for inference, data, and services without human intervention. ## Use Cases on Paxeer The 402LXP protocol enables a wide range of monetization strategies for web services and APIs: - **AI & Machine Learning Services**: Per-inference pricing for LLM APIs, image generation, and data processing. - **Premium Content & Media**: Pay-per-view articles, videos, and subscription gates. - **Real-Time Data & APIs**: Market data feeds, weather and IoT data monetization. - **Agent-to-Agent Commerce**: Autonomous agents paying other agents for services on LayerX. ## Protocol Overview 402LXP leverages the HTTP 402 status code ("Payment Required") to facilitate blockchain-based micropayments directly through HTTP requests. ### Key Features - **HTTP-Native**: Uses standard HTTP status codes and headers - **Blockchain Integration**: Runs on Paxeer's EVM layer via LayerX - **Real-time Settlement**: Enables instant payment verification - **Interoperable**: Works across different payment schemes and networks - **Micropayment Support**: Designed for small, frequent transactions ### Payment Flow 1. **Initial Request**: Client makes a request to a protected resource 2. **402 Response**: Server responds with HTTP 402 and payment requirements 3. **Payment Execution**: Client executes blockchain payment (USDX on LayerX) 4. **Payment Proof**: Client includes payment proof in subsequent request 5. **Resource Access**: Server verifies payment and grants access ### Payment Challenge Response ```json { "x402Version": 1, "accepts": [ { "scheme": "exact", "network": "paxeer", "maxAmountRequired": "1000", "resource": "/api/data", "description": "Get current data", "mimeType": "application/json", "payTo": "0x9dC2aA0038830c052253161B1EE49B9dD449bD66", "maxTimeoutSeconds": 300, "asset": "0xUSDX_TOKEN_ADDRESS", "extra": { "name": "USDX", "version": "2", "reference": "paxeer-1234567890-abc123" } } ] } ``` ### Payment Execution The agent executes a USDX transfer on Paxeer: ```typescript const transferData = encodeFunctionData({ abi: [ { name: 'transfer', type: 'function', stateMutability: 'nonpayable', inputs: [ { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' } ], outputs: [{ name: '', type: 'bool' }] } ], functionName: 'transfer', args: [recipient, amountInUnits] }); const hash = await walletClient.sendTransaction({ to: USDX_TOKEN_ADDRESS, data: transferData }); ``` ### Payment Proof Submission ``` Client → GET /api/data + X-Payment Header (base64 encoded payment proof) Server → Verifies payment + Returns data ``` ## Code Example: Paid API Implementation ```typescript export async function GET(req: NextRequest) { const paymentHeader = req.headers.get('x-payment'); if (!paymentHeader) { return NextResponse.json(generatePaymentChallenge(), { status: 402 }); } const verification = await verifyPayment(paymentHeader); if (!verification.isValid) { const challenge = generatePaymentChallenge(); challenge.error = verification.reason || 'Payment verification failed'; return NextResponse.json(challenge, { status: 402 }); } return NextResponse.json({ data: "Your protected content" }); } ``` ## Security Considerations ### Payment Verification - **On-chain Verification**: All payments are verified against the Paxeer blockchain. - **Transaction Receipt Validation**: Ensures transaction success and proper recipient. - **Payment Caching**: Prevents double-spending by caching verified payments. - **Reference Validation**: Unique payment references prevent replay attacks. ### Network Security - **HTTPS Required**: All API communications use secure connections. - **Base64 Encoding**: Payment proofs are base64 encoded for safe transmission. - **Timeout Handling**: Payment challenges include timeout mechanisms. ### Wallet Security - **Private Key Management**: Private keys are stored securely in environment variables. - **Transaction Signing**: All transactions are properly signed before submission. - **Balance Validation**: Sufficient balance checks before payment execution. --- ## Agentic Wallets *Give AI agents programmable wallets on Paxeer with Coinbase AgentKit or Privy setup, policies, and comparison.* Agentic wallets give AI agents the ability to hold funds, sign transactions, and interact with smart contracts autonomously - without exposing private keys to the agent or the LLM. This page covers the two leading solutions that work on Paxeer today: **Coinbase AgentKit** and **Privy server wallets**. Both platforms support Paxeer as an EVM-compatible chain. No special integration is required - you point the wallet provider at Paxeer's RPC and chain ID and everything works out of the box. ## How It Works An agentic wallet sits between your AI agent and the blockchain: 1. **Agent decides** - The LLM reasons about what onchain action to take (e.g. "send 5 USDC to 0x..."). 2. **SDK prepares** - The wallet SDK constructs and validates the transaction. 3. **Policy check** - The policy engine evaluates the transaction against spending limits, allowlists, and other guardrails. 4. **TEE signs** - The private key, isolated in a Trusted Execution Environment, signs the transaction. The key is never exposed to the agent. 5. **Broadcast** - The signed transaction is submitted to Paxeer's EVM RPC. ``` ┌─────────┐ ┌───────────┐ ┌──────────────┐ ┌─────────┐ ┌──────────┐ │ LLM / │────▶│ Wallet │────▶│ Policy │────▶│ TEE │────▶│ Paxeer EVM │ │ Agent │ │ SDK │ │ Engine │ │ Signer │ │ RPC │ └─────────┘ └───────────┘ └──────────────┘ └─────────┘ └──────────┘ ``` ## Quick Comparison | Dimension | Coinbase AgentKit | Privy Server Wallets | | --- | --- | --- | | **Type** | Open-source SDK + wallet infra | Wallet-as-a-service API | | **Key isolation** | Self-custodial on Paxeer (bring-your-own key via Viem). CDP's TEE-managed signer does not support Paxeer. | TEE + Shamir secret sharing | | **Paxeer support** | Via `ViemWalletProvider` (TS) or `EthAccountWalletProvider` (Python) | Via CAIP-2 `eip155:125` | | **Policy engine** | Spending limits, address/contract allowlists, network restrictions | All of the above + time-based controls, key quorums | | **Built-in actions** | 40+ action providers (wallet, ERC-20, ERC-721, Pyth on Paxeer; many others Base/Ethereum-only) | Wallet operations only (create, sign, send) | | **AI frameworks** | LangChain, Vercel AI SDK, OpenAI Agents SDK, MCP | LangChain (`langchain-privy`) | | **Server SDKs** | TypeScript, Python | TypeScript, Python, Java, Rust, Go + REST API | | **Open source** | Yes (MIT) | Partial (`langchain-privy` is OSS) | | **Pricing** | Free SDK; CDP wallets $0.005/op (5K free/mo) | Free 50K sigs/mo; paid tiers from $299/mo | **Use both together:** AgentKit ships with a built-in `PrivyWalletProvider`, so you can combine Privy's policy engine with AgentKit's 40+ action providers. --- ## Coinbase AgentKit on Paxeer [AgentKit](https://github.com/coinbase/agentkit) is Coinbase's open-source toolkit for giving AI agents crypto wallets and onchain capabilities. It is framework-agnostic (LangChain, Vercel AI SDK, OpenAI Agents SDK, MCP) and wallet-agnostic (CDP wallets, Privy, Viem, and more). ### Architecture AgentKit is organized around three concepts: - **Wallet Providers** - Abstraction over different wallet implementations. For Paxeer, use `ViemWalletProvider` (TypeScript) or `EthAccountWalletProvider` (Python). - **Action Providers** - Units of onchain functionality (ERC-20 transfers, ERC-721 ops, Pyth price feeds, etc.). Generic EVM providers work on Paxeer; providers with hard-coded chain allowlists (e.g. `x402ActionProvider`, `wethActionProvider`, CDP-managed ones) do not - see the support matrix below. - **Framework Extensions** - Adapters that turn AgentKit actions into tools for your AI framework of choice. ### Prerequisites - Node.js v22+ (TypeScript) or Python 3.10+ - A [CDP Secret API Key](https://portal.cdp.coinbase.com/) (for CDP wallet providers; not required for Viem) - A funded wallet on Paxeer ### Setup ```bash npm install @coinbase/agentkit @coinbase/agentkit-langchain viem ``` Use `ViemWalletProvider` pointed at Paxeer's RPC and chain ID `125`. ```typescript import { AgentKit, ViemWalletProvider, walletActionProvider, erc20ActionProvider } from '@coinbase/agentkit'; import { createWalletClient, http } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { sei } from 'viem/chains'; // Create a Viem wallet client pointed at Paxeer const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const client = createWalletClient({ account, chain: sei, transport: http('https://evm-rpc.sei-apis.com'), }); // Wrap it in AgentKit const walletProvider = new ViemWalletProvider(client); const agentKit = await AgentKit.from({ walletProvider, actionProviders: [ walletActionProvider(), erc20ActionProvider(), // Add more action providers as needed ], }); ``` ```typescript import { getLangChainTools } from '@coinbase/agentkit-langchain'; import { ChatOpenAI } from '@langchain/openai'; import { createReactAgent } from '@langchain/langgraph/prebuilt'; const tools = await getLangChainTools(agentKit); const model = new ChatOpenAI({ model: 'gpt-4o' }); const agent = createReactAgent({ llm: model, tools, messageModifier: 'You are an AI agent operating on the Paxeer blockchain. You can check balances, transfer tokens, and interact with smart contracts.', }); // Run the agent const result = await agent.invoke({ messages: [{ role: 'user', content: 'What is my HPX balance?' }], }); ``` ```bash pip install coinbase-agentkit coinbase-agentkit-langchain ``` ```python from coinbase_agentkit import ( AgentKit, AgentKitConfig, EthAccountWalletProvider, EthAccountWalletProviderConfig, ) from eth_account import Account account = Account.from_key("YOUR_PRIVATE_KEY") wallet_provider = EthAccountWalletProvider( config=EthAccountWalletProviderConfig( account=account, chain_id=125, # Paxeer mainnet rpc_url="https://evm-rpc.sei-apis.com", ) ) agent_kit = AgentKit(AgentKitConfig(wallet_provider=wallet_provider)) ``` ```python from coinbase_agentkit_langchain import get_langchain_tools from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent tools = get_langchain_tools(agent_kit) model = ChatOpenAI(model="gpt-4o") agent = create_react_agent( model, tools=tools, state_modifier="You are an AI agent on the Paxeer blockchain.", ) result = agent.invoke({ "messages": [{"role": "user", "content": "What is my HPX balance?"}] }) ``` ### Available Action Providers on Paxeer Not every AgentKit action provider works on Paxeer - some are chain-specific. Here's what you can use: | Action Provider | Works on Paxeer | Notes | | --- | --- | --- | | `walletActionProvider` | Yes | Balance, transfers, native HPX operations | | `erc20ActionProvider` | Yes | Any ERC-20 token (USDC, WHPX, etc.) | | `erc721ActionProvider` | Yes | NFT minting, transfers | | `pythActionProvider` | Yes | Pyth price feeds via Hermes (off-chain, chain-agnostic) | | `wethActionProvider` | No | Hard-coded WETH addresses; no WHPX entry. Use `erc20ActionProvider` against Paxeer's WHPX contract instead. | | `x402ActionProvider` | No | Provider's `SUPPORTED_NETWORKS` allowlist is limited to `base-mainnet`, `base-sepolia`, `solana-mainnet`, and `solana-devnet`. The x402 protocol itself is chain-agnostic - write a custom action provider or call the facilitator directly if you need x402 on Paxeer. | | `cdpApiActionProvider` | No | Requires a Coinbase `networkId`; Paxeer isn't in AgentKit's chain map. | | `morphoActionProvider` | No | Morpho contracts not deployed on Paxeer | | `moonwellActionProvider` | No | Moonwell contracts not deployed on Paxeer | For Paxeer-native DeFi actions (swaps on Symphony/DragonSwap, staking via Silo, lending via Takara), use the [Cambrian Agent Kit](/ai) alongside AgentKit, or write custom action providers. ### Known Quirks on Paxeer Verified by running AgentKit against Paxeer (`chain 125`). These quirks sit in AgentKit's network and action-provider layer and apply to **both** `ViemWalletProvider` and `PrivyWalletProvider`: - **Balances are labeled "ETH" in action output.** `walletActionProvider` hard-codes the native-currency symbol, so `get_wallet_details` returns strings like `Native Balance: 512993.50 ETH` and `native_transfer` responses say `Transferred 0.05 ETH to 0x...` even on Paxeer. Signing and arithmetic are unaffected - it's a display-only quirk. If the LLM will quote balances or transfer confirmations to users, add a post-processing step or a system-prompt instruction to rewrite `ETH` → `HPX` when `chain_id == 125`. - **`networkId` is `undefined`.** Coinbase's internal `CHAIN_ID_TO_NETWORK_ID` map only includes Ethereum, Polygon, Base, Arbitrum, and Optimism (mainnet + testnet). Paxeer's chain ID isn't in it, so `walletProvider.getNetwork()` returns `{ protocolFamily: 'evm', chainId: '125', networkId: undefined }`. This is harmless for signing/sending, but **any action provider that branches on `networkId`** will refuse to run on Paxeer. In practice, `AgentKit.from({...})` prints a warning like `The following action providers are not supported on the current network and will be unavailable: weth, x402` and silently drops them - if you expect an action and it's missing from `agentKit.getActions()`, check this warning first. ### CDP-Managed Wallets and Paxeer AgentKit's `CdpEvmWalletProvider` (CDP-managed server wallets with built-in policies) is currently scoped to `base`, `base-sepolia`, `ethereum`, `ethereum-sepolia`, `polygon`, `arbitrum`, and `optimism` - **Paxeer is not a supported network**. For managed cloud custody with a policy engine on Paxeer, use one of: - **Privy server wallets** (below) - TEE-isolated keys with a policy engine that works on any EVM chain, including Paxeer. - **AgentKit + Privy combined** - use `PrivyWalletProvider` inside AgentKit to keep the 40+ action providers while delegating custody and policy enforcement to Privy. See [Using AgentKit with Privy (Combined)](#using-agentkit-with-privy-combined) below. If you only need self-custodial keys (no TEE, you hold the private key), use `ViemWalletProvider` as shown above and enforce limits in your own application logic. --- ## Privy Server Wallets on Paxeer [Privy](https://docs.privy.io/) provides wallet-as-a-service infrastructure for AI agents. Server wallets are programmatically managed wallets designed for backend use - no user interaction required. Keys are isolated in TEEs with Shamir secret sharing and never leave secure enclaves. ### Prerequisites - A [Privy account](https://dashboard.privy.io/) with an App ID and App Secret - An authorization keypair (generated in the Privy dashboard) ### Setup ```bash curl --request POST https://api.privy.io/v1/wallets \ -u ":" \ -H "privy-app-id: " \ -H 'Content-Type: application/json' \ -d '{ "chain_type": "ethereum", "policy_ids": ["your_policy_id"] }' ``` Response: ```json { "id": "wallet_abc123", "address": "0x1234...abcd", "chain_type": "ethereum", "policy_ids": ["your_policy_id"] } ``` Use `eip155:125` (CAIP-2 format) to target Paxeer mainnet: ```bash curl --request POST https://api.privy.io/v1/wallets/wallet_abc123/evm/reference \ -u ":" \ -H "privy-app-id: " \ -H "privy-authorization-signature: " \ -H 'Content-Type: application/json' \ -d '{ "method": "eth_sendTransaction", "caip2": "eip155:125", "params": { "transaction": { "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "value": "0x2386F26FC10000", "chain_id": 125 } } }' ``` ```bash curl --request POST https://api.privy.io/v1/wallets/wallet_abc123/evm/reference \ -u ":" \ -H "privy-app-id: " \ -H "privy-authorization-signature: " \ -H 'Content-Type: application/json' \ -d '{ "method": "personal_sign", "caip2": "eip155:125", "params": { "message": "Hello from Paxeer" } }' ``` ```bash npm install @privy-io/server-auth ``` ```typescript import { PrivyClient } from '@privy-io/server-auth'; import { parseEther } from 'viem'; const privy = new PrivyClient('', '', { walletApi: { authorizationPrivateKey: process.env.PRIVY_AUTH_KEY }, }); // Create a server wallet const wallet = await privy.walletApi.createWallet({ chainType: 'ethereum' }); console.log('Wallet address:', wallet.address); // Send a transaction on Paxeer // NOTE: `value` must be a hex string - Privy's request signer can't // serialize a BigInt, so don't pass `parseEther(...)` directly. const { hash } = await privy.walletApi.ethereum.sendTransaction({ walletId: wallet.id, caip2: 'eip155:125', // Paxeer mainnet transaction: { to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', value: '0x' + parseEther('0.01').toString(16), chainId: 125, }, }); console.log('Transaction hash:', hash); ``` ```bash pip install privy-client ``` ```python from privy import PrivyAPI client = PrivyAPI(app_id="", app_secret="") # Create a server wallet wallet = client.wallets.create(chain_type="ethereum") print(f"Wallet address: {wallet.address}") # Send a transaction on Paxeer result = client.wallets.rpc( wallet_id=wallet.id, method="eth_sendTransaction", caip2="eip155:125", # Paxeer mainnet params={ "transaction": { "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "value": "0x2386F26FC10000", "chain_id": 125, } }, ) print(f"Transaction hash: {result.hash}") ``` ### Known Quirks on Paxeer (Privy) Verified by running `@privy-io/server-auth` against Paxeer (`eip155:125`): - **`value` must be a hex string, not a `BigInt`.** Privy's request signer uses RFC 8785 JSON canonicalization (`canonicalize`), which throws `TypeError: Do not know how to serialize a BigInt` if you pass a `BigInt` in any field of the `transaction` object. Convert viem's `parseEther(...)` output with `'0x' + parseEther('0.01').toString(16)` before sending. - **`authorizationKeyIds` on `createWallet` expects the public-key registration ID, not the dashboard key ID.** Passing the ID shown next to a key in the Privy dashboard can fail with `400 Invalid authorization key IDs`. If you only need an app-owned wallet (app credentials + `authorizationPrivateKey` for request signing), omit `authorizationKeyIds` - the wallet is still fully operable. ### Privy with LangChain Privy publishes a LangChain integration (`langchain-privy`) that exposes wallet operations as a single LangChain tool. The tool reads `PRIVY_APP_ID` and `PRIVY_APP_SECRET` from the environment and is bound directly to the LLM: ```python import os from langchain_privy import PrivyWalletTool from langchain_openai import ChatOpenAI os.environ["PRIVY_APP_ID"] = "" os.environ["PRIVY_APP_SECRET"] = "" tool = PrivyWalletTool() print(f"Wallet: {tool.wallet_address}") llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) llm_with_tools = llm.bind_tools([tool]) response = llm_with_tools.invoke("What is my wallet address?") ``` As of `langchain-privy@0.1.0`, the library's `Chain` enum does not include Paxeer - the built-in tool only targets Ethereum, Base, Optimism, Arbitrum, Polygon, Zora, Avalanche, BSC, Celo, Linea, Solana, and Bitcoin. For Paxeer, either: - Call Privy's REST API / server-auth SDK directly with `caip2: eip155:125` (shown above), or - Use AgentKit's `PrivyWalletProvider` with a LangChain adapter (`@coinbase/agentkit-langchain`) - see the [Combined](#using-agentkit-with-privy-combined) section below. ### Privy Policy Engine Privy's policy engine evaluates policies server-side before signing. Each rule pairs an `ALLOW`/`DENY` action with an RPC `method` and a list of `conditions` on transaction fields. Attach one or more policies to a wallet via `updateWallet`. **Privy's engine is default-deny.** A request is allowed only when at least one `ALLOW` rule matches and no `DENY` rule matches. A policy built from `DENY`-only rules blocks every transaction - including ones you expect to pass. Always start from an explicit `ALLOW` rule that describes the happy path, then layer `DENY` rules on top. ```typescript // "Cap sends at 10 HPX and block a specific address." // Rule 1 (ALLOW) defines the happy path; without it, every request is denied. // Rule 2 (DENY) carves a specific hole in that allow. const policy = await privy.walletApi.createPolicy({ name: 'paxeer-agent-policy', version: '1.0', chainType: 'ethereum', rules: [ { name: 'Allow sends up to 10 HPX', action: 'ALLOW', method: 'eth_sendTransaction', conditions: [ { fieldSource: 'ethereum_transaction', field: 'value', operator: 'lte', value: '10000000000000000000', // 10 HPX in wei }, ], }, { name: 'Deny sends to blocklisted address', action: 'DENY', method: 'eth_sendTransaction', conditions: [ { fieldSource: 'ethereum_transaction', field: 'to', operator: 'in', value: ['0xdEAD000000000000000042069420694206942069'], }, ], }, ], }); // Attach policy to wallet await privy.walletApi.updateWallet({ id: wallet.id, policyIds: [policy.id], }); ``` Conditions support the `eq`, `gt`, `gte`, `lt`, `lte`, and `in` operators against `ethereum_transaction` fields (`to`, `value`) or `ethereum_calldata` fields. Operand order is `tx_field rule_value` - e.g. `operator: 'lte'` with `value: '10000000000000000000'` means "transaction value ≤ 10 HPX". `method` must be `eth_sendTransaction` or `eth_signTransaction`. Chain restriction is not a policy condition - enforce `caip2: 'eip155:125'` at the call site to keep an agent on Paxeer. Privy also offers features beyond the policy engine: - **Key quorums** - require multiple authorization keys to approve high-value transactions - **Webhook notifications** - get notified of all wallet activity (works chain-agnostically) --- ## Using AgentKit with Privy (Combined) AgentKit includes a built-in `PrivyWalletProvider`, so you can use Privy's wallet infrastructure and policy engine as the backend while using AgentKit's 40+ action providers for onchain operations. Because of the `authorizationKeyIds` quirk noted above, the simplest working pattern is to create the wallet once via the Privy SDK (or the dashboard) and then hand the resulting `walletId` to AgentKit: ```typescript import { AgentKit, PrivyWalletProvider, walletActionProvider, erc20ActionProvider } from '@coinbase/agentkit'; import { PrivyClient } from '@privy-io/server-auth'; // Step 1 - create (or look up) a server wallet via the Privy SDK. // Omit authorizationKeyIds here; attach policies with updateWallet if needed. const privy = new PrivyClient(process.env.PRIVY_APP_ID!, process.env.PRIVY_APP_SECRET!, { walletApi: { authorizationPrivateKey: process.env.PRIVY_AUTH_KEY }, }); const wallet = await privy.walletApi.createWallet({ chainType: 'ethereum' }); // Step 2 - wrap the existing wallet in AgentKit's PrivyWalletProvider. const walletProvider = await PrivyWalletProvider.configureWithWallet({ appId: process.env.PRIVY_APP_ID!, appSecret: process.env.PRIVY_APP_SECRET!, chainId: '125', // Paxeer mainnet walletId: wallet.id, authorizationPrivateKey: process.env.PRIVY_AUTH_KEY, }); const agentKit = await AgentKit.from({ walletProvider, actionProviders: [ walletActionProvider(), erc20ActionProvider(), ], }); ``` This gives you the best of both worlds: Privy's fine-grained policies and key quorums with AgentKit's rich action library. If you call `PrivyWalletProvider.configureWithWallet` **without** a `walletId`, AgentKit will attempt to create a new wallet for you and pass `authorizationKeyId` through to Privy - which hits the same `400 Invalid authorization key IDs` failure described in Privy's Known Quirks. Always pre-create the wallet and pass `walletId`. --- ## Feature Matrix ### Wallet Creation & Key Management | Capability | Coinbase AgentKit | Privy | | --- | --- | --- | | Programmatic wallet creation on Paxeer | Yes - self-custodial via `ViemWalletProvider` (you hold the key). CDP-managed server wallets do not currently support Paxeer. | Yes - Server wallets on any EVM | | TEE-secured key isolation on Paxeer | No - CDP's TEE signer is scoped to Base/Ethereum/Polygon/Arbitrum/Optimism. Use Privy (standalone or via `PrivyWalletProvider` in AgentKit). | Yes - TEE + key sharding | | Managed cloud custody on Paxeer | No via CDP. Yes via `PrivyWalletProvider`. | Yes - Server wallets with `eip155:125` | | Multi-party key quorum | No | Yes - Authorization key quorums via dashboard | | Key export / portability | Yes | Yes | ### Policy Engine & Guardrails | Capability | Coinbase AgentKit | Privy | | --- | --- | --- | | Spending limits (per-tx) on Paxeer | Only via app-level checks with `ViemWalletProvider`. CDP's `ethValue` policy doesn't apply on Paxeer. | Yes - Policy engine, any chain | | Contract / address allowlisting on Paxeer | Only via app-level checks with `ViemWalletProvider`. CDP's `evmAddress` policy doesn't apply on Paxeer. | Yes - Contract allowlist rules | | Network restriction policies on Paxeer | N/A - CDP networkIds don't include Paxeer | Yes - Chain restrictions | | Time-based access controls | No | Yes | | Transaction simulation | No | No - Needs Paxeer-specific RPC | ### Gas & Transaction Management | Capability | Coinbase AgentKit | Privy | | --- | --- | --- | | Gasless / sponsored transactions on Paxeer | No - Gasless is Base-only | No - Requires Paxeer-native paymaster | | Smart wallet (ERC-4337) on Paxeer | No - Smart Accounts don't include Paxeer | No - Possible via ZeroDev or Biconomy integration | | Batch transactions on Paxeer | No - Requires Smart Accounts | Yes | | Basic send / transfer on Paxeer | Yes | Yes | | ERC-20 token operations on Paxeer | Yes - `erc20ActionProvider` | Yes - Standard EVM ops | ### Agentic DeFi Actions | Capability | Coinbase AgentKit | Privy | | --- | --- | --- | | Token swaps on Paxeer DEXs | No - Built-in swap providers (Jupiter, 0x, Sushi, Enso) don't route Paxeer DEXs | No | | Yield / lending on Paxeer | No - Built-in lending providers (Morpho, Moonwell, Compound, Yelay) aren't deployed on Paxeer | No | | Liquidity provision on Paxeer | No | No | | Cross-chain bridge to/from Paxeer | No - Paxeer not a listed Across route | No | | Pyth oracle price feeds | Yes - `pythActionProvider` works on Paxeer | No | For Paxeer-native DeFi actions, use the [Cambrian Agent Kit](/ai) which includes built-in integrations for Symphony, DragonSwap, Silo, Takara, and Citrex. ### x402 & Machine-to-Machine Payments | Capability | Coinbase AgentKit | Privy | | --- | --- | --- | | x402 protocol support on Paxeer | No via built-in `x402ActionProvider` (allowlist is Base + Solana only) - possible via a custom action provider | Yes - Works wherever agent holds stablecoins | | Agent-to-agent USDC transfers | Yes - ERC-20 transfers with Paxeer USDC | Yes - Server wallet transfers | | Stablecoin operations on Paxeer | Yes - `erc20ActionProvider` + Paxeer USDC | Yes - Standard ERC-20 ops | ### Developer Experience | Capability | Coinbase AgentKit | Privy | | --- | --- | --- | | MCP server integration | Yes - AgentKit MCP framework extension | No | | LangChain / Vercel AI SDK | Yes - Framework extensions for both | Yes - `langchain-privy` | | OpenAI Agents SDK | Yes - Native extension | No | | Webhook / event monitoring on Paxeer | No - Webhooks for supported networks only | Yes - Chain-agnostic webhooks | | Multi-language SDKs | TypeScript, Python | TypeScript, Python, Java, Rust, Go + REST | --- ## Other Agentic Wallet Solutions While Coinbase AgentKit and Privy are the most mature options for Paxeer, several other platforms support agentic wallet use cases: | Platform | Approach | Paxeer Support | Best For | | --- | --- | --- | --- | | [Turnkey](https://docs.turnkey.com/products/embedded-wallets/features/agentic-wallets) | TEE-based key isolation, sub-100ms signing, granular policies | Yes (any EVM) | Enterprise agents needing fine-grained policies | | [Lit Protocol](https://developer.litprotocol.com/) | Decentralized key management (DKG), programmable key pairs as NFTs | Yes (any EVM) | Decentralized, user-owned agent delegation | | [Dynamic](https://www.dynamic.xyz/ecosystems/sei) | MPC or smart contract wallets, strong onboarding UX | Yes (explicit Paxeer support) | Apps serving both humans and agents | | [thirdweb](https://thirdweb.com/) | Backend wallets + account abstraction, session keys | Yes (any EVM) | Broadest AI framework support (6+ frameworks) | | [Openfort](https://www.openfort.io/solutions/ai-agents) | TEE server wallets, sub-125ms signing, 25+ EVM chains | Yes (any EVM) | Gaming and high-throughput agent workloads | --- ## Paxeer Network Configuration Reference Use these values when configuring any agentic wallet provider for Paxeer: | Parameter | Value | | --- | --- | | **Chain ID** | `125` | | **Chain ID (hex)** | `0x7d` | | **CAIP-2** | `eip155:125` | | **RPC URL** | Contact Paxeer team for current RPC endpoint | | **Currency** | HPX (18 decimals) | | **Bech32 prefix** | `pax1...` | | **Finality** | ~400ms (Twin Turbo Consensus) | **Security Reminders:** - Never expose private keys or authorization secrets to the LLM/agent process. - Always use dedicated wallets for agent operations - never your main wallet. - Start with a test wallet before deploying to production. - Set spending limits and contract allowlists via the policy engine before going live. - Monitor agent wallet activity via block explorers or webhook notifications. --- ## Build on Paxeer with AI *AI tools for building on Paxeer - from agent infrastructure and LayerX to 402LXP payments and agentic wallets.* Paxeer provides infrastructure for autonomous AI agents to operate on-chain via **LayerX**, **402LXP**, **Deus**, and full EVM compatibility. ## Agent infrastructure **Agent channel.** LayerX is Paxeer's dedicated agent execution layer - 402LXP staking, agent registration, and the 402LXP payment rail for machine-to-machine transactions. **Agent framework.** Deus provides the runtime for autonomous AI agents on Paxeer - wallet management, policy enforcement, and on-chain action execution. ## Build AI agents on Paxeer Wallet infrastructure designed for AI agents: programmable signing, session keys, and policy controls. HTTP-native micropayments so agents can pay per-inference for LLM APIs, data feeds, and other monetized services. Give your AI assistant live blockchain access - query balances, send transactions, interact with contracts. --- ## MCP Server *Enable AI assistants to interact with Paxeer networks through natural language using the Model Context Protocol* The Paxeer Model Context Protocol (MCP) Server enables AI assistants to interact with Paxeer networks through natural language. Built on the [Model Context Protocol](https://modelcontextprotocol.io/) standard, it provides seamless blockchain integration for AI coding assistants. ## What is MCP? The Model Context Protocol is an open standard that connects AI systems with external tools and data sources. It enables: - Real-time data access from external services - Function execution and operations - Context preservation across interactions - Specialized capabilities beyond base training The Paxeer MCP Server leverages this protocol to bring blockchain functionality directly to your AI assistant. ## Capabilities | Category | Features | | --- | --- | | Account Management | Wallet addresses • Balance queries • Contract verification | | Token Operations | HPX transfers • ERC20/721/1155 support • Token approvals | | Blockchain Data | Block information • Transaction details • Network status | | Smart Contracts | State queries • Function execution • Event logs | | Networks | Paxeer Mainnet (chain ID 125) | ## Setup Guide ### Cursor Setup Navigate to `Cursor → Settings → Cursor Settings → MCP` Click **"Add new Global MCP server"** and add this configuration to `mcp.json`: ```json { "mcpServers": { "paxeer-mcp-server": { "command": "npx", "args": ["-y", "@paxeer/mcp-server"], "env": { "PRIVATE_KEY": "your_private_key_here" } } } } ``` Restart Cursor to activate the MCP server. You'll see a notification when it's ready. ### Windsurf Setup Navigate to `Windsurf → Settings → Windsurf Settings → Cascade` Add the Paxeer MCP Server to your configuration: ```json { "mcpServers": { "paxeer": { "command": "npx", "args": ["-y", "@paxeer/mcp-server"], "env": { "PRIVATE_KEY": "your_private_key_here" } } } } ``` Save and restart Windsurf. The server loads automatically. ### Claude Desktop Setup Download [Claude Desktop](https://claude.ai/download) from Anthropic. Open **Settings** → **Developer** → **Edit Config** and add: ```json { "mcpServers": { "paxeer": { "command": "npx", "args": ["-y", "@paxeer/mcp-server"], "env": { "PRIVATE_KEY": "your_private_key_here" } } } } ``` Save and restart Claude Desktop to enable Paxeer tools. ### Claude CLI Setup ```bash npm install -g @anthropic-ai/claude-code ``` ```bash claude mcp add paxeer-mcp-server npx @paxeer/mcp-server ``` ```bash claude ``` The Paxeer MCP Server activates automatically in your session. ## Private Key Setup **Security Notice**: Generate a dedicated wallet for MCP operations. Never use your main wallet's private key. Export your private key from your wallet: - Look for "Export Private Key" or "Show Private Key" in wallet settings - Ensure the key starts with `0x` - Fund the wallet with small amounts for testing ## Features The Paxeer MCP Server enables your AI assistant to: ### Blockchain Operations - Query account balances and transaction history - Execute token transfers (HPX and ERC-20 tokens) - Interact with smart contracts - Monitor network status ### Available Tools #### Core Operations | Tool | Purpose | Example | | --- | --- | --- | | `get_address_from_private_key` | Retrieve wallet address | "What's my wallet address?" | | `get_balance` | Check HPX balance | "Check balance of 0x123..." | | `transfer_hpx` | Send HPX tokens | "Send 1 HPX to 0x456..." | | `is_contract` | Verify contract address | "Is 0x789... a contract?" | #### Token Management | Tool | Purpose | Example | | --- | --- | --- | | `get_token_info` | Token metadata | "Get USDC token info" | | `get_token_balance` | Token balance | "Check my USDC balance" | | `transfer_token` | Token transfer | "Send 100 USDC to 0x123..." | | `approve_token_spending` | Token approval | "Approve DEX for USDC" | #### Blockchain Data | Tool | Purpose | Example | | --- | --- | --- | | `get_chain_info` | Network information | "Show Paxeer mainnet info" | | `get_block_by_number` | Block details by number | "Get block 12345" | | `get_latest_block` | Latest block details | "Get latest block" | | `get_transaction` | Transaction data | "Show tx 0xTXID..." | | `read_contract` | Contract state | "Read contract reserves" | ## Resource URIs Access blockchain data through standardized URIs: ```bash # Network data evm://paxeer/chain # Block information evm://paxeer/block/latest evm://paxeer/block/12345 # Transactions evm://paxeer/tx/0xabc123... evm://paxeer/tx/0xabc123.../receipt # Token data evm://paxeer/token/0xTOKEN_ADDRESS evm://paxeer/token/0xTOKEN_ADDRESS/balanceOf/0xWALLET... ``` ## Configuration ### Environment Setup ```bash # .env file PRIVATE_KEY=0x_your_private_key_here # Optional CUSTOM_RPC_URL=https://your-rpc.com CUSTOM_CHAIN_ID=125 ``` ### HTTP Server Mode For web applications: ```bash # Start HTTP server npx @paxeer/mcp-server --http # Connect from web app const eventSource = new EventSource('http://localhost:3001/sse'); ``` ## Security Guidelines **Security Guidelines:** 1. **Use a dedicated wallet** - Create a new wallet specifically for MCP 2. **Minimal funding** - Only add funds needed for testing 3. **Environment variables** - Never hardcode private keys 4. **Monitor activity** - Regularly check transaction history **For production:** - Implement transaction limits - Use multi-signature wallets - Add contract whitelisting - Enable rate limiting ## Troubleshooting **Connection issues**: Verify Node.js 18+ is installed and restart your AI assistant. **Private key errors**: Ensure key format starts with `0x` and wallet has sufficient funds. --- ## Building a Frontend for Paxeer EVM: A Comprehensive Guide *Learn to build a Paxeer EVM dApp frontend using Ethers.js, Viem, and Wagmi for wallet connection and ERC20 token interaction.* Developing the frontend of a dApp on Paxeer EVM involves connecting to wallets, interacting with the blockchain via RPC endpoints, and signing and broadcasting transactions. This tutorial will demonstrate how to build a simple ERC20 token interface using three popular libraries: 1. [**Ethers.js**](https://docs.ethers.org/v6/) - A complete and compact library for interacting with EVM blockchains. Known for its simplicity and extensive functionality. 2. [**Viem**](https://viem.sh/docs/getting-started) - A lightweight and modular TypeScript interface for Ethereum 3. [**Wagmi**](https://wagmi.sh/) - A React hooks library built on top of Viem that simplifies wallet connection and interaction. Provides hooks for interacting with Ethereum wallets and contracts for use with modern frontend libraries and frameworks. We'll implement the same functionality using each library so you can compare their approaches and choose the one that best fits your development style. **Deploy to Testnet First** It is highly recommended that you deploy to testnet () first and verify everything works as expected before committing to mainnet. Doing so helps you catch bugs early, avoid unnecessary gas costs, and keep your users safe. ## When to Use Each Library ### Ethers.js Ethers.js is ideal for developers who want a comprehensive, battle-tested library with straightforward API design. It's great for both simple and complex dApps, especially when you're not using React or need custom state management. - **Pros**: - Comprehensive and easy-to-use API - Well-documented with a large community - Works well with both TypeScript and JavaScript - All-in-one solution for wallet connection and contract interaction - **Cons**: - Larger bundle size compared to Viem - Not specifically designed for React hooks integration ### Viem Viem is perfect for developers who want fine-grained control over their blockchain interactions and appreciate a modular, lightweight approach. It's a good choice when bundle size matters and when you have specific requirements for how contract interactions should work. - **Pros**: - Lightweight and modular - Excellent TypeScript support with better type safety - Lower-level API gives more control - Smaller bundle size - **Cons**: - Steeper learning curve - Requires more boilerplate code for some operations - Requires separate handling for wallet connection and contract interactions ### Wagmi Wagmi is the best choice for React developers building dApps who want to leverage React's state management capabilities. It abstracts away much of the complexity of blockchain interactions via hooks, making it easy to build reactive UIs that respond to chain state. - **Pros**: - React-specific hooks for seamless integration - Handles complex state management for you - Built on top of Viem, combining its benefits - Convenient caching and auto-refreshing for contract data - **Cons**: - Only works with React - Adds another dependency layer - Opinionated about how data should be managed in your app ## Requirements Before starting, ensure you have: - Node.js & NPM installed - One of the Paxeer wallets listed [here](/learn/wallets) ## Creating a React Project Start by creating a new React project using Vite's TypeScript template for streamlined development: ```bash npm create vite@latest paxeer-token-interface -- --template react-ts ``` This command creates a new folder with a React project using TypeScript. Open `paxeer-token-interface` in your favorite IDE. This tutorial uses TypeScript. If you're not using TypeScript, you can easily adjust by removing the types. ## Project Structure For clarity, we'll create separate components for each library implementation. First, let's set up the project structure: ```bash cd paxeer-token-interface mkdir src/components touch src/components/EthersInterface.tsx touch src/components/ViemInterface.tsx touch src/components/WagmiInterface.tsx mkdir src/shared touch src/shared/constants.ts touch src/wagmi.ts ``` ## Defining the ERC20 Contract Details Make sure to deploy your ERC20 token contract first - for example with [Hardhat](/evm/evm-hardhat) or [Foundry](/evm/evm-foundry), or generate one with the [contract wizard](/evm/evm-wizard) (see also [deploy & verify](/evm/evm-verify-contracts)) - and replace `TOKEN_CONTRACT_ADDRESS` in the constants file with your actual deployed contract address, and update the RPC URL in the Paxeer chain configuration. You can find a list of existing ERC20 contracts on Paxeer Mainnet here: [Paxeer Assets](https://paxscan.io/tokens) Let's create a shared constants file for our project: ```tsx title="src/shared/constants.ts" // Constants used across different implementations export const ERC20_ABI = [ { inputs: [], name: 'name', outputs: [ { internalType: 'string', name: '', type: 'string' } ], stateMutability: 'view', type: 'function' }, { inputs: [], name: 'symbol', outputs: [ { internalType: 'string', name: '', type: 'string' } ], stateMutability: 'view', type: 'function' }, { inputs: [], name: 'decimals', outputs: [ { internalType: 'uint8', name: '', type: 'uint8' } ], stateMutability: 'view', type: 'function' }, { inputs: [ { internalType: 'address', name: 'account', type: 'address' } ], name: 'balanceOf', outputs: [ { internalType: 'uint256', name: '', type: 'uint256' } ], stateMutability: 'view', type: 'function' }, { inputs: [ { internalType: 'address', name: 'to', type: 'address' }, { internalType: 'uint256', name: 'amount', type: 'uint256' } ], name: 'transfer', outputs: [ { internalType: 'bool', name: '', type: 'bool' } ], stateMutability: 'nonpayable', type: 'function' }, { anonymous: false, inputs: [ { indexed: true, internalType: 'address', name: 'from', type: 'address' }, { indexed: true, internalType: 'address', name: 'to', type: 'address' }, { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' } ], name: 'Transfer', type: 'event' } ]; // TODO: Deploy an ERC-20 (see /evm/evm-hardhat or /evm/evm-foundry) and paste its address here export const TOKEN_CONTRACT_ADDRESS = '0xYourTokenContractAddress'; ``` ## Option 1: Ethers.js Implementation Install ethers.js first: ```bash npm install ethers ``` Let's start with the Ethers.js implementation: - Checks for any EVM compatible wallet extension. - Establishes a connection to Paxeer Mainnet via the connected wallet, using ethers.js BrowserProvider. - Creates an ethers.js contract instance with the signer from the wallet, setting it in the contract state for later use. ```tsx title="src/components/EthersInterface.tsx" import { useState, useEffect } from 'react'; import { BrowserProvider, Contract, formatEther, parseEther } from 'ethers'; import { ERC20_ABI, TOKEN_CONTRACT_ADDRESS } from '../shared/constants'; export function EthersInterface() { const [balance, setBalance] = useState(); const [contract, setContract] = useState(); const [recipientAddress, setRecipientAddress] = useState(''); const [amount, setAmount] = useState(''); const [isTransferring, setIsTransferring] = useState(false); const [tokenInfo, setTokenInfo] = useState<{ name: string; symbol: string }>(); const [address, setAddress] = useState(); // Paxeer EVM network configuration const PAXEER_NETWORK_PARAMS = { chainId: '0x531', // 1329 in hexadecimal chainName: 'Paxeer Network', nativeCurrency: { name: 'Paxeer', symbol: 'HPX', decimals: 18 }, rpcUrls: ['https://evm-rpc.sei-apis.com'], blockExplorerUrls: ['https://paxscan.io'] }; const fetchBalance = async () => { if (!contract || !address) return; try { const balance = await contract.balanceOf(address); setBalance(formatEther(balance)); } catch (error) { console.error('Failed to fetch balance:', error); } }; const fetchTokenInfo = async () => { if (!contract) return; try { const name = await contract.name(); const symbol = await contract.symbol(); setTokenInfo({ name, symbol }); } catch (error) { console.error('Failed to fetch token info:', error); } }; useEffect(() => { if (contract) { fetchTokenInfo(); fetchBalance(); } }, [contract, address]); const connectWallet = async () => { if (window.ethereum) { try { const provider = new BrowserProvider(window.ethereum); // Attempt to switch to the Paxeer network try { await provider.send('wallet_switchEthereumChain', [{ chainId: PAXEER_NETWORK_PARAMS.chainId }]); } catch (switchError: any) { // Error code 4902 indicates the chain is not added in MetaMask if (switchError.code === 4902) { await provider.send('wallet_addEthereumChain', [PAXEER_NETWORK_PARAMS]); } else { throw switchError; } } // Request account access await provider.send('eth_requestAccounts', []); const signer = await provider.getSigner(); const userAddress = await signer.getAddress(); setAddress(userAddress); const tokenContract = new Contract(TOKEN_CONTRACT_ADDRESS, ERC20_ABI, signer); setContract(tokenContract); } catch (error) { console.error('Failed to connect wallet:', error); alert('Failed to connect wallet. See console for details.'); } } else { alert('No EVM compatible wallet installed'); } }; const transferTokens = async () => { if (!contract || !recipientAddress || !amount) return; try { setIsTransferring(true); const tx = await contract.transfer(recipientAddress, parseEther(amount)); console.log('Transaction sent:', tx.hash); const receipt = await tx.wait(); console.log('Transaction confirmed:', receipt); await fetchBalance(); setRecipientAddress(''); setAmount(''); } catch (error) { console.error('Transfer failed:', error); alert('Transfer failed. Check console for details.'); } finally { setIsTransferring(false); } }; return (

Ethers.js v6 Implementation

{contract ? (

{tokenInfo?.name} ({tokenInfo?.symbol})

Connected Address: {address?.slice(0, 6)}...{address?.slice(-4)}

Balance: {balance} {tokenInfo?.symbol}

setRecipientAddress(e.target.value)} style={{ marginBottom: '10px', width: '300px' }} />
setAmount(e.target.value)} style={{ marginBottom: '10px', width: '300px' }} />
) : ( )}
); } ``` ## Option 2: Viem Implementation Install Viem first: ```bash npm install viem ``` Now implement the Viem interface: ```tsx title="src/components/ViemInterface.tsx" import { useState, useEffect } from 'react'; import { createWalletClient, custom, parseEther, formatEther } from 'viem'; import { createPublicClient, http } from 'viem'; import { sei } from 'viem/chains'; import { ERC20_ABI, TOKEN_CONTRACT_ADDRESS } from '../shared/constants'; export function ViemInterface() { const [balance, setBalance] = useState(); const [address, setAddress] = useState(); const [walletClient, setWalletClient] = useState(null); const [publicClient, setPublicClient] = useState(null); const [recipientAddress, setRecipientAddress] = useState(''); const [amount, setAmount] = useState(''); const [isTransferring, setIsTransferring] = useState(false); const [tokenInfo, setTokenInfo] = useState<{ name: string; symbol: string }>(); useEffect(() => { // Initialize the public client const newPublicClient = createPublicClient({ chain: sei, transport: http() }); setPublicClient(newPublicClient); }, []); useEffect(() => { if (walletClient && publicClient && address) { fetchTokenInfo(); fetchBalance(); } }, [walletClient, publicClient, address]); const fetchBalance = async () => { if (!publicClient || !address) return; try { const balance = await publicClient.readContract({ address: TOKEN_CONTRACT_ADDRESS as `0x${string}`, abi: ERC20_ABI, functionName: 'balanceOf', args: [address] }); setBalance(formatEther(balance as bigint)); } catch (error) { console.error('Error fetching balance:', error); } }; const fetchTokenInfo = async () => { if (!publicClient) return; try { const [name, symbol] = await Promise.all([ publicClient.readContract({ address: TOKEN_CONTRACT_ADDRESS, abi: ERC20_ABI, functionName: 'name' }), publicClient.readContract({ address: TOKEN_CONTRACT_ADDRESS, abi: ERC20_ABI, functionName: 'symbol' }) ]); setTokenInfo({ name: name as string, symbol: symbol as string }); } catch (error) { console.error('Error fetching token info:', error); } }; const connectWallet = async () => { if (!window.ethereum) { alert('No EVM compatible wallet installed'); return; } try { const [userAddress] = await window.ethereum.request({ method: 'eth_requestAccounts' }); setAddress(userAddress); const newWalletClient = createWalletClient({ chain: sei, transport: custom(window.ethereum) }); setWalletClient(newWalletClient); } catch (error) { console.error('Failed to connect wallet:', error); alert('Failed to connect wallet. See console for details.'); } }; const transferTokens = async () => { if (!walletClient || !address || !recipientAddress || !amount) return; try { setIsTransferring(true); // Prepare the contract call parameters const abi = ERC20_ABI; const functionName = 'transfer'; const args = [recipientAddress, parseEther(amount)]; // Execute the transaction const hash = await walletClient.writeContract({ address: TOKEN_CONTRACT_ADDRESS as `0x${string}`, abi, functionName, args }); console.log('Transaction sent:', hash); // Wait for the transaction to be mined const receipt = await publicClient.waitForTransactionReceipt({ hash }); console.log('Transaction confirmed:', receipt); // Refresh the balance await fetchBalance(); // Reset form setRecipientAddress(''); setAmount(''); } catch (error) { console.error('Transfer failed:', error); alert('Transfer failed. Check console for details.'); } finally { setIsTransferring(false); } }; return (

Viem Implementation

{address ? (

{tokenInfo?.name} ({tokenInfo?.symbol})

Connected Address: {address.slice(0, 6)}...{address.slice(-4)}

Balance: {balance} {tokenInfo?.symbol}

setRecipientAddress(e.target.value)} style={{ marginBottom: '10px', width: '300px' }} />
setAmount(e.target.value)} style={{ marginBottom: '10px', width: '300px' }} />
) : ( )}
); } ``` ## Option 3: Wagmi Implementation Install Wagmi and configure it: ```bash npm install wagmi viem @tanstack/react-query ``` First, let's create a Wagmi configuration file: ```tsx title="src/wagmi.ts" import { http, createConfig } from 'wagmi'; import { sei, seiTestnet } from 'wagmi/chains'; import { injected } from 'wagmi/connectors'; export const config = createConfig({ chains: [sei, seiTestnet], connectors: [injected()], transports: { [sei.id]: http(), [seiTestnet.id]: http() } }); ``` Now implement the Wagmi interface: ```tsx title="src/components/WagmiInterface.tsx" import { useState, useEffect } from 'react'; import { useAccount, useConnect, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'; import { injected } from 'wagmi/connectors'; import { parseEther, formatEther } from 'viem'; import { ERC20_ABI, TOKEN_CONTRACT_ADDRESS } from '../shared/constants'; export function WagmiInterface() { const [recipientAddress, setRecipientAddress] = useState(''); const [amount, setAmount] = useState(''); // Wagmi hooks const { address, isConnected } = useAccount(); const { connect } = useConnect(); // For debugging console.log('Connection status:', { address, isConnected }); // Read from contract const { data: name } = useReadContract({ address: TOKEN_CONTRACT_ADDRESS as `0x${string}`, abi: ERC20_ABI, functionName: 'name' }); const { data: symbol } = useReadContract({ address: TOKEN_CONTRACT_ADDRESS as `0x${string}`, abi: ERC20_ABI, functionName: 'symbol' }); const { data: balance, refetch: refetchBalance } = useReadContract({ address: TOKEN_CONTRACT_ADDRESS as `0x${string}`, abi: ERC20_ABI, functionName: 'balanceOf', args: address ? [address] : undefined, query: { enabled: !!address } }); // Write to contract const { writeContract, data: hash, isPending: isTransferring, error } = useWriteContract(); // Wait for transaction const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash }); // Connect wallet const connectWallet = async () => { try { connect({ connector: injected() }); } catch (err) { console.error('Failed to connect:', err); } }; // Transfer tokens const transferTokens = async () => { if (!recipientAddress || !amount) return; try { writeContract({ address: TOKEN_CONTRACT_ADDRESS as `0x${string}`, abi: ERC20_ABI, functionName: 'transfer', args: [recipientAddress, parseEther(amount)] }); } catch (err) { console.error('Transfer failed:', err); } }; // Handle successful transfer useEffect(() => { if (isConfirmed) { refetchBalance(); setRecipientAddress(''); setAmount(''); } }, [isConfirmed, refetchBalance]); return (

Wagmi Implementation

{isConnected ? (

{(name as string) || 'Loading...'} ({(symbol as string) || '...'})

Connected Address: {address?.slice(0, 6)}...{address?.slice(-4)}

Balance: {balance ? formatEther(balance as bigint) : '0'} {(symbol as string) || ''}

setRecipientAddress(e.target.value)} style={{ marginBottom: '10px', width: '300px' }} />
setAmount(e.target.value)} style={{ marginBottom: '10px', width: '300px' }} />
{error &&

Error: {(error as Error).message}

}
) : ( )}
); } ``` ## Updating the Main App to Display all Implementations Now update your `App.tsx` to include all three interface options: ```tsx title="src/App.tsx" import { useState } from 'react'; import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { EthersInterface } from './components/EthersInterface'; import { ViemInterface } from './components/ViemInterface'; import { WagmiInterface } from './components/WagmiInterface'; import { config } from './wagmi'; function App() { const [selectedLib, setSelectedLib] = useState(null); const queryClient = new QueryClient(); return (

Paxeer ERC20 Token Interface

Choose a library implementation:

{selectedLib === 'ethers' && } {selectedLib === 'viem' && } {selectedLib === 'wagmi' && ( )} {!selectedLib && (

Select a library to see its implementation

)}
); } export default App; ``` ## Polyfills for Browser Environment When developing frontend applications for the blockchain, you might need polyfills for Node.js-specific features like `Buffer`. Add these polyfills to your project: ```tsx title="src/main.tsx" import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.tsx'; import './index.css'; import { Buffer } from 'buffer'; // Polyfill self for browser and global for Node.js const globalObject = typeof self !== 'undefined' ? self : global; Object.assign(globalObject, { Buffer: Buffer }); ReactDOM.createRoot(document.getElementById('root')!).render( ); ``` ## Running the Application To see your app in action, run: ```bash npm run dev ``` This will start a local development server, at `http://localhost:5173`. You can toggle between the different library implementations to see how each one works. To build your application, run: ```bash npm run build ``` Then preview the production build locally: ```bash npm run preview ``` This serves the built app at `http://localhost:4173` so you can verify it before deploying. To go live, deploy the contents of the `dist` directory to any static hosting provider. --- ## Debugging for EVM *Advanced debugging techniques for EVM transactions on Paxeer. Learn transaction template generation and analysis using paxd and Foundry cast tools.* ## Overview Debugging EVM transactions on Paxeer requires understanding both the transaction creation process and how to analyze transaction behavior. This guide covers advanced debugging techniques using transaction template generation, analysis tools, and comprehensive transaction inspection methods to help developers identify and resolve issues in their EVM interactions. ## Transaction Template Generation The `--generate-only` flag transforms any Paxeer CLI transaction command into a template generator, creating complete transaction structures without broadcasting them. Through the `--generate-only` flag and Foundry's `cast` tool, developers can craft, analyze, and debug transactions across the EVM environment. ### Basic Command Pattern The general pattern follows this structure: ```bash paxd tx --from --evm-rpc https://public-rpc.paxeer.app/evm/reference --generate-only ``` ### Generating EVM Transaction Templates To generate an EVM transaction template, use the `evm` module with `--generate-only`. Here's an example sending HPX to another EVM address: ```bash paxd tx evm send 0xRecipientAddress 1000000000000000000 \ --gas-fee-cap=100000000000 \ --gas-limit=21000 \ --evm-rpc=https://public-rpc.paxeer.app/evm/reference \ --from=mykey \ --generate-only ``` This command returns a transaction template that you can analyze before broadcasting. The template includes all transaction details without executing the actual transaction. --- ## Analyzing EVM Transactions with Cast Once you have a transaction hash, you can use Foundry's `cast` command to inspect the transaction details: ```bash cast tx 0x5010e6600e67f04a9bc3d3b670a7c2de380b180713d9a014a5dbd76b7e2190f1 \ --rpc-url=https://public-rpc.paxeer.app/evm/reference ``` **Example Output:** ```text blockHash 0x4696d63a9a9ae88b03bcc94ccbd87f407e994b309d1dff9c0626de51ac57b76e blockNumber 130076639 from 0xAa55a16dD4E73c48C968928983c2bcC98d913d96 transactionIndex 7 effectiveGasPrice 100000000000 accessList [] chainId 125 gasLimit 2500000 hash 0x5010e6600e67f04a9bc3d3b670a7c2de380b180713d9a014a5dbd76b7e2190f1 input 0xc31d960f0000... maxFeePerGas 100000000000 maxPriorityFeePerGas 100000000000 nonce 3 to 0x000000000000000000000000000000000000100b type 2 value 0 ``` ### Additional Analysis Commands **Get transaction receipt:** ```bash cast receipt 0x5010e6600e67f04a9bc3d3b670a7c2de380b180713d9a014a5dbd76b7e2190f1 \ --rpc-url=https://public-rpc.paxeer.app/evm/reference ``` **Decode transaction input data:** ```bash cast 4byte-decode 0xc31d960f0000... ``` All template generation commands create JSON files that you can inspect, modify, and use for debugging before executing the actual transactions. --- ## RPC Consistency Always point `cast` commands to the Paxeer EVM RPC endpoint (`https://public-rpc.paxeer.app/evm/reference`), not a Cosmos RPC URL. --- ## Tracing and Revert Reasons If your endpoint supports debug RPC methods, you can retrieve full execution traces and revert reasons: ```bash # Full transaction trace cast rpc debug_traceTransaction 0xTX_HASH '{"tracer":"callTracer","timeout":"120s"}' \ --rpc-url=$EVM_RPC_URL # Simulate and trace a call without broadcasting cast rpc debug_traceCall '{"from":"0xFROM","to":"0xTO","data":"0xDATA"}' \ 'latest' '{"tracer":"callTracer","timeout":"120s"}' \ --rpc-url=$EVM_RPC_URL ``` Many public RPC endpoints disable debug methods. If disabled, run a local fork and trace there. --- ## Reproduce with a Local Fork Fork the network locally to reproduce issues deterministically and run traces even if remote debug RPC is disabled: ```bash # Start a local fork anvil --fork-url https://public-rpc.paxeer.app/evm/reference # Re-run calls against the fork cast call 0xContract "method(uint256)" 1 --rpc-url=http://127.0.0.1:8545 # Trace the call on the fork via debug RPC cast rpc debug_traceCall '{"to":"0xContract","data":"0xENCODED_DATA"}' \ 'latest' '{"tracer":"callTracer"}' --rpc-url=http://127.0.0.1:8545 ``` --- ## Event Logs and Decoding Use the receipt to inspect logs and decode with the contract ABI: ```bash # Fetch receipt JSON and view logs cast receipt 0xTX_HASH --rpc-url=$EVM_RPC_URL --json | jq '.logs' # Compute an event signature for topic[0] cast keccak "Transfer(address,address,uint256)" ``` Decode event topics/data using the contract ABI (from your repo or block explorer). Topic[0] equals the keccak of the event signature. --- ## Nonce, Balance, and Gas Diagnostics Check common failure points quickly: ```bash cast nonce 0xYourAddress --rpc-url=$EVM_RPC_URL cast balance 0xYourAddress --rpc-url=$EVM_RPC_URL cast gas-price --rpc-url=$EVM_RPC_URL cast block latest baseFeePerGas --rpc-url=$EVM_RPC_URL cast chain-id --rpc-url=$EVM_RPC_URL ``` **Checklist:** - Incorrect nonce (pending tx in mempool) - Insufficient native balance for gas - Underpriced `maxFeePerGas`/`maxPriorityFeePerGas` - Chain ID mismatch (Paxeer mainnet is `125`) --- ## Offline Workflow with --generate-only Generate, inspect, sign, and broadcast safely: ```bash # 1) Generate a template (no broadcast) paxd tx evm send 0xTO 1000000000000000000 \ --from=$WALLET_NAME --evm-rpc=$EVM_RPC_URL --generate-only > tx.json # 2) Sign offline with your key paxd tx sign tx.json --from $WALLET_NAME --chain-id $CHAIN_ID > signed.json # 3) Broadcast the signed tx paxd tx broadcast signed.json ``` Never commit signed transactions or private keys. Use environment variables and `.gitignore` for sensitive data. --- ## Storage Inspection Verify on-chain state directly when debugging state changes: ```bash cast storage 0xContract 0xSLOT --rpc-url=$EVM_RPC_URL ``` --- ## Transaction Analysis When analyzing transactions, follow these essential practices: - Always verify EVM transactions thoroughly - Use `cast` to decode input data when working with EVM transactions - Keep track of gas parameters - Monitor transaction status on the EVM layer **Analysis Tips:** ```bash # Use Foundry's cast tool for detailed transaction inspection cast tx 0x5010e6600e67f04a9bc3d3b670a7c2de380b180713d9a014a5dbd76b7e2190f1 \ --rpc-url=$EVM_RPC_URL # Get transaction receipt to verify success cast receipt 0x5010e6600e67f04a9bc3d3b670a7c2de380b180713d9a014a5dbd76b7e2190f1 \ --rpc-url=$EVM_RPC_URL # Decode transaction input data cast 4byte-decode 0xa9059cbb000000... ``` Transaction Analysis Checklist: Always verify transaction success before proceeding. Check gas usage to optimize future transactions. Use Foundry's cast tool for detailed transaction inspection. --- ## Error Handling Handle potential EVM transaction issues with proper monitoring: ```javascript try { // Check EVM transaction status const evmStatus = await checkEvmStatus(txHash); if (!evmStatus.success) { const evmError = await cast.call(['tx', txHash, '--rpc-url', evmRpcUrl]); console.error('EVM transaction failed:', evmError); } } catch (error) { console.error('Transaction analysis failed:', error); } ``` **Best Practices for Error Handling:** - Always check transaction status before proceeding - Use detailed error logging for debugging failed transactions - Implement retry logic for network-related failures - Monitor gas usage and adjust limits accordingly --- ## Security Guidelines Critical Security Requirements: Keep private keys secure and never include them in templates. Use an `.env` file or other environment variables when working with wallet keys or mnemonics. Never commit sensitive information to version control. Always verify transaction details before signing. **Environment Variable Setup:** ```bash # Create .env file for sensitive data WALLET_NAME=mykey EVM_RPC_URL=https://public-rpc.paxeer.app/evm/reference PRIVATE_KEY=your_private_key_here # Add .env to .gitignore echo ".env" >> .gitignore ``` **Secure Transaction Execution:** ```bash # Use environment variables in commands paxd tx evm send 0x1234567890abcdef... 1000000000000000000 \ --from=$WALLET_NAME \ --evm-rpc=$EVM_RPC_URL # Generate transaction template first to verify details paxd tx evm send 0x1234567890abcdef... 1000000000000000000 \ --from=$WALLET_NAME \ --evm-rpc=$EVM_RPC_URL \ --generate-only ``` --- ## Paxeer EVM vs Ethereum: Key Differences *Explore key differences between Paxeer EVM and Ethereum, including block time, finality, consensus, opcodes, state root, and gas fees.* While Paxeer features full EVM compatibility, there are some distinctions between Paxeer's EVM and Ethereum itself: | Feature | Paxeer EVM | Ethereum | | --- | --- | --- | | Blocktime | ~250 ms | ~12 s | | Transaction Throughput | 5000 TPS | ~15-30 TPS | | Finality | Instant (~250 ms) | Various commitment levels (safe, latest, justified, finalized) | | Consensus | Twin Turbo + machineRFT | Proof of Stake | | EVM Tooling Compatibility | 100% | 100% | | Execution Environment | EVM | EVM | | Address Space | Dual ECDSA-derived addresses; Bech32 (`pax1…`) + EVM-compatible address (`0x…`) | ECDSA-derived address (`0x…`) | | Chain ID | 125 | 1 | - Paxeer's Twin Turbo consensus combined with machineRFT delivers ~250ms block times and instant finality - a transaction is final as soon as its block is committed. - Paxeer achieves 5000 TPS throughput, far exceeding Ethereum's capacity. - The various commitment levels typical for Ethereum (i.e., safe, latest, justified, finalized) do not apply on Paxeer - all confirmed transactions are final. ## Opcode Differences | Opcode | Paxeer EVM | Ethereum | Notes | | --- | --- | --- | --- | | PREVRANDAO | Returns a value derived from the current block time | Returns the RANDAO mix (EIP-4399) | Not a randomness source; use an oracle/VRF. `DIFFICULTY` aliases to this. | | COINBASE | Always the global fee collector address | Block proposer (miner) address | Do not assume it is the validator address. | | BASEFEE | Returns current base fee; no burn | Returns current base fee; a portion is burned (EIP-1559) | Query `eth_gasPrice` for the live value. | | BLOCKHASH | Hash of the block header; different encoding | Keccak of the Ethereum block header | Usable for recent blocks only; values are not interchangeable across chains. | | GASLIMIT | Per-block gas limit | Per-block gas limit | Represents block gas limit in both chains. | | TIMESTAMP | Block time from consensus | Proposer-chosen block time | Same semantics; do not use as randomness. | | DIFFICULTY | Alias of PREVRANDAO | Alias of PREVRANDAO (EIP-4399) | Returns the same value as PREVRANDAO. | | Blob-related opcodes | Not supported | Supported post-Cancun (EIP-4844) | Blob transactions are not enabled on Paxeer. | ### PREVRANDAO Since Paxeer uses Twin Turbo consensus with machineRFT rather than Proof of Stake, it doesn't have the "randomness" artifact that can be set as `PREVRANDAO`'s return value. On Paxeer, `PREVRANDAO` returns a value derived from the current block time. For strong randomness needs in contract logic, use a verifiable randomness oracle (as is advised on Ethereum itself). ### COINBASE Coinbase address on Paxeer is always set to (the EVM address of) the global fee collector. ## State Root Paxeer uses an AVL-tree for data storage instead of Ethereum's Merkle Patricia Trie (MPT). The global state root is the AVL-tree root, which is not equivalent to Ethereum's MPT-based state root. ## Block Hash The block hash on Paxeer is computed based on the block header in the native consensus format and differs from Ethereum's block hash format. ## Base Fee & Tips Paxeer supports standard EVM transaction types. Query the live gas price with `eth_gasPrice` rather than hard-coding it. Excess gas beyond actual usage may not be fully refunded. Current EIP-1559 parameters can be fetched from the `paxd` CLI: ```sh paxd q params subspace evm KeyTargetGasUsedPerBlock key: KeyTargetGasUsedPerBlock subspace: evm value: '"850000"' ``` ```sh paxd q params subspace evm KeyMaxDynamicBaseFeeUpwardAdjustment key: KeyMaxDynamicBaseFeeUpwardAdjustment subspace: evm value: '"0.007500000000000000"' ``` ## Finality Paxeer has instant finality - a transaction is final as soon as its block is committed (~250 ms) - meaning that commitment levels of "safe", "latest", "justified", and "finalized" on Ethereum are all the same thing on Paxeer. ## Pending State On Ethereum the block proposer would execute its proposed block first (and update its local state) before broadcasting the proposal to others (the updated state would be marked "pending" until the node is accepted by other nodes). On Paxeer, the block proposer broadcasts first and only executes the proposal if it's accepted (i.e., every node executes the block at roughly the same time), so Paxeer does not have a window when "pending state" exists. ## Gas Model & Fees Paxeer does not implement base-fee burning. All transaction fees accrue to validators. Fees are calculated as: Transaction Fee = Gas Used x Gas Price **Practical implications** - **Simpler fee handling:** Use `gasPrice` (you can omit `maxFeePerGas` / `maxPriorityFeePerGas`). - **More stable fees:** Higher throughput reduces fee spikes during busy periods. - **Typically lower cost:** Many workloads that are costly on Ethereum become economical on Paxeer. **Useful Infos** - **Does Paxeer burn a base fee (EIP-1559)?** No. - **Who receives fees?** Validators. - **Are Paxeer fees lower?** Yes, due to higher throughput and parallel execution. ## ERC Token Standards Compatibility Paxeer's EVM fully supports the common token standards: - **ERC-20** (fungible tokens) - **ERC-721** (NFTs) - **ERC-1155** (multi-token) Existing OpenZeppelin contracts and tools work unchanged. ## Testing & Migration Checklist - Re-deploy your Solidity code to Paxeer; most contracts need no changes. - If you used SELFDESTRUCT, refactor to a soft-close pattern. - Remove EIP-1559 fee UI complexity; use a single `gasPrice` input in frontends. - If you rely on on-chain "randomness," integrate an oracle/VRF. - Size your `gasLimit` with a modest buffer (parallel execution can slightly vary estimates). --- ## Ecosystem Contracts *Comprehensive registry of smart contract addresses for projects building on Paxeer. Find verified contract addresses organized by project.* import { EcosystemContracts } from '/snippets/ecosystem-contracts.jsx'; Discover and interact with the ecosystem building on Paxeer. This registry contains smart contract addresses for projects built across Paxeer. **Work in Progress** This registry is actively maintained and expanded. We're constantly adding new projects and updating contract addresses. Check back regularly for the latest additions to the Paxeer ecosystem. --- ## How to Use This Registry - **Search by Project Name**: Use the search bar to quickly find contracts for specific projects. - **View Contract Details**: Click on the PaxScan link to see detailed information about the contract - **Bookmark This Page**: Keep this registry handy for quick access to the latest contract addresses and updates. **Add Your Project** Don't see your project listed? Want to add your contracts to this registry? **Reach out to us** and we'll help get you added! ## Contract Registry --- Always verify contract addresses independently before interacting with them. While we strive to maintain accurate information, you should always double-check addresses through official project channels. --- ## Paxeer EVM Smart Contract Development with Foundry *Develop, test, and deploy smart contracts on Paxeer EVM with Foundry examples using Forge, Cast, and ethers.js.* import { RunSnippet } from '/snippets/run-snippet.jsx'; import { SandboxEmbed } from '/snippets/sandbox-embed.jsx'; import { AddPaxeerButton } from '/snippets/add-paxeer-button.jsx'; This tutorial will guide you through setting up Foundry for Paxeer EVM development and using OpenZeppelin contracts to build secure, standardized smart contracts. We'll cover environment setup, contract creation, deployment, and show how to leverage OpenZeppelin's pre-built components with the powerful Foundry toolkit. **Deploy to Testnet First** It is highly recommended that you deploy to testnet () first and verify everything works as expected before committing to mainnet. Doing so helps you catch bugs early, avoid unnecessary gas costs, and keep your users safe. ## Try it before you install Installing Foundry and scaffolding a project takes a few minutes. If you just want to see a contract deploy to Paxeer, you can do both of these right now in the browser. First, confirm the RPC endpoints you'll put in `foundry.toml` respond: Then add Paxeer testnet to your wallet and deploy a minimal `Counter.sol` from Remix - no `forge`, no install. In Remix: compile under **Solidity Compiler**, then **Deploy & Run** with **Environment** set to **Injected Provider - MetaMask**. ## Table of Contents - [Try it before you install](#try-it-before-you-install) - [Prerequisites](#prerequisites) - [Setting Up Your Development Environment](#setting-up-your-development-environment) - [Configuring Foundry for Paxeer EVM](#configuring-foundry-for-paxeer-evm) - [Using OpenZeppelin Contracts](#using-openzeppelin-contracts) - [Creating and Deploying Smart Contracts](#creating-and-deploying-smart-contracts) - [Testing Your Smart Contracts](#testing-your-smart-contracts) - [Deploying to Paxeer Testnet and Mainnet](#deploying-to-paxeer-testnet-and-mainnet) - [Interacting with Deployed Contracts](#interacting-with-deployed-contracts) ## Prerequisites Before we begin, ensure you have the following: - [Foundry](https://book.getfoundry.sh/) installed on your system - A basic understanding of Solidity and smart contract development - A wallet with HPX tokens for gas - [Node.js](https://nodejs.org/) (v18.0.0 or later) for ethers.js interactions ## Setting Up Your Development Environment First, let's install Foundry if you haven't already. Follow the [installation guide](https://book.getfoundry.sh/getting-started/installation) or use the quick install command: ```bash curl -L https://foundry.paradigm.xyz | bash foundryup ``` Create a new Foundry project: ```bash # Create a new directory for your project mkdir paxeer-foundry-project cd paxeer-foundry-project # Initialize a new Foundry project forge init --no-git # Initialize git repository (required for installing dependencies) git init ``` This will create a standard Foundry project structure with `src/`, `test/`, and `script/` directories. ## Configuring Foundry for Paxeer EVM Create a `foundry.toml` file in your project root to configure Foundry for Paxeer networks: ```toml title="foundry.toml" [profile.default] src = "src" out = "out" libs = ["lib"] solc_version = "0.8.28" optimizer = true optimizer_runs = 200 # Paxeer testnet configuration [rpc_endpoints] paxeer_testnet = "https://evm-rpc-testnet.sei-apis.com" paxeer_mainnet = "https://evm-rpc.sei-apis.com" ``` Create a `.env` file to store your private key and other sensitive information: ```bash title=".env" PRIVATE_KEY=your_private_key_here PAXEER_TESTNET_RPC=https://evm-rpc-testnet.sei-apis.com PAXEER_MAINNET_RPC=https://evm-rpc.sei-apis.com ``` Add `.env` to your `.gitignore` file to prevent committing sensitive information such as your `PRIVATE_KEY` and potentially lose funds. ## Using OpenZeppelin Contracts OpenZeppelin provides a library of secure, tested smart contract components. Let's install OpenZeppelin contracts: ```bash forge install OpenZeppelin/openzeppelin-contracts ``` Create a `remappings.txt` file to properly map the imports: ```bash forge remappings > remappings.txt ``` ## Creating and Deploying Smart Contracts Let's create different types of smart contracts. Choose from the tabs below based on what you want to build: Let's start with a simple counter contract. Update the default `src/Counter.sol` file: ```solidity title="src/Counter.sol" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; contract Counter { uint256 public number; address public owner; event NumberChanged(uint256 newNumber, address changedBy); constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Only owner can call this function"); _; } function setNumber(uint256 newNumber) public { number = newNumber; emit NumberChanged(newNumber, msg.sender); } function increment() public { number++; emit NumberChanged(number, msg.sender); } function getCount() public view returns (uint256) { return number; } function reset() public onlyOwner { number = 0; emit NumberChanged(0, msg.sender); } } ``` Create a deployment script in `script/DeployCounter.s.sol`: ```solidity title="script/DeployCounter.s.sol" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {Script} from "forge-std/Script.sol"; import {Counter} from "../src/Counter.sol"; contract DeployCounter is Script { function run() external returns (Counter) { vm.startBroadcast(); Counter counter = new Counter(); vm.stopBroadcast(); return counter; } } ``` Create an ERC20 token using OpenZeppelin. Create `src/PaxeerToken.sol`: ```solidity title="src/PaxeerToken.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "@openzeppelin/evm/evm-general/token/ERC20/ERC20.sol"; import "@openzeppelin/evm/evm-general/access/Ownable.sol"; contract PaxeerToken is ERC20, Ownable { constructor(address initialOwner) ERC20("Paxeer Token", "HPX") Ownable(initialOwner) { // Mint 1 million tokens to the contract deployer (with 18 decimals) _mint(msg.sender, 1000000 * 10 ** decimals()); } // Function to mint new tokens (only owner) function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } // Function to burn tokens function burn(uint256 amount) public { _burn(msg.sender, amount); } // Function to burn tokens from another account (with allowance) function burnFrom(address from, uint256 amount) public { _spendAllowance(from, msg.sender, amount); _burn(from, amount); } } ``` Create a deployment script in `script/DeployPaxeerToken.s.sol`: ```solidity title="script/DeployPaxeerToken.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import {Script} from "forge-std/Script.sol"; import {PaxeerToken} from "../src/PaxeerToken.sol"; contract DeployPaxeerToken is Script { function run() external returns (PaxeerToken) { vm.startBroadcast(); PaxeerToken token = new PaxeerToken(msg.sender); vm.stopBroadcast(); return token; } } ``` Create an ERC721 NFT contract using OpenZeppelin. Create `src/PaxeerNFT.sol`: ```solidity title="src/PaxeerNFT.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "@openzeppelin/evm/evm-general/token/ERC721/ERC721.sol"; import "@openzeppelin/evm/evm-general/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/evm/evm-general/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/evm/evm-general/access/Ownable.sol"; contract PaxeerNFT is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { uint256 private _nextTokenId; string private _baseTokenURI; constructor(address initialOwner, string memory baseTokenURI) ERC721("Paxeer NFT Collection", "PAXEERNFT") Ownable(initialOwner) { _baseTokenURI = baseTokenURI; } // Function to update the base URI (only owner) function setBaseURI(string memory baseTokenURI) public onlyOwner { _baseTokenURI = baseTokenURI; } // Override the baseURI function function _baseURI() internal view override returns (string memory) { return _baseTokenURI; } function safeMint(address to, string memory uri) public onlyOwner returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); return tokenId; } // Public mint function with a fee function publicMint(string memory uri) public payable returns (uint256) { require(msg.value >= 0.01 ether, "Insufficient payment"); uint256 tokenId = _nextTokenId++; _safeMint(msg.sender, tokenId); _setTokenURI(tokenId, uri); return tokenId; } // Withdraw contract balance (only owner) function withdraw() public onlyOwner { uint256 balance = address(this).balance; require(balance > 0, "No funds to withdraw"); payable(owner()).transfer(balance); } // The following functions are overrides required by Solidity function _update(address to, uint256 tokenId, address auth) internal override(ERC721, ERC721Enumerable) returns (address) { return super._update(to, tokenId, auth); } function _increaseBalance(address account, uint128 value) internal override(ERC721, ERC721Enumerable) { super._increaseBalance(account, value); } function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } } ``` Create a deployment script in `script/DeployPaxeerNFT.s.sol`: ```solidity title="script/DeployPaxeerNFT.s.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import {Script} from "forge-std/Script.sol"; import {PaxeerNFT} from "../src/PaxeerNFT.sol"; contract DeployPaxeerNFT is Script { function run() external returns (PaxeerNFT) { string memory baseURI = "https://your-metadata-server.com/metadata/"; vm.startBroadcast(); PaxeerNFT nft = new PaxeerNFT(msg.sender, baseURI); vm.stopBroadcast(); return nft; } } ``` ## Testing Your Smart Contracts Foundry provides excellent testing capabilities. Let's create comprehensive tests for our contracts. Update `test/Counter.t.sol`: ```solidity title="test/Counter.t.sol" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {Test, console} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; contract CounterTest is Test { Counter public counter; address public owner; address public user; function setUp() public { owner = address(this); user = address(0x1); counter = new Counter(); counter.setNumber(0); } function test_Increment() public { counter.increment(); assertEq(counter.number(), 1); } function test_SetNumber() public { counter.setNumber(42); assertEq(counter.number(), 42); } function test_GetCount() public { uint256 initialCount = counter.getCount(); counter.increment(); assertEq(counter.getCount(), initialCount + 1); } function test_Reset() public { counter.setNumber(100); counter.reset(); assertEq(counter.number(), 0); } function test_OnlyOwnerCanReset() public { vm.prank(user); vm.expectRevert("Only owner can call this function"); counter.reset(); } function test_EventEmitted() public { vm.expectEmit(true, true, false, true); emit Counter.NumberChanged(42, address(this)); counter.setNumber(42); } function testFuzz_SetNumber(uint256 x) public { counter.setNumber(x); assertEq(counter.number(), x); } } ``` Create `test/PaxeerToken.t.sol`: ```solidity title="test/PaxeerToken.t.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import {Test} from "forge-std/Test.sol"; import {PaxeerToken} from "../src/PaxeerToken.sol"; contract PaxeerTokenTest is Test { PaxeerToken public token; address public owner; address public user1; address public user2; function setUp() public { owner = address(this); user1 = address(0x1); user2 = address(0x2); token = new PaxeerToken(owner); } function test_InitialSupply() public { assertEq(token.totalSupply(), 1000000 * 10 ** 18); assertEq(token.balanceOf(owner), 1000000 * 10 ** 18); } function test_Transfer() public { token.transfer(user1, 1000); assertEq(token.balanceOf(user1), 1000); assertEq(token.balanceOf(owner), 1000000 * 10 ** 18 - 1000); } function test_Mint() public { token.mint(user1, 500); assertEq(token.balanceOf(user1), 500); assertEq(token.totalSupply(), 1000000 * 10 ** 18 + 500); } function test_Burn() public { token.burn(1000); assertEq(token.totalSupply(), 1000000 * 10 ** 18 - 1000); } function test_OnlyOwnerCanMint() public { vm.prank(user1); vm.expectRevert(); token.mint(user2, 1000); } } ``` Create `test/PaxeerNFT.t.sol`: ```solidity title="test/PaxeerNFT.t.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import {Test} from "forge-std/Test.sol"; import {PaxeerNFT} from "../src/PaxeerNFT.sol"; contract PaxeerNFTTest is Test { PaxeerNFT public nft; address public owner; address public user1; string public baseURI = "https://example.com/metadata/"; function setUp() public { owner = address(this); user1 = address(0x1); nft = new PaxeerNFT(owner, baseURI); } function test_SafeMint() public { uint256 tokenId = nft.safeMint(user1, "1.json"); assertEq(nft.ownerOf(tokenId), user1); assertEq(nft.tokenURI(tokenId), string(abi.encodePacked(baseURI, "1.json"))); } function test_PublicMint() public { vm.deal(user1, 1 ether); vm.prank(user1); uint256 tokenId = nft.publicMint{value: 0.01 ether}("2.json"); assertEq(nft.ownerOf(tokenId), user1); } function test_PublicMintInsufficientPayment() public { vm.deal(user1, 0.005 ether); vm.prank(user1); vm.expectRevert("Insufficient payment"); nft.publicMint{value: 0.005 ether}("3.json"); } function test_TotalSupply() public { assertEq(nft.totalSupply(), 0); nft.safeMint(user1, "1.json"); assertEq(nft.totalSupply(), 1); } function test_Withdraw() public { vm.deal(user1, 1 ether); vm.prank(user1); nft.publicMint{value: 0.01 ether}("1.json"); uint256 contractBalance = address(nft).balance; assertEq(contractBalance, 0.01 ether); nft.withdraw(); assertEq(address(nft).balance, 0); } } ``` Run your tests with: ```bash forge test ``` For more verbose output: ```bash forge test -vvv ``` ## Deploying to Paxeer Testnet and Mainnet Now let's deploy our contracts to the Paxeer networks. You can use either Forge scripts or direct deployment commands. ### Using Forge Scripts (Recommended) Deploy to Paxeer testnet: ```bash forge script script/DeployCounter.s.sol --rpc-url $PAXEER_TESTNET_RPC --private-key $PRIVATE_KEY --broadcast ``` Deploy to Paxeer mainnet: ```bash forge script script/DeployCounter.s.sol --rpc-url $PAXEER_MAINNET_RPC --private-key $PRIVATE_KEY --broadcast ``` ### Using Direct Forge Create Commands Alternatively, you can deploy directly: ```bash # Deploy Counter to testnet forge create --rpc-url $PAXEER_TESTNET_RPC --private-key $PRIVATE_KEY src/Counter.sol:Counter # Deploy PaxeerToken to testnet forge create --rpc-url $PAXEER_TESTNET_RPC --private-key $PRIVATE_KEY src/PaxeerToken.sol:PaxeerToken --constructor-args $(cast abi-encode "constructor(address)" "YOUR_ADDRESS") # Deploy PaxeerNFT to testnet forge create --rpc-url $PAXEER_TESTNET_RPC --private-key $PRIVATE_KEY src/PaxeerNFT.sol:PaxeerNFT --constructor-args $(cast abi-encode "constructor(address,string)" "YOUR_ADDRESS" "https://your-metadata-server.com/metadata/") ``` Successful deployment will output: ```bash [⠒] Compiling... No files changed, compilation skipped Deployer: 0xYOUR_DEPLOYER_ADDRESS Deployed to: 0xYOUR_CONTRACT_ADDRESS Transaction hash: 0xYOUR_TX_HASH ``` ## Interacting with Deployed Contracts Once deployed, you can interact with your contracts using Foundry's `cast` tool or ethers.js. ### Using Cast Commands ```bash # Query the counter value cast call $CONTRACT_ADDRESS "getCount()(uint256)" --rpc-url $PAXEER_TESTNET_RPC # Increment the counter cast send $CONTRACT_ADDRESS "increment()" --private-key $PRIVATE_KEY --rpc-url $PAXEER_TESTNET_RPC # Set a specific number cast send $CONTRACT_ADDRESS "setNumber(uint256)" 42 --private-key $PRIVATE_KEY --rpc-url $PAXEER_TESTNET_RPC # Check ERC20 token balance cast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" $YOUR_ADDRESS --rpc-url $PAXEER_TESTNET_RPC # Transfer ERC20 tokens cast send $TOKEN_ADDRESS "transfer(address,uint256)" $RECIPIENT_ADDRESS 1000 --private-key $PRIVATE_KEY --rpc-url $PAXEER_TESTNET_RPC ``` ### Using ethers.js Install ethers.js to interact with your contracts programmatically: ```bash npm install ethers@latest ``` Create a Node.js script to interact with your deployed contracts: ```tsx title="interact.js" import { ethers } from 'ethers'; const privateKey = process.env.PRIVATE_KEY; const evmRpcEndpoint = process.env.PAXEER_TESTNET_RPC; const provider = new ethers.JsonRpcProvider(evmRpcEndpoint); const signer = new ethers.Wallet(privateKey, provider); // Counter contract interaction const counterAbi = ['function getCount() public view returns (uint256)', 'function increment() public', 'function setNumber(uint256 newNumber) public', 'event NumberChanged(uint256 newNumber, address changedBy)']; const counterAddress = 'YOUR_COUNTER_CONTRACT_ADDRESS'; const counterContract = new ethers.Contract(counterAddress, counterAbi, signer); // ERC20 token interaction const tokenAbi = ['function balanceOf(address owner) view returns (uint256)', 'function transfer(address to, uint256 amount) returns (bool)', 'function mint(address to, uint256 amount)', 'event Transfer(address indexed from, address indexed to, uint256 value)']; const tokenAddress = 'YOUR_TOKEN_CONTRACT_ADDRESS'; const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, signer); async function interactWithContracts() { try { // Counter interactions console.log('Current count:', await counterContract.getCount()); const incrementTx = await counterContract.increment(); await incrementTx.wait(); console.log('Incremented! New count:', await counterContract.getCount()); // Token interactions const balance = await tokenContract.balanceOf(signer.address); console.log('Token balance:', ethers.formatEther(balance)); // Transfer tokens const transferTx = await tokenContract.transfer('0xRecipientAddress', ethers.parseEther('100')); await transferTx.wait(); console.log('Tokens transferred!'); } catch (error) { console.error('Error:', error); } } interactWithContracts(); ``` Foundry generates the ABI for contracts in the `out` folder. You can use this ABI to interact with contracts from other tools like `ethers.js` or `web3.js`. --- ## EVM General Guide for Paxeer *Learn how the EVM powers Paxeer's fast, gas-efficient smart contracts using Solidity and Vyper, with code examples for both languages.* ## Overview The Ethereum Virtual Machine (EVM) is the runtime environment for smart contracts, enabling compatibility with Ethereum-based dApps. Paxeer is an EVM-compatible blockchain with ~250ms block times and 5000 TPS throughput, powered by Twin Turbo consensus and machineRFT. Here are some key points about the EVM: 1. **Turing Completeness**: The EVM is Turing complete, meaning it can execute any computable function. This allows developers to write complex smart contracts. 2. **Gas**: Transactions and contract executions on the EVM compatible network consume gas. Gas is a measure of computational work, and users pay for it in `hpx` on Paxeer networks. Gas ensures that malicious or inefficient code doesn't overload the network. 3. **Bytecode Execution**: Smart contracts are compiled into bytecode (low-level machine-readable instructions) and deployed to the EVM compatible network. The EVM executes this bytecode. ## Smart contract languages The two most popular languages for developing smart contracts on the EVM are **Solidity** and **Vyper**. ### Solidity - Object-oriented, high-level language for implementing smart contracts. - Curly-bracket language that has been most profoundly influenced by C++. - Statically typed (the type of a variable is known at compile time). - Supports: - Inheritance (you can extend other contracts). - Libraries (you can create reusable code that you can call from different contracts - like static functions in a static class in other object oriented programming languages). - Complex user-defined types. #### Example solidity contract ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >= 0.7.0; contract Coin { // The keyword "public" makes variables // accessible from other contracts address public minter; mapping (address => uint) public balances; // Events allow clients to react to specific // contract changes you declare event Sent(address from, address to, uint amount); // Constructor code is only run when the contract // is created constructor() { minter = msg.sender; } // Sends an amount of newly created coins to an address // Can only be called by the contract creator function mint(address receiver, uint amount) public { require(msg.sender == minter); require(amount < 1e60); balances[receiver] += amount; } // Sends an amount of existing coins // from any caller to an address function send(address receiver, uint amount) public { require(amount <= balances[msg.sender], "Insufficient balance."); balances[msg.sender] -= amount; balances[receiver] += amount; emit Sent(msg.sender, receiver, amount); } } ``` ### Vyper - Pythonic programming language - Strong typing - Small and understandable compiler code - Efficient bytecode generation - Deliberately has less features than Solidity with the aim of making contracts more secure and easier to audit. Vyper does not support: - Modifiers - Inheritance - Inline assembly - Function overloading - Operator overloading - Recursive calling - Infinite-length loops - Binary fixed points #### Example Vyper contract ```python # Open Auction # Auction params # Beneficiary receives money from the highest bidder beneficiary: public(address) auctionStart: public(uint256) auctionEnd: public(uint256) # Current state of auction highestBidder: public(address) highestBid: public(uint256) # Set to true at the end, disallows any change ended: public(bool) # Keep track of refunded bids so we can follow the withdraw pattern pendingReturns: public(HashMap[address, uint256]) # Create a simple auction with `_bidding_time` # seconds bidding time on behalf of the # beneficiary address `_beneficiary`. @external def __init__(_beneficiary: address, _bidding_time: uint256): self.beneficiary = _beneficiary self.auctionStart = block.timestamp self.auctionEnd = self.auctionStart + _bidding_time # Bid on the auction with the value sent # together with this transaction. # The value will only be refunded if the # auction is not won. @external @payable def bid(): # Check if bidding period is over. assert block.timestamp < self.auctionEnd # Check if bid is high enough assert msg.value > self.highestBid # Track the refund for the previous high bidder self.pendingReturns[self.highestBidder] += self.highestBid # Track new high bid self.highestBidder = msg.sender self.highestBid = msg.value # Withdraw a previously refunded bid. The withdraw pattern is # used here to avoid a security issue. If refunds were directly # sent as part of bid(), a malicious bidding contract could block # those refunds and thus block new higher bids from coming in. @external def withdraw(): pending_amount: uint256 = self.pendingReturns[msg.sender] self.pendingReturns[msg.sender] = 0 send(msg.sender, pending_amount) # End the auction and send the highest bid # to the beneficiary. @external def endAuction(): # 1. Conditions # Check if auction endtime has been reached assert block.timestamp >= self.auctionEnd # Check if this function has already been called assert not self.ended # 2. Effects self.ended = True # 3. Interaction send(self.beneficiary, self.highestBid) ``` ## Network Configuration To interact with Paxeer EVM, configure your tools with the following: | Property | Value | | --- | --- | | **RPC URL** | `https://public-rpc.paxeer.app/evm/reference` | | **Chain ID** | `125` | | **Currency Symbol** | `hpx` | | **Block Explorer** | [paxscan.io](https://paxscan.io) | ### Hardhat Configuration ```javascript // hardhat.config.js module.exports = { networks: { paxeer: { url: "https://public-rpc.paxeer.app/evm/reference", chainId: 125, accounts: [process.env.PRIVATE_KEY], }, }, }; ``` ### Foundry Configuration ```bash # Deploy with Foundry forge create --rpc-url https://public-rpc.paxeer.app/evm/reference \ --private-key $PRIVATE_KEY \ src/MyContract.sol:MyContract ``` For more details on deploying with Hardhat or Foundry, see the [Hardhat](/evm/evm-hardhat) and [Foundry](/evm/evm-foundry) guides. --- ## Paxeer EVM Development with Hardhat *Learn to configure Hardhat 3 for Paxeer EVM development, integrate OpenZeppelin contracts, and deploy to testnet or mainnet.* import { RunSnippet } from '/snippets/run-snippet.jsx'; import { SandboxEmbed } from '/snippets/sandbox-embed.jsx'; import { AddPaxeerButton } from '/snippets/add-paxeer-button.jsx'; This tutorial will guide you through setting up Hardhat for Paxeer EVM development and using OpenZeppelin contracts to build secure, standardized smart contracts. We'll cover environment setup, contract creation, deployment, and show how to leverage OpenZeppelin's pre-built components. **Deploy to Testnet First** It is highly recommended that you deploy to testnet () first and verify everything works as expected before committing to mainnet. Doing so helps you catch bugs early, avoid unnecessary gas costs, and keep your users safe. ## Try it before you install Setting up the full toolchain takes a few minutes. If you just want to see a contract deploy to Paxeer, you can do both of these right now in the browser. First, confirm the RPC endpoints you'll put in `hardhat.config` respond: Then add Paxeer testnet to your wallet and deploy a minimal `Counter.sol` from Remix - no Node, no install. In Remix: compile under **Solidity Compiler**, then **Deploy & Run** with **Environment** set to **Injected Provider - MetaMask**. ## Table of Contents - [Try it before you install](#try-it-before-you-install) - [Prerequisites](#prerequisites) - [Setting Up Your Development Environment](#setting-up-your-development-environment) - [Configuring Hardhat for Paxeer EVM](#configuring-hardhat-for-paxeer-evm) - [Using OpenZeppelin Contracts](#using-openzeppelin-contracts) - [Creating and Deploying an ERC20 Token, ERC721 NFT or an Upgradeable UUPS Token ](#creating-and-deploying-an-erc20-token-erc721-nft-or-an-upgradeable-uups-token) - [Testing Your Smart Contracts](#testing-your-smart-contracts) - [Deploying to Paxeer Testnet and Mainnet](#deploying-to-paxeer-testnet-and-mainnet) ## Prerequisites Before we begin, ensure you have the following installed: - [Node.js](https://nodejs.org/) (v18.0.0 or later) - [npm](https://www.npmjs.com/) (v7.0.0 or later) or [yarn](https://yarnpkg.com/) - A code editor (VS Code recommended) ## Setting Up Your Development Environment Let's create a new project and set up Hardhat: ```bash # Create a new directory for your project mkdir paxeer-hardhat-project cd paxeer-hardhat-project # Scaffold a Hardhat 3 project (ESM + TypeScript) npx hardhat --init ``` When prompted, choose **Hardhat 3** and **a TypeScript project using Mocha and Ethers.js**, then follow the prompts. This generates an ESM project (`"type": "module"` in `package.json`) with `@nomicfoundation/hardhat-toolbox-mocha-ethers`, `ethers`, Hardhat Ignition, TypeScript, and a ready-to-use `tsconfig.json`. Then add the OpenZeppelin contract library: ```bash npm install @openzeppelin/evm/evm-general ``` Prefer a non-interactive setup (CI or scripted environments)? Do the same thing by hand - no prompts: ```bash npm init -y npm pkg set type=module npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox-mocha-ethers @openzeppelin/evm/evm-general typescript @types/node ``` The `@nomicfoundation/hardhat-toolbox-mocha-ethers` bundle pulls in `ethers`, Ignition, Mocha/Chai, and the keystore, network-helpers, verify, and typechain plugins - so this single install covers everything in this guide. If you hit peer-dependency errors, re-run with `--legacy-peer-deps`. ## Configuring Hardhat for Paxeer EVM Next, we'll need to configure Hardhat to work with the Paxeer EVM. Update your `hardhat.config.ts` file: ```typescript title="hardhat.config.ts" import type { HardhatUserConfig } from 'hardhat/config'; import { configVariable } from 'hardhat/config'; import hardhatToolboxMochaEthers from '@nomicfoundation/hardhat-toolbox-mocha-ethers'; const config: HardhatUserConfig = { // Hardhat 3 loads plugins from an explicit array - no side-effect `import` statements plugins: [hardhatToolboxMochaEthers], solidity: { version: '0.8.28', settings: { optimizer: { enabled: true, runs: 200 } } }, networks: { // Paxeer testnet () paxeertestnet: { type: 'http', chainType: 'l1', url: 'https://evm-rpc-testnet.sei-apis.com', accounts: [configVariable('PAXEER_PRIVATE_KEY')], chainId: 1328 }, // Paxeer mainnet (hyperpax_125-1) paxeermaintnet: { type: 'http', chainType: 'l1', url: 'https://evm-rpc.sei-apis.com', accounts: [configVariable('PAXEER_PRIVATE_KEY')], chainId: 1329 } } }; export default config; ``` Hardhat 3 is ESM-first and requires `"type": "module"` in `package.json` (the scaffold sets this for you). Each network needs an explicit `type: 'http'`, and plugins load via the `plugins` array rather than the Hardhat 2 side-effect `import '@nomicfoundation/hardhat-toolbox'`. A local simulated network is provided automatically - no `hardhat`/`localhost` entry required. Hardhat 3 reads secrets through `configVariable(...)`, backed by an encrypted keystore - so your key is never stored in a plaintext file. Set it once: ```bash npx hardhat keystore set PAXEER_PRIVATE_KEY ``` You'll be prompted to paste the private key of the account you deploy from; Hardhat stores it encrypted on your machine. For CI, export a `PAXEER_PRIVATE_KEY` environment variable instead - `configVariable` falls back to it. Use a dedicated, throwaway deploy key funded with only what you need - never a personal wallet holding real funds. ## Using OpenZeppelin Contracts OpenZeppelin provides a library of secure, tested smart contract components that you can use to build your applications. The `@openzeppelin/evm/evm-general` package was already installed during setup, so you're ready to import its contracts directly. ## Creating and Deploying an ERC20 Token, ERC721 NFT or an Upgradeable UUPS Token Let's create a simple ERC20 token using OpenZeppelin contracts. Create a new file in the `contracts` directory called `PaxeerToken.sol`: ```solidity title="contracts/PaxeerToken.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "@openzeppelin/evm/evm-general/token/ERC20/ERC20.sol"; import "@openzeppelin/evm/evm-general/access/Ownable.sol"; contract PaxeerToken is ERC20, Ownable { constructor(address initialOwner) ERC20("Paxeer Token", "HPX") Ownable(initialOwner) { // Mint 1 million tokens to the contract deployer (with 18 decimals) _mint(msg.sender, 1000000 * 10 ** decimals()); } // Function to mint new tokens (only owner) function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } // Function to burn tokens function burn(uint256 amount) public { _burn(msg.sender, amount); } } ``` Now, create a deployment script in the `ignition/modules` directory called `deploy-paxeer-token.ts`: ```typescript title="ignition/modules/deploy-paxeer-token.ts" import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'; export default buildModule('PaxeerTokenModule', (m) => { const deployer = m.getAccount(0); const paxeerToken = m.contract('PaxeerToken', [deployer]); return { paxeerToken }; }); ``` To deploy the token to the Paxeer testnet: ```bash npx hardhat ignition deploy ignition/modules/deploy-paxeer-token.ts --network paxeertestnet ``` Now, let's create an ERC721 NFT contract. Create a new file `PaxeerNFT.sol` in the `contracts` directory: ```solidity title="contracts/PaxeerNFT.sol" // SPDX-License-Identifier: MIT // Compatible with OpenZeppelin Contracts ^5.0.0 pragma solidity ^0.8.22; import {ERC721} from "@openzeppelin/evm/evm-general/token/ERC721/ERC721.sol"; import {ERC721Burnable} from "@openzeppelin/evm/evm-general/token/ERC721/extensions/ERC721Burnable.sol"; import {ERC721Enumerable} from "@openzeppelin/evm/evm-general/token/ERC721/extensions/ERC721Enumerable.sol"; import {ERC721Pausable} from "@openzeppelin/evm/evm-general/token/ERC721/extensions/ERC721Pausable.sol"; import {ERC721URIStorage} from "@openzeppelin/evm/evm-general/token/ERC721/extensions/ERC721URIStorage.sol"; import {Ownable} from "@openzeppelin/evm/evm-general/access/Ownable.sol"; contract PaxeerNFT is ERC721, ERC721Enumerable, ERC721URIStorage, ERC721Pausable, Ownable, ERC721Burnable { uint256 private _nextTokenId; // Base URI for metadata string private _baseTokenURI; constructor(address initialOwner, string memory baseTokenURI) ERC721("Paxeer NFT Collection", "PAXEERNFT") Ownable(initialOwner) { _baseTokenURI = baseTokenURI; } // Function to update the base URI (only owner) function setBaseURI(string memory baseTokenURI) public onlyOwner { _baseTokenURI = baseTokenURI; } // Override the baseURI function function _baseURI() internal view override returns (string memory) { return _baseTokenURI; } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); } function safeMint(address to, string memory uri) public onlyOwner returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); return tokenId; } // The following functions are overrides required by Solidity. function _update(address to, uint256 tokenId, address auth) internal override(ERC721, ERC721Enumerable, ERC721Pausable) returns (address) { return super._update(to, tokenId, auth); } function _increaseBalance(address account, uint128 value) internal override(ERC721, ERC721Enumerable) { super._increaseBalance(account, value); } function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } } ``` Create a deployment script `deploy-paxeer-nft.ts`: ```typescript title="scripts/deploy-paxeer-nft.ts" import { network } from 'hardhat'; // Hardhat 3 exposes ethers through a network connection rather than a global import const { ethers } = await network.create(); const [deployer] = await ethers.getSigners(); console.log('Deploying contracts with the account:', deployer.address); // Base URI for your NFT metadata const baseURI = 'https://your-metadata-server.com/metadata/'; const PaxeerNFT = await ethers.getContractFactory('PaxeerNFT'); const paxeerNFT = await PaxeerNFT.deploy(deployer.address, baseURI); await paxeerNFT.waitForDeployment(); console.log('PaxeerNFT deployed to:', await paxeerNFT.getAddress()); // Mint an example NFT console.log('Minting an example NFT...'); const mintTx = await paxeerNFT.safeMint(deployer.address, '0.json'); const receipt = await mintTx.wait(); // _nextTokenId starts at 0, so the first minted token has ID 0. // Read the actual ID from the Transfer event: const transferEvent = receipt.logs.map((log) => paxeerNFT.interface.parseLog(log)).find((parsed) => parsed?.name === 'Transfer'); console.log('NFT minted with ID:', transferEvent?.args.tokenId.toString() ?? 'unknown (no Transfer event in receipt)'); ``` Deploy the NFT contract to the Paxeer testnet: ```bash npx hardhat run scripts/deploy-paxeer-nft.ts --network paxeertestnet ``` Upgradeable contracts allow you to modify the contract's logic after deployment without changing the contract address, which is crucial for fixing bugs or adding new features. The UUPS (Universal Upgradeable Proxy Standard) pattern is a popular way to implement upgradeability. For upgradeable contracts, also install the upgradeable variant of the OpenZeppelin library (the `@openzeppelin/evm/evm-general` package you installed earlier supplies the ERC1967 proxy): ```bash npm install @openzeppelin/evm/evm-general-upgradeable ``` OpenZeppelin's `@openzeppelin/hardhat-upgrades` plugin is built for Hardhat 2 and does not register with Hardhat 3's `plugins` array. So this guide deploys the proxy directly using the ERC1967 standard and upgrades through UUPS `upgradeToAndCall` - no extra plugin, fully Hardhat 3-native. The tradeoff: you don't get the plugin's automatic storage-layout safety checks, so make sure each new version only **appends** state variables. You can validate layouts separately with [`@openzeppelin/upgrades-core`](https://www.npmjs.com/package/@openzeppelin/upgrades-core). No `hardhat.config.ts` changes are needed beyond the configuration shown earlier. Now, let's create an upgradeable ERC20 token. Create `contracts/UpgradeablePaxeerToken.sol`: ```solidity title="contracts/UpgradeablePaxeerToken.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "@openzeppelin/evm/evm-general-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/evm/evm-general-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/evm/evm-general-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/evm/evm-general-upgradeable/proxy/utils/UUPSUpgradeable.sol"; contract UpgradeablePaxeerToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable { /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(address initialOwner) initializer public { __ERC20_init("Upgradeable Paxeer Token", "uhpx"); __Ownable_init(initialOwner); // Mint 1 million tokens to the initializer (deployer) _mint(initialOwner, 1000000 * 10 ** decimals()); } // Function to mint new tokens (only owner) function mint(address to, uint256 amount) virtual public onlyOwner { _mint(to, amount); } // Required for UUPS upgradeability function _authorizeUpgrade(address newImplementation) internal onlyOwner override {} // OPTIONAL: Add a version identifier (useful for tracking upgrades) function version() public virtual pure returns (string memory) { return "V1"; } } ``` Note the `Initializable` base, the `initializer` modifier, the `__ERC20_init` / `__Ownable_init` calls, and the `_authorizeUpgrade` override - these are what make the contract safe to run behind a proxy. (OpenZeppelin v5's `UUPSUpgradeable` is stateless, so there is no `__UUPSUpgradeable_init` to call.) Add a thin, named ERC1967 proxy so Hardhat emits an artifact you can deploy by name. Create `contracts/PaxeerTokenProxy.sol`: ```solidity title="contracts/PaxeerTokenProxy.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "@openzeppelin/evm/evm-general/proxy/ERC1967/ERC1967Proxy.sol"; contract PaxeerTokenProxy is ERC1967Proxy { constructor(address implementation, bytes memory _data) ERC1967Proxy(implementation, _data) {} } ``` Now create a deployment script `scripts/deploy-upgradeable-token.ts`. It deploys the implementation, then deploys the proxy with the `initialize` call encoded as constructor data so initialization happens atomically: ```typescript title="scripts/deploy-upgradeable-token.ts" import { network } from 'hardhat'; const { ethers } = await network.create(); const [deployer] = await ethers.getSigners(); console.log('Deploying contracts with the account:', deployer.address); // 1. Deploy the implementation contract const UpgradeablePaxeerToken = await ethers.getContractFactory('UpgradeablePaxeerToken'); const implementation = await UpgradeablePaxeerToken.deploy(); await implementation.waitForDeployment(); // 2. Deploy the ERC1967 proxy, running initialize(deployer) atomically const initData = UpgradeablePaxeerToken.interface.encodeFunctionData('initialize', [deployer.address]); const PaxeerTokenProxy = await ethers.getContractFactory('PaxeerTokenProxy'); const proxy = await PaxeerTokenProxy.deploy(await implementation.getAddress(), initData); await proxy.waitForDeployment(); // 3. Interact with the token through the proxy address from here on const token = await ethers.getContractAt('UpgradeablePaxeerToken', await proxy.getAddress()); console.log('Proxy deployed to:', await token.getAddress()); console.log('Implementation:', await implementation.getAddress()); console.log('Version:', await token.version()); // V1 ``` Deploy this to the testnet: ```bash npx hardhat run scripts/deploy-upgradeable-token.ts --network paxeertestnet ``` **Upgrading the Contract** Let's say you want to add a new feature or fix a bug. Create a new version of the contract, `contracts/UpgradeablePaxeerTokenV2.sol`: ```solidity title="contracts/UpgradeablePaxeerTokenV2.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "./UpgradeablePaxeerToken.sol"; // Import the V1 contract contract UpgradeablePaxeerTokenV2 is UpgradeablePaxeerToken { // Add a new state variable (ensure it doesn't clash with V1 storage layout) uint256 public totalMintedSinceV2; // Override the mint function to track new mints function mint(address to, uint256 amount) public override onlyOwner { super.mint(to, amount); totalMintedSinceV2 += amount; // Add V2 logic } // Override the version function function version() public pure override returns (string memory) { return "V2"; } // IMPORTANT: V2 does not need its own initializer or constructor for upgrades. // The state from V1 is preserved. } ``` Now, create an upgrade script `scripts/upgrade-token.ts`. **Replace `PROXY_ADDRESS` with the address printed when you deployed the proxy.** ```typescript title="scripts/upgrade-token.ts" import { network } from 'hardhat'; const { ethers } = await network.create(); // !! REPLACE WITH YOUR PROXY ADDRESS from the deploy step !! const PROXY_ADDRESS = '0xYOUR_PROXY_CONTRACT_ADDRESS_HERE'; const [deployer] = await ethers.getSigners(); console.log('Upgrading contract with the account:', deployer.address); // 1. Deploy the new implementation const UpgradeablePaxeerTokenV2 = await ethers.getContractFactory('UpgradeablePaxeerTokenV2'); const v2Implementation = await UpgradeablePaxeerTokenV2.deploy(); await v2Implementation.waitForDeployment(); // 2. Point the proxy at the new implementation (UUPS upgrades are owner-gated) const token = await ethers.getContractAt('UpgradeablePaxeerTokenV2', PROXY_ADDRESS); await (await token.upgradeToAndCall(await v2Implementation.getAddress(), '0x')).wait(); console.log('Upgrade complete. Proxy remains at:', PROXY_ADDRESS); console.log('New implementation:', await v2Implementation.getAddress()); console.log('Contract version:', await token.version()); // V2 ``` Run the upgrade script: ```bash npx hardhat run scripts/upgrade-token.ts --network paxeertestnet ``` You have now successfully deployed and upgraded a UUPS contract on the Paxeer network using Hardhat and OpenZeppelin! ## Testing Your Smart Contracts Hardhat makes it easy to test your contracts before deploying them. Create a test file `test/paxeer-token-test.ts`: ```typescript title="test/paxeer-token-test.ts" import { expect } from 'chai'; import { network } from 'hardhat'; describe('PaxeerToken', function () { let ethers; let paxeerToken; let owner; let addr1; let addr2; beforeEach(async function () { // Hardhat 3 exposes ethers through a network connection ({ ethers } = await network.create()); // Get signers [owner, addr1, addr2] = await ethers.getSigners(); // Deploy the token paxeerToken = await ethers.deployContract('PaxeerToken', [owner.address]); }); describe('Deployment', function () { it('Should set the right owner', async function () { expect(await paxeerToken.owner()).to.equal(owner.address); }); it('Should assign the total supply of tokens to the owner', async function () { const ownerBalance = await paxeerToken.balanceOf(owner.address); const totalSupply = await paxeerToken.totalSupply(); expect(totalSupply).to.equal(ownerBalance); }); it('Should have correct name and symbol', async function () { expect(await paxeerToken.name()).to.equal('Paxeer Token'); expect(await paxeerToken.symbol()).to.equal('HPX'); }); }); describe('Transactions', function () { it('Should transfer tokens between accounts', async function () { // Transfer 50 tokens from owner to addr1 await paxeerToken.transfer(addr1.address, 50); expect(await paxeerToken.balanceOf(addr1.address)).to.equal(50); // Transfer 50 tokens from addr1 to addr2 await paxeerToken.connect(addr1).transfer(addr2.address, 50); expect(await paxeerToken.balanceOf(addr2.address)).to.equal(50); }); it("Should fail if sender doesn't have enough tokens", async function () { const initialOwnerBalance = await paxeerToken.balanceOf(owner.address); // Try to send 1 token from addr1 (0 tokens) to owner await expect(paxeerToken.connect(addr1).transfer(owner.address, 1)).to.be.reverted; // Owner balance shouldn't change expect(await paxeerToken.balanceOf(owner.address)).to.equal(initialOwnerBalance); }); }); describe('Minting', function () { it('Should allow owner to mint new tokens', async function () { await paxeerToken.mint(addr1.address, 1000); expect(await paxeerToken.balanceOf(addr1.address)).to.equal(1000); }); it('Should not allow non-owners to mint', async function () { await expect(paxeerToken.connect(addr1).mint(addr1.address, 1000)).to.be.reverted; }); }); }); ``` Run your tests with: ```bash npx hardhat test ``` ## Deploying to Paxeer Testnet and Mainnet Once you've tested your contracts, you can deploy them to the Paxeer testnet or mainnet. To deploy, you'll need: 1. HPX tokens in your wallet for gas 2. Your private key stored in the encrypted keystore (`npx hardhat keystore set PAXEER_PRIVATE_KEY`) - or exported as a `PAXEER_PRIVATE_KEY` environment variable in CI Deploy to the testnet: ```bash npx hardhat ignition deploy ignition/modules/deploy-paxeer-token.ts --network paxeertestnet ``` Deploy to the mainnet (only when you're ready for production): ```bash npx hardhat ignition deploy ignition/modules/deploy-paxeer-token.ts --network paxeermaintnet ``` --- ## Verify Contracts *Learn how to verify deployed contracts on Paxeer using Sourcify, Foundry, Hardhat, and the Remix plugin for full transparency.* import { SandboxEmbed } from '/snippets/sandbox-embed.jsx'; import { AddPaxeerButton } from '/snippets/add-paxeer-button.jsx'; Verify your deployed contract using flattened source code, JSON input, Sourcify and more. Verifying your deployed contract ensures transparency and trust by making the source code publicly available and verifiable. ## Benefits of Verification - **Transparency**: Publicly available source code. - **Trust**: Community can verify the contract's functionality. - **Security**: Greater confidence in the contract's integrity. ## Sourcify [Sourcify](https://sourcify.dev/) provides decentralized, open-source contract verification. Paxeer has full Sourcify support on **mainnet** (chain ID 125). Sourcify performs byte-by-byte verification by recompiling your source code with the exact same compiler settings and comparing the output against the on-chain bytecode. No API key is required. ### Verify with Foundry After deploying your contract with Foundry, verify it on Sourcify: ```bash forge verify-contract \ --verifier sourcify \ --chain-id 125 \ \ ``` **Example:** ```bash forge verify-contract \ --verifier sourcify \ --chain-id 125 \ 0x1234567890abcdef1234567890abcdef12345678 \ src/Counter.sol:Counter ``` To check verification status: ```bash forge verify-check \ --verifier sourcify \ --chain-id 125 \ ``` You can also deploy and verify in a single step using `forge create`: ```bash forge create src/Counter.sol:Counter \ --rpc-url https://public-rpc.paxeer.app/evm/reference \ --private-key $PRIVATE_KEY \ --verify \ --verifier sourcify \ --chain-id 125 ``` If you need to pass constructor arguments, append them after the contract path. For example: `forge create src/Token.sol:Token --constructor-args "MyToken" "MTK" 18` ### Verify with Hardhat The [`hardhat-verify`](https://hardhat.org/docs/learn-more/smart-contract-verification) plugin supports Sourcify out of the box with no additional configuration or API key. #### 1. Install dependencies ```bash npm install --save-dev hardhat @nomicfoundation/hardhat-verify ``` #### 2. Configure `hardhat.config.ts` ```ts import { defineConfig, configVariable } from "hardhat/config"; import hardhatVerify from "@nomicfoundation/hardhat-verify"; export default defineConfig({ networks: { paxeer_mainnet: { type: "http", chainId: 125, url: "https://public-rpc.paxeer.app/evm/reference", accounts: [configVariable("PAXEER_PRIVATE_KEY")], }, }, solidity: { compilers: [ { version: "0.8.28", settings: { optimizer: { enabled: true, runs: 200 }, }, }, ], }, plugins: [hardhatVerify], }); ``` Sourcify verification is enabled by default in `hardhat-verify`. No `verify` config block is needed for Sourcify. #### 3. Deploy ```bash npx hardhat ignition deploy ignition/modules/Counter.ts --network paxeer_mainnet ``` #### 4. Verify with Sourcify ```bash npx hardhat verify sourcify --network paxeer_mainnet ``` If your contract has constructor arguments: ```bash npx hardhat verify sourcify --network paxeer_mainnet "arg1" "arg2" ``` Running `npx hardhat verify` without a subtask will attempt verification on all enabled providers (Etherscan, Blockscout, and Sourcify) simultaneously. ### Verify via Sourcify UI You can also verify contracts directly through the [Sourcify web interface](https://verify.sourcify.dev/): 1. Go to [verify.sourcify.dev](https://verify.sourcify.dev/) 2. Select **Paxeer** (chain ID 125) 3. Enter your contract address 4. Upload your source files or standard JSON input 5. Click **Verify** ## Remix Contract Verification Plugin Remix IDE offers an automated contract verification solution through its Contract Verification plugin, which integrates with both Sourcify and Etherscan verification services. Don't have a contract deployed yet? Spin up Remix with a sample `Counter.sol`, deploy it to Paxeer, then follow the steps below to verify it. ### Setup and Configuration 1. **Activate the Plugin**: In Remix IDE, navigate to the Plugin Manager and activate the **Contract Verification** plugin. 2. **Configure Network Settings**: - Search for and select **Paxeer** as your blockchain - Select **Mainnet** (chain ID 125) 3. **Enable Verification Services**: Activate both verification methods: - ✅ **Sourcify** - ✅ **Etherscan** 4. **Etherscan Configuration**: Configure the following settings: - **API Key**: Obtain from [paxscan.io](https://paxscan.io) - **URL**: `https://paxscan.io` ### Deploy and Verify Once configured, deploy your contract through Remix as usual. The Contract Verification plugin will automatically verify your contract upon successful deployment, submitting to both Sourcify and PaxScan simultaneously. Verification status will be displayed in the plugin interface, and your verified contract will be publicly viewable on PaxScan explorer. ## Verify via 0xngmi Etherscan Verification (third-party) You can batch-verify a contract that is already verified on another chain using the community tool [Etherscan Verification by 0xngmi](https://etherscan-verification.0xngmi.com/). This copies the verified source from a "source chain" and submits it to [PaxScan](https://paxscan.io), as long as the bytecode and compiler settings match. Prerequisite: The contract is already verified on at least one supported chain (same compiler version, optimization runs, constructor args, and library addresses as on Paxeer). Steps: 1. Open [Etherscan Verification by 0xngmi](https://etherscan-verification.0xngmi.com/). 2. In "Source Contract", paste the address of the contract that is already verified and select its chain. 3. In "Target Contracts", add your Paxeer deployment address and select "Paxeer" as the target chain. 4. Click "Verify Contracts" and wait for the result. 5. Confirm the result on [PaxScan](https://paxscan.io) by visiting the contract page and checking the Contract tab. If verification fails with a bytecode mismatch, re-check compiler version, optimization runs, constructor arguments, and linked library addresses; they must be identical to the deployment on Paxeer. --- ## Using the OpenZeppelin Contract Wizard with Paxeer EVM *Generate secure, standard-compliant smart contracts for Paxeer EVM using the OpenZeppelin Contract Wizard, then deploy directly from your browser.* import { SandboxEmbed } from '/snippets/sandbox-embed.jsx'; import { AddPaxeerButton } from '/snippets/add-paxeer-button.jsx'; The OpenZeppelin Contract Wizard is a powerful interactive tool that simplifies the creation of secure, standard-compliant smart contracts for Paxeer EVM. It provides a user-friendly interface for generating production-ready contract code based on OpenZeppelin's battle-tested libraries. Explore and create your custom smart contracts using the embedded wizard below.