Architecture & Implementation Roadmap for Passet Hub
Including USDT Integration Research | Prepared by Manus AI | November 2025
Goal: Implement HTTP-native payments on Passet Hub using the X402 protocol, enabling seamless programmatic payments for AI agents, APIs, and web services using USDT and DOT tokens.
The X402 protocol represents a revolutionary approach to internet payments by leveraging the HTTP 402 "Payment Required" status code to enable instant, programmatic payments without registration, OAuth, or complex authentication flows. Developed by Coinbase and supported by major infrastructure providers including Cloudflare, X402 provides a blockchain-agnostic payment standard that works seamlessly with existing HTTP infrastructure.
This report presents a comprehensive architecture and implementation roadmap for deploying X402 payments on Passet Hub, Polkadot's EVM-compatible smart contract platform. The implementation faces unique challenges due to Passet Hub's PolkaVM architecture and the lack of native EIP-3009 support in standard USDT. Through extensive research and analysis, we have identified viable paths forward including deploying custom EIP-3009 compatible tokens and building a self-hosted facilitator adapted for PolkaVM's constraints.
Standard Tether USDT (0xdac17f958d2ee523a2206206994597c13d831ec7) does NOT implement EIP-3009 and cannot be used directly with X402. A custom implementation is required.
| Component | Status | Timeline |
|---|---|---|
| Custom USDT Token | Design Ready | Weeks 1-2 |
| Self-Hosted Facilitator | Adaptation Required | Weeks 1-2 |
| Payment Escrow Contract | Planned | Weeks 3-4 |
| HTTP 402 Server | Planned | Weeks 3-4 |
| Frontend & UX | Planned | Weeks 5-6 |
X402 is an open standard for internet-native payments built around the HTTP 402 "Payment Required" status code. The protocol enables users and autonomous agents to pay for resources via API without requiring registration, email addresses, OAuth flows, or complex cryptographic operations. By leveraging standard HTTP headers and blockchain settlement, X402 provides a seamless payment experience that feels natural to both Web2 and Web3 developers.
| Principle | Description | Benefit |
|---|---|---|
| Zero Fees | Protocol charges 0% fees for merchants and customers | Maximum value retention, no rent extraction |
| Instant Settlement | Payments settle in ~6 seconds at blockchain speed | Real-time commerce without T+2 delays |
| Blockchain Agnostic | Not tied to any specific blockchain or token | Flexibility and future-proofing |
| Frictionless | As little as 1 line of middleware code | Rapid integration and deployment |
| Web Native | Works with existing HTTP stacks via headers | No infrastructure overhaul required |
Enable AI agents to autonomously pay for API calls, data access, and computational resources. Agents can operate without human intervention, making micro-payments for each request.
Allow content creators to charge per-article, per-video, or per-download without subscriptions. Users pay only for what they consume, with instant settlement to creators.
Implement pay-per-use pricing for APIs without complex billing systems. Each API call includes a micro-payment, eliminating the need for monthly subscriptions or credit card processing.
Enable IoT devices to pay for services automatically. Smart devices can purchase data, storage, or computational resources as needed without human intervention.
| Component | Role | Responsibilities |
|---|---|---|
| Client | Payment initiator | Create payment signatures, submit requests |
| Server | Resource provider | Challenge for payment, verify signatures, deliver resources |
| Facilitator | Payment processor | Validate payments, submit to blockchain, confirm settlement |
| Blockchain | Settlement layer | Execute token transfers, provide finality |
| Token Contract | Payment medium | Handle EIP-3009 transferWithAuthorization |
The X402 protocol supports three facilitator deployment models, each with different trade-offs in terms of control, complexity, and network support. The facilitator is responsible for payment verification and blockchain settlement, acting as a bridge between the HTTP layer and the blockchain layer.
| Model | Networks Supported | Production Ready | Requirements | Passet Hub Compatible |
|---|---|---|---|---|
| CDP-Hosted | Base, Base Sepolia, Solana, Solana Devnet | Yes | CDP API keys | No |
| Community-Maintained | Base Sepolia, Solana Devnet (testnet only) | No | None | No |
| Self-Hosted | Any EVM or Solana network | Yes | RPC endpoint, gas wallet | Yes |
Since Passet Hub is not currently supported by CDP-hosted or community-maintained facilitators, the self-hosted facilitator model is the only viable option. This provides full control and the ability to implement PolkaVM-specific adaptations.
All X402 facilitators require EIP-3009 compatible tokens on their supported networks. EIP-3009 is a standard that enables gasless token transfers through signature-based authorization, eliminating the need for separate approval transactions. This is critical for X402's seamless payment experience.
// Transfer tokens with signature authorization
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
// Receive tokens with signature authorization
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
// EIP-712 domain separator for signature verification
function DOMAIN_SEPARATOR() external view returns (bytes32);
// Token metadata for EIP-712
function name() external view returns (string memory);
function version() external view returns (string memory);
The facilitator performs several critical verification steps before submitting transactions to the blockchain. This multi-layered validation ensures payment integrity and prevents common attack vectors.
| Verification Step | Purpose | Failure Action |
|---|---|---|
| Signature Validation | Ensure payment authorized by token holder | Reject payment, return error |
| Amount Verification | Confirm payment amount matches requirement | Reject payment, return error |
| Token Check | Validate correct token contract used | Reject payment, return error |
| Expiration Check | Ensure payment within valid time window | Reject payment, return error |
| Nonce Validation | Prevent replay attacks | Reject payment, return error |
| Balance Check | Verify sender has sufficient tokens | Reject payment, return error |
Through comprehensive research including source code review and documentation analysis, we have identified critical limitations with standard Tether USDT that prevent direct X402 integration.
Standard Tether USDT does NOT implement EIP-3009. The contract was deployed in 2017 using Solidity 0.4.17, before EIP-3009 existed. It lacks all required functions for X402 integration.
| Property | Value |
|---|---|
| Contract Address | 0xdac17f958d2ee523a2206206994597c13d831ec7 |
| Solidity Version | 0.4.17 (from 2017) |
| EIP-3009 Support | No |
| EIP-2612 (Permit) | No |
| transferWithAuthorization | Not Implemented |
| DOMAIN_SEPARATOR | Not Implemented |
Research has identified several modern USDT implementations that DO support EIP-3009, providing potential templates for Passet Hub deployment.
Status: Production-ready, OpenZeppelin audited (January 2025)
Features: Full EIP-3009 support, EIP-2612 (permit), modern Solidity, security-focused
Use Case: Deployed on Arbitrum as USDT upgrade
Advantage: Audited by industry leader, proven in production
Challenge: May require adaptation for PolkaVM constraints
Status: Open source, designed for L2 networks
Features: EIP-3009 compatible, EIP-1271 smart account support, optimized for L2
Use Case: Shared bridge infrastructure for L2 deployments
Advantage: Designed for L2 constraints, similar to PolkaVM
Challenge: May still require PolkaVM-specific modifications
Status: Production on Polygon
EIP-3009 Support: No
Note: Explicitly documented as NOT compatible with EIP-3009, similar to Polygon USDC
Conclusion: Not suitable as a template
Deploy a custom USDT implementation with full EIP-3009 support, optimized for PolkaVM constraints.
Create a wrapper contract around standard USDT that adds EIP-3009 functionality through a deposit/withdraw mechanism.
Since DOT is native to Polkadot, create an ERC-20 wrapped DOT with EIP-3009 support for X402 payments.
For X402 integration on Passet Hub, we recommend deploying a custom USDT implementation based on USDT0 or txfusion/tether-l2, simplified for PolkaVM constraints. This provides the best balance of compatibility, control, and user experience.
Naming Convention: Use a clear name like "USDT-PH" (USDT Passet Hub) or "phUSDT" to distinguish from standard USDT while maintaining brand recognition.
Backing Strategy: Establish a clear backing mechanism through either:
EIP-3009 defines a standard interface for signature-based token transfers, enabling gasless transactions and seamless integration with payment protocols like X402. The implementation requires careful attention to security, particularly signature verification and replay protection.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract USDTPH {
// Token metadata
string private constant NAME = "USDT Passet Hub";
string private constant SYMBOL = "USDT-PH";
string private constant VERSION = "1";
uint8 private constant DECIMALS = 6;
// State variables
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
uint256 private _totalSupply;
// EIP-712 type hashes
bytes32 private constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = keccak256(
"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
);
// Events
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
// EIP-712 domain separator (computed on-demand for PolkaVM)
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(NAME)),
keccak256(bytes(VERSION)),
block.chainid,
address(this)
));
}
// EIP-3009: Transfer with authorization
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(block.timestamp > validAfter, "Authorization not yet valid");
require(block.timestamp < validBefore, "Authorization expired");
require(!_authorizationStates[from][nonce], "Authorization already used");
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(abi.encode(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from,
to,
value,
validAfter,
validBefore,
nonce
))
));
address signer = ecrecover(digest, v, r, s);
require(signer == from, "Invalid signature");
_authorizationStates[from][nonce] = true;
_transfer(from, to, value);
emit AuthorizationUsed(from, nonce);
}
// Internal transfer logic
function _transfer(address from, address to, uint256 value) internal {
require(from != address(0), "Transfer from zero address");
require(to != address(0), "Transfer to zero address");
require(_balances[from] >= value, "Insufficient balance");
_balances[from] -= value;
_balances[to] += value;
emit Transfer(from, to, value);
}
// Standard ERC-20 functions
function name() external pure returns (string memory) {
return NAME;
}
function symbol() external pure returns (string memory) {
return SYMBOL;
}
function decimals() external pure returns (uint8) {
return DECIMALS;
}
function version() external pure returns (string memory) {
return VERSION;
}
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
// ... additional ERC-20 functions
}
The contract uses constants and computes the domain separator on-demand to avoid storage operations in the constructor. This is essential for PolkaVM compatibility.
| Adaptation | Standard Pattern | PolkaVM Pattern |
|---|---|---|
| Token Metadata | Storage variables set in constructor | Constants defined at compile time |
| Domain Separator | Computed once in constructor, cached | Computed on-demand in view function |
| Type Hashes | May be computed in constructor | Defined as constants |
| Inheritance | Multiple OpenZeppelin contracts | Inline implementations, minimal inheritance |
| Libraries | SafeMath, ECDSA, etc. | Inline logic, built-in overflow protection |
The contract uses a nonce-based replay protection mechanism where each authorization can only be used once. The nonce is a bytes32 value chosen by the authorizer, providing flexibility in nonce management strategies.
mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
function transferWithAuthorization(..., bytes32 nonce, ...) external {
require(!_authorizationStates[from][nonce], "Authorization already used");
// ... signature verification
_authorizationStates[from][nonce] = true;
// ... execute transfer
}
Authorizations include validAfter and validBefore timestamps, creating a time window during which the authorization is valid. This prevents stale authorizations from being executed and provides additional security.
require(block.timestamp > validAfter, "Authorization not yet valid");
require(block.timestamp < validBefore, "Authorization expired");
The contract uses EIP-712 typed structured data for signatures, providing a secure and user-friendly signing experience. The signature covers all authorization parameters along with the domain separator.
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(abi.encode(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from, to, value, validAfter, validBefore, nonce
))
));
address signer = ecrecover(digest, v, r, s);
require(signer == from, "Invalid signature");
Since Passet Hub is not supported by existing facilitator infrastructure, we must implement a self-hosted facilitator adapted for PolkaVM's unique characteristics. The facilitator serves as the bridge between HTTP payment requests and blockchain settlement.
| Component | Technology | Responsibilities |
|---|---|---|
| HTTP Server | Node.js / Express | Handle payment verification requests |
| Signature Validator | ethers.js | Verify EIP-712 signatures off-chain |
| Blockchain Client | ethers.js / web3.js | Submit transactions to Passet Hub |
| Gas Estimator | Custom logic | Estimate PolkaVM resource costs |
| Transaction Manager | Custom logic | Handle nonce management, retries |
| Database | PostgreSQL | Track settlements, prevent replays |
Unlike Ethereum's single gas metric, PolkaVM uses three separate resource dimensions. The facilitator must handle this complexity when estimating costs and submitting transactions.
Implementation Note: The Ethereum RPC proxy automatically maps these three dimensions into a single gas value for compatibility. However, the facilitator should be aware of this multi-dimensional model for accurate cost estimation and monitoring.
Passet Hub requires accounts to maintain a minimum balance (existential deposit) to remain active. The facilitator must ensure payment amounts account for this requirement, particularly when creating new recipient accounts.
// Pseudo-code for existential deposit handling
const EXISTENTIAL_DEPOSIT = ethers.utils.parseEther("0.01"); // Example value
async function validatePayment(payment) {
const recipientBalance = await provider.getBalance(payment.to);
if (recipientBalance.isZero()) {
// New account - ensure payment exceeds existential deposit
if (payment.value.lt(EXISTENTIAL_DEPOSIT)) {
throw new Error("Payment below existential deposit for new account");
}
}
// ... additional validation
}
POST /verify
Content-Type: application/json
{
"from": "0x...",
"to": "0x...",
"value": "1000000",
"validAfter": 1699564800,
"validBefore": 1699651200,
"nonce": "0x...",
"signature": {
"v": 27,
"r": "0x...",
"s": "0x..."
},
"token": "0x..."
}
Response:
{
"valid": true,
"message": "Payment verified"
}
POST /submit
Content-Type: application/json
{
"from": "0x...",
"to": "0x...",
"value": "1000000",
"validAfter": 1699564800,
"validBefore": 1699651200,
"nonce": "0x...",
"signature": {
"v": 27,
"r": "0x...",
"s": "0x..."
},
"token": "0x..."
}
Response:
{
"txHash": "0x...",
"status": "pending"
}
GET /settlement/:txHash
Response:
{
"txHash": "0x...",
"status": "confirmed",
"blockNumber": 12345678,
"confirmations": 12
}
| Property | Value |
|---|---|
| Network Name | Passet Hub (Paseo TestNet) |
| Chain ID | 420420422 (0x1911f0a6) |
| RPC Endpoint | https://testnet-passet-hub-eth-rpc.polkadot.io |
| Block Explorer | https://blockscout-passet-hub.parity-testnet.parity.io |
| Faucet | https://faucet.polkadot.io/?parachain=1111 |
| Native Token | PAS |
require("@nomicfoundation/hardhat-toolbox");
require("@parity/hardhat-polkadot");
const { vars } = require("hardhat/config");
module.exports = {
solidity: "0.8.28",
resolc: {
version: "0.3.0",
compilerSource: "npm"
},
networks: {
passetHub: {
polkavm: true,
url: "https://testnet-passet-hub-eth-rpc.polkadot.io",
accounts: [vars.get("PRIVATE_KEY")],
chainId: 420420422
}
}
};
npx hardhat compile
npx hardhat ignition deploy ./ignition/modules/USDTPH.js --network passetHub
Save the deployed token address for facilitator configuration.
npx hardhat ignition deploy ./ignition/modules/PaymentEscrow.js --network passetHub
Pass the token address as a constructor parameter.
// .env file
PASSET_HUB_RPC=https://testnet-passet-hub-eth-rpc.polkadot.io
USDT_TOKEN_ADDRESS=0x...
ESCROW_CONTRACT_ADDRESS=0x...
FACILITATOR_PRIVATE_KEY=0x...
CHAIN_ID=420420422
npm install
npm run build
npm start
Verify the service is running and can communicate with Passet Hub.
The following 6-week roadmap provides a structured approach to implementing X402 payments on Passet Hub. Each phase builds upon the previous one, with clear milestones and deliverables.
| Task | Duration | Deliverable |
|---|---|---|
| USDT Contract Development | 3 days | USDT-PH contract with EIP-3009 support |
| PolkaVM Optimization | 2 days | Contract adapted for constructor constraints |
| Contract Deployment | 1 day | Deployed and verified on Passet Hub |
| Facilitator Setup | 2 days | Forked and configured X402 facilitator |
| PolkaVM Adaptations | 2 days | Multi-dimensional gas handling implemented |
| Basic Testing | 2 days | End-to-end payment flow validated |
| Task | Duration | Deliverable |
|---|---|---|
| Escrow Contract Development | 3 days | Time-locked payment escrow with disputes |
| Proof-of-Payment Logic | 2 days | On-chain cryptographic verification |
| HTTP 402 Server | 3 days | Challenge/response server with security |
| Facilitator Integration | 2 days | Server integrated with facilitator |
| Testing | 2 days | Comprehensive integration tests |
| Task | Duration | Deliverable |
|---|---|---|
| UX Design | 2 days | Wireframes and user flow diagrams |
| Frontend Development | 4 days | React app with wallet integration |
| Accessibility Implementation | 2 days | WCAG 2.1 AA compliant interface |
| Error Handling | 2 days | User-friendly error messages and recovery |
| User Testing | 2 days | Feedback and refinements |
| Phase | Duration | Key Deliverables |
|---|---|---|
| Phase 1: Infrastructure | Weeks 1-2 | USDT-PH token, self-hosted facilitator |
| Phase 2: Smart Contracts | Weeks 3-4 | Escrow contract, HTTP 402 server |
| Phase 3: Frontend | Weeks 5-6 | Web2-like UI, user testing complete |
| Total Duration | 6 Weeks | Complete X402 payment system |
| Test Category | Test Cases | Coverage |
|---|---|---|
| ERC-20 Functions | Transfer, approve, transferFrom work correctly | Critical |
| EIP-3009 Functions | transferWithAuthorization, receiveWithAuthorization | Critical |
| Signature Verification | Valid signatures accepted, invalid rejected | Critical |
| Nonce Management | Replay protection, nonce reuse prevented | Critical |
| Time Bounds | validAfter and validBefore enforced | High |
| Domain Separator | Correct chain ID and contract address | Medium |
| Test Category | Test Cases | Coverage |
|---|---|---|
| Payment Verification | Valid payments accepted, invalid rejected | Critical |
| Signature Validation | Off-chain signature verification | Critical |
| Transaction Submission | Successful submission to Passet Hub | Critical |
| Gas Estimation | Accurate PolkaVM resource estimation | High |
| Error Handling | Network errors, contract errors handled | High |
| Attack Vector | Test | Expected Result |
|---|---|---|
| Replay Attack | Reuse same signature/nonce | Second attempt rejected |
| Signature Manipulation | Modify payment parameters after signing | Signature verification fails |
| Expired Authorization | Use authorization past validBefore | Rejected as expired |
| Premature Authorization | Use authorization before validAfter | Rejected as not yet valid |
| Insufficient Balance | Authorize transfer exceeding balance | Transaction reverts |
| Wrong Token | Submit payment with incorrect token | Facilitator rejects |