Skip to content

Smart Contract Information

Addresses and core interfaces for the smart contracts that make up the SoloPay payment system.

Contract Addresses by Network

Testnet Addresses

These are currently testnet addresses. Mainnet addresses will be announced separately.

Polygon Amoy (80002)

ContractAddress
PaymentGatewaySee gatewayAddress in POST /payments response
ERC2771ForwarderSee forwarderAddress in POST /payments response

Why not hardcode addresses?

Contract addresses may differ per chain and per merchant, and may change on upgrades. The payment creation API response always contains the latest addresses — trust those values.

json
{
  "gatewayAddress": "0x...", // PaymentGateway address
  "forwarderAddress": "0x..." // ERC2771Forwarder address (null if Gasless not supported)
}

Core Contract Interfaces (ABI)

PaymentGateway

The core payment contract. A single pay() function is used for both direct payment (user sends the transaction) and gasless payment (relayer sends the transaction via ERC2771Forwarder).

pay() — Execute Payment (Direct or Gasless)

solidity
function pay(
    bytes32 paymentId,       // Unique payment ID (issued by API)
    address tokenAddress,    // ERC-20 token address for payment
    uint256 amount,          // Payment amount (in wei)
    address recipientAddress,// Recipient address (merchant wallet)
    bytes32 merchantId,      // Merchant ID
    uint256 deadline,        // Payment deadline (Unix timestamp, from API)
    PermitSignature calldata permit // EIP-2612 permit; use zero (deadline=0) if not applicable
) external

The deadline is provided in the payment creation or status API response. Fee is applied on-chain from contract configuration; it is not passed as an argument.

Frontend call example (wagmi) — direct payment

typescript
import { useWriteContract } from 'wagmi';

const { writeContract } = useWriteContract();

const zeroPermit = { deadline: 0, v: 0, r: '0x00...', s: '0x00...' };

await writeContract({
  address: gatewayAddress,
  abi: PaymentGatewayABI,
  functionName: 'pay',
  args: [
    paymentId,
    tokenAddress,
    BigInt(amount),
    recipientAddress,
    merchantId,
    BigInt(deadline),
    zeroPermit,
  ],
});

Gasless: The same pay() is encoded and forwarded by the relayer via ERC2771Forwarder. The user signs an EIP-712 ForwardRequest whose data is the encoded pay(...) call. Submit via POST /payments/:id/relay.

ERC2771Forwarder

OpenZeppelin's standard ERC2771Forwarder contract. It validates user signatures in gasless payments and forwards them to PaymentGateway.

nonces() — Query user's current nonce

Always use the latest nonce when generating an EIP-712 signature.

typescript
// Query current nonce
const nonce = await publicClient.readContract({
  address: forwarderAddress, // forwarderAddress from API response
  abi: ERC2771ForwarderABI,
  functionName: 'nonces',
  args: [userAddress],
});

ForwardRequest Type Structure

The data structure used when generating an EIP-712 signature.

typescript
const ForwardRequestTypes = {
  ForwardRequest: [
    { name: 'from', type: 'address' }, // User wallet address
    { name: 'to', type: 'address' }, // PaymentGateway address
    { name: 'value', type: 'uint256' }, // Always 0 (token payment)
    { name: 'gas', type: 'uint256' }, // Recommended: 200000
    { name: 'nonce', type: 'uint256' }, // Fetched from Forwarder
    { name: 'deadline', type: 'uint48' }, // Signature expiry (Unix timestamp)
    { name: 'data', type: 'bytes' }, // Encoded pay() call data
  ],
};

const domain = {
  name: 'ERC2771Forwarder', // Fixed value
  version: '1', // Fixed value
  chainId: 80002, // Chain ID in use
  verifyingContract: forwarderAddress,
};

Next Steps

Non-custodial Web3 payment infrastructure for ERC-20 checkout, sponsored gas, and wallet-to-wallet settlement.