NavenDocs
NavenDocs
Introduction
x402scanTransaction APIPayment Attestations
Back to Naven Network
x402scan

Payment Attestations

Verify Naven-signed x402 payment facts onchain or offchain.

Naven issues an EIP-712 attestation for every indexed x402 payment. The attestation is a portable statement that Naven observed a payment with the specified payer, recipient, token, amount, and recipient sequence.

An attestation does not prescribe how a project uses a payment. Consumers choose their own fulfillment, authorization, replay protection, and finality policies.

Signed schema

The current schema is Naven Payment Attestation version 2:

struct PaymentAttestation {
    bytes32 paymentId;
    address payer;
    address recipient;
    uint256 recipientSequence;
    address token;
    uint256 amount;
}

The EIP-712 domain contains:

{
  "name": "Naven Payment Attestation",
  "version": "2"
}

The domain intentionally does not contain a verifying contract or destination chain. The same attestation can therefore be verified by offchain services and contracts on different networks.

Payment ID

paymentId uniquely identifies the indexed payment event:

paymentId = keccak256(
    abi.encode(
        "naven.payment.v1",
        sourceChainId,
        transactionHash,
        logIndex
    )
);

The source chain, transaction hash, and log index are committed inside the payment ID. Consumers can use the single bytes32 paymentId as their storage and replay-protection key.

Recipient sequence

recipientSequence is assigned independently for each (sourceChainId, recipient) pair:

  • The first indexed payment starts at 1.
  • Each later payment receives exactly the next number.
  • A number is never reassigned or changed after issuance.

The sequence is included in the EIP-712 signature. It can be used as a compact, recipient-specific ingestion cursor without replacing paymentId as the unique payment identity. It records Naven issuance order, which is not necessarily the same as source-chain transaction order.

Verify with viem

Install viem, fetch a transaction by payment ID, and verify the response against the Naven signer address your application has explicitly allowlisted:

import { verifyTypedData } from "viem";

const trustedNavenSigner =
  "0xc90fd9dbc7234e00b8160c53480e58ad973f547d";

const response = await fetch(
  "https://api.naven.network/x402scan/transactions/0xPAYMENT_ID",
);
const transaction = await response.json();
const { attestation } = transaction;

if (transaction.signer.toLowerCase() !== trustedNavenSigner) {
  throw new Error("Unexpected Naven attestation signer");
}

const valid = await verifyTypedData({
  address: trustedNavenSigner,
  domain: {
    name: "Naven Payment Attestation",
    version: "2",
  },
  primaryType: "PaymentAttestation",
  types: {
    PaymentAttestation: [
      { name: "paymentId", type: "bytes32" },
      { name: "payer", type: "address" },
      { name: "recipient", type: "address" },
      { name: "recipientSequence", type: "uint256" },
      { name: "token", type: "address" },
      { name: "amount", type: "uint256" },
    ],
  },
  message: {
    paymentId: attestation.paymentId,
    payer: attestation.payer,
    recipient: attestation.recipient,
    recipientSequence: BigInt(attestation.recipientSequence),
    token: attestation.token,
    amount: BigInt(attestation.amount),
  },
  signature: transaction.signature,
});

if (!valid) throw new Error("Invalid Naven payment attestation");

Do not accept the signer field merely because it appears in the same API response. Compare it with a signer address obtained from a trusted Naven source or pinned in your application configuration.

Verify in Solidity

The domain has only name and version, so a contract can reproduce the EIP-712 digest without knowing the payment's source chain or a designated consumer contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

library NavenPaymentAttestations {
    bytes32 internal constant DOMAIN_TYPEHASH =
        keccak256("EIP712Domain(string name,string version)");
    bytes32 internal constant NAME_HASH =
        keccak256("Naven Payment Attestation");
    bytes32 internal constant VERSION_HASH = keccak256("2");
    bytes32 internal constant ATTESTATION_TYPEHASH = keccak256(
        "PaymentAttestation(bytes32 paymentId,address payer,address recipient,uint256 recipientSequence,address token,uint256 amount)"
    );

    struct PaymentAttestation {
        bytes32 paymentId;
        address payer;
        address recipient;
        uint256 recipientSequence;
        address token;
        uint256 amount;
    }

    function recoverSigner(
        PaymentAttestation calldata attestation,
        bytes calldata signature
    ) internal pure returns (address) {
        bytes32 domainSeparator = keccak256(
            abi.encode(DOMAIN_TYPEHASH, NAME_HASH, VERSION_HASH)
        );
        bytes32 structHash = keccak256(
            abi.encode(
                ATTESTATION_TYPEHASH,
                attestation.paymentId,
                attestation.payer,
                attestation.recipient,
                attestation.recipientSequence,
                attestation.token,
                attestation.amount
            )
        );
        bytes32 digest = keccak256(
            abi.encodePacked("\x19\x01", domainSeparator, structHash)
        );

        return ECDSA.recover(digest, signature);
    }
}

Compare the recovered address with a Naven signer pinned by the consuming contract. If an attestation authorizes a one-time action, store its paymentId as consumed before completing that action.

Consumer responsibilities

Before fulfilling a purchase or granting access, a consumer should:

  1. Verify the EIP-712 signature against its allowlisted Naven signer.
  2. Check recipient, token, and amount against its own requirements.
  3. Decide whether the payment has sufficient source-chain finality.
  4. Store paymentId and reject reuse when its business rule is one payment per fulfillment.

Naven does not bind an attestation to a consumer contract, destination chain, order, entitlement, or expiration. Those rules remain under the consumer's control.

Transaction API

Retrieve Naven-indexed x402 payments for a recipient address.

Facilitator Introduction

Understand payment verification and settlement with the Naven facilitator.

On this page

Signed schemaPayment IDRecipient sequenceVerify with viemVerify in SolidityConsumer responsibilities