-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Summary
Propose adding a new exact-split scheme to x402 that enables facilitators to charge fees while allowing sellers to use standard x402 payment requirements without any code changes.
Motivation
Currently, x402 facilitators face a challenge when implementing fee-based business models:
| Current Situation | Problem |
|---|---|
| Facilitator wants to charge a fee | Seller must adapt their code |
Seller specifies payTo: sellerAddress |
Fee must be handled outside x402 |
| Standard "exact" scheme | No built-in fee mechanism |
Real-world use case: A public facilitator (no whitelist) wants to charge 0.01 USDC per transaction. Today, this requires:
- Seller changes
payToto a splitter contract address - Seller adds
extra.sellerfield with their own address - Seller calculates
amount + feethemselves - Both parties must coordinate on the fee structure
This breaks the simplicity of x402 and creates integration friction.
Buyer-Side Complexity
Without native x402 support for fee-based schemes, buyers face significant friction:
- Must implement a custom
SchemeNetworkClient(EIP-712 signing, nonce calculation, payload transformation) - Lose the "just use
@x402/fetch" simplicity that makes x402 attractive - Each fee-based facilitator requires custom client-side code
This creates an adoption blocker for community/small facilitators:
- Large, well-funded entities can offer "free" facilitation (subsidized by other business models)
- Community facilitators need transaction fees to sustain operations
- But buyers won't adopt complex custom schemes → facilitators can't charge fees → only centralized players remain
So from my view native fee support is crucial for a diverse, decentralized facilitator ecosystem. I do not see how I would run one without fees.
Proposed Solution: exact-split Scheme
A new scheme that transforms standard payment requirements internally:
┌─────────────────────────────────────────────────────────────┐
│ Seller's PaymentRequirements (STANDARD!) │
│ ───────────────────────────────────────── │
│ scheme: "exact-split" │
│ payTo: "0xSeller..." ← Seller's own address │
│ amount: "20000" ← What seller wants (0.02 USDC)│
└─────────────────────────────────────────────────────────────┘
▼
ExactSplitEvmScheme
transforms to:
▼
┌─────────────────────────────────────────────────────────────┐
│ EIP-712 Authorization (signed by buyer) │
│ ─────────────────────────────────────── │
│ to: "0xSplitter..." ← Splitter contract │
│ value: "30000" ← amount + fee (0.03 USDC) │
│ nonce: keccak256(seller, salt) │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ PaymentPayload.payload │
│ ───────────────────── │
│ authorization: { from, to, value, ... } │
│ seller: "0xSeller..." ← For settlement │
│ salt: "0x..." ← Random, for nonce uniqueness │
│ originalAmount: "20000" ← What seller receives │
│ fee: "10000" ← Facilitator fee │
└─────────────────────────────────────────────────────────────┘
Technical Design
1. SchemeNetworkClient Implementation
class ExactSplitEvmScheme implements SchemeNetworkClient {
readonly scheme = "exact-split";
async createPaymentPayload(
x402Version: number,
requirements: PaymentRequirements
): Promise<Pick<PaymentPayload, "x402Version" | "payload">> {
// 1. Get seller from requirements.payTo
// 2. Generate random salt
// 3. Compute nonce = keccak256(abi.encode(seller, salt))
// 4. Transform: to = splitterAddress, value = amount + fee
// 5. Sign EIP-712 TransferWithAuthorization
// 6. Return payload with seller, salt, originalAmount, fee
}
}2. Nonce Calculation
// On-chain in Splitter contract:
bytes32 nonce = keccak256(abi.encode(seller, salt));This ensures:
- Each seller+salt combination produces a unique nonce
- Replay protection is maintained
- Seller address is cryptographically bound to the authorization
3. Splitter Contract Interface
interface IEIP3009Splitter {
function splitTransferWithAuthorization(
address from,
address seller,
uint256 totalAmount,
uint256 validAfter,
uint256 validBefore,
bytes32 salt,
bytes calldata signature
) external;
}The contract:
- Receives
totalAmountfrom buyer - Sends
totalAmount - fixedFeeto seller - Sends
fixedFeeto facilitator wallet - All in one atomic transaction
Facilitator /supported Response
{
"x402Version": 2,
"kinds": [
{
"scheme": "exact-split",
"network": "eip155:10",
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"extra": {
"splitterAddress": "0x...",
"fixedFee": "10000",
"feeDescription": "0.01 USDC fixed fee per transaction"
}
}
]
}Benefits
| Aspect | Standard "exact" | Proposed "exact-split" |
|---|---|---|
| Seller code changes | Must adapt for fees | None required |
| Fee transparency | Off-chain agreement | On-chain, verifiable |
| Atomic settlement | N/A | Single transaction |
| Buyer trust | Must trust facilitator | Contract-enforced split |
Open Questions
-
Scheme naming: Should it be
exact-split,exact-fee, or something else? -
Fee discovery: How should the
fixedFeebe communicated?- Option A: In
/supportedresponseextrafield - Option B: Standardized field in PaymentRequirements
- Option C: Query endpoint on splitter contract
- Option A: In
-
Variable fees: Should the scheme support percentage-based fees, or only fixed fees?
-
Multi-recipient splits: Should we generalize to N recipients (e.g., royalties)?
-
Nonce pattern: Is
keccak256(seller, salt)the right approach, or should we use a different binding?
On question 3 and 4 I would prefer a "simple" solution for the start.
Reference Implementation
A working PoC is available demonstrating:
- Custom
ExactSplitEvmSchemeclass implementingSchemeNetworkClient - Full verify/settle flow with balance verification
EIP3009SplitterV1 Contract on Optimism Sepolia:
- Address:
0x7e67bf96ADbf4a813DD7b0A3Ca3060a937018946 - Etherscan: https://sepolia-optimism.etherscan.io/address/0x7e67bf96ADbf4a813DD7b0A3Ca3060a937018946
Next Steps
I'm happy to contribute a full implementation (client-side scheme + facilitator logic) as a PR. However, I'd appreciate feedback on the general approach and the open questions above before investing time in a polished PR.
Specifically looking for input on:
- Is this the right direction for fee support in x402?
- Any concerns with the nonce binding pattern?
- Preferences on scheme naming and fee discovery mechanism?