import base64, json, secrets, time
from eth_account import Account
from eth_account.messages import encode_typed_data
def build_x402_payment(
chain_id: str, # e.g. "eip155:8453"
amount: int, # in USDC 6-decimal units
sender_key: str, # private key (hex)
recipient: str, # facilitator address
usdc_address: str, # USDC contract address
) -> str:
sender = Account.from_key(sender_key).address
chain_num = int(chain_id.split(":")[1]) # 8453
nonce = "0x" + secrets.token_hex(32)
now = int(time.time())
# EIP-712 domain
domain = {
"name": "USD Coin",
"version": "2",
"chainId": chain_num,
"verifyingContract": usdc_address,
}
# EIP-3009 TransferWithAuthorization
message = {
"from": sender,
"to": recipient,
"value": amount,
"validAfter": now - 10,
"validBefore": now + 300,
"nonce": bytes.fromhex(nonce[2:]),
}
types = {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"TransferWithAuthorization": [
{"name": "from", "type": "address"},
{"name": "to", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "validAfter", "type": "uint256"},
{"name": "validBefore", "type": "uint256"},
{"name": "nonce", "type": "bytes32"},
],
}
signable = encode_typed_data(domain, types, "TransferWithAuthorization", message)
signed = Account.sign_message(signable, private_key=sender_key)
payload = {
"chainId": chain_id,
"amount": str(amount),
"sender": sender,
"recipient": recipient,
"nonce": nonce,
"signature": signed.signature.hex(),
"validAfter": now - 10,
"validBefore": now + 300,
}
return base64.b64encode(json.dumps(payload).encode()).decode()