Payment Service API Fees
Base route: /integration/fees
Requests require authentication via x-depay-api-key or x-depay-signature. See Auth Model for details.
Both endpoints take an EIP-712 signature produced by the owner wallet, which is separate from the request-level auth. The service relays that signed data on-chain and pays the gas.
POST /integration/fees/set-meta
Submit owner-signed SetFee data for gasless execution.
Request body
{
"ownerAddress": "0x501BEF961A6f40E063efD6048768b0BC35ab1428",
"chainId": 8453,
"feeBps": 200,
"nonce": "7",
"deadline": "1714203600",
"signature": "0x..."
}
feeBps— fee in basis points, max 500 (= 5%)nonce— read fromfactory.nonces(ownerAddress)on-chaindeadline— unix seconds (milliseconds are auto-normalized)
- TypeScript
- Python
- cURL
set-fee.ts
import axios from "axios";
import { createPublicClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
const FACTORY = "0x79972d16fe9Aac806caB10377DD0c27781aE0491" as const;
const account = privateKeyToAccount(
process.env.OWNER_PRIVATE_KEY as `0x${string}`,
);
const publicClient = createPublicClient({ chain: base, transport: http() });
const domain = {
name: "PaymentFactory",
version: "1",
chainId: 8453,
verifyingContract: FACTORY,
} as const;
const types = {
SetFee: [
{ name: "user", type: "address" },
{ name: "feeBps", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
} as const;
export async function setFee(feeBps: number): Promise<void> {
// Nonce must match the current on-chain value or the relay is rejected.
const nonce = await publicClient.readContract({
address: FACTORY,
abi: [
{
name: "nonces",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ name: "", type: "uint256" }],
},
] as const,
functionName: "nonces",
args: [account.address],
});
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
const signature = await account.signTypedData({
domain,
types,
primaryType: "SetFee",
message: {
user: account.address,
feeBps: BigInt(feeBps),
nonce,
deadline,
},
});
await axios.post(
`${process.env.SODAPOP_BASE_URL}/integration/fees/set-meta`,
{
ownerAddress: account.address,
chainId: 8453,
feeBps,
nonce: nonce.toString(),
deadline: deadline.toString(),
signature,
},
{
headers: {
"Content-Type": "application/json",
"x-depay-api-key": process.env.SODAPOP_API_KEY ?? "",
},
},
);
}
set_fee.py
import os
import time
import requests
from eth_account import Account
from web3 import Web3
FACTORY = Web3.to_checksum_address("0x79972d16fe9Aac806caB10377DD0c27781aE0491")
NONCES_ABI = [
{
"name": "nonces",
"type": "function",
"stateMutability": "view",
"inputs": [{"name": "owner", "type": "address"}],
"outputs": [{"name": "", "type": "uint256"}],
}
]
account = Account.from_key(os.environ["OWNER_PRIVATE_KEY"])
w3 = Web3(Web3.HTTPProvider(os.environ["BASE_RPC_URL"]))
def set_fee(fee_bps: int) -> None:
# Nonce must match the current on-chain value or the relay is rejected.
nonce = (
w3.eth.contract(address=FACTORY, abi=NONCES_ABI)
.functions.nonces(account.address)
.call()
)
deadline = int(time.time()) + 3600
typed_data = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"SetFee": [
{"name": "user", "type": "address"},
{"name": "feeBps", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint256"},
],
},
"primaryType": "SetFee",
"domain": {
"name": "PaymentFactory",
"version": "1",
"chainId": 8453,
"verifyingContract": FACTORY,
},
"message": {
"user": account.address,
"feeBps": fee_bps,
"nonce": nonce,
"deadline": deadline,
},
}
signature = Account.sign_typed_data(
os.environ["OWNER_PRIVATE_KEY"], full_message=typed_data
).signature.hex()
response = requests.post(
f"{os.environ['SODAPOP_BASE_URL']}/integration/fees/set-meta",
json={
"ownerAddress": account.address,
"chainId": 8453,
"feeBps": fee_bps,
"nonce": str(nonce),
"deadline": str(deadline),
"signature": signature,
},
headers={"x-depay-api-key": os.environ["SODAPOP_API_KEY"]},
timeout=30,
)
response.raise_for_status()
Install with pip install eth-account web3 requests.
curl -X POST "$BASE_URL/integration/fees/set-meta" \
-H 'Content-Type: application/json' \
-H "x-depay-api-key: $SODAPOP_API_KEY" \
-d '{
"ownerAddress": "'"$OWNER_ADDRESS"'",
"chainId": 8453,
"feeBps": 200,
"nonce": "7",
"deadline": "1714203600",
"signature": "<SET_FEE_EIP712_SIGNATURE>"
}'
Request signature instead of API key:
curl -X POST "$BASE_URL/integration/fees/set-meta" \
-H 'Content-Type: application/json' \
-H 'x-depay-timestamp: <TIMESTAMP_MS>' \
-H 'x-depay-signature: <REQUEST_SIGNATURE>' \
-d '{
"ownerAddress": "'"$OWNER_ADDRESS"'",
"chainId": 8453,
"feeBps": 200,
"nonce": "7",
"deadline": "1714203600",
"signature": "<SET_FEE_EIP712_SIGNATURE>"
}'
POST /integration/fees/set-payout-meta
Submit owner-signed SetPayout data for gasless execution.
Request body
{
"ownerAddress": "0x501BEF961A6f40E063efD6048768b0BC35ab1428",
"chainId": 8453,
"payoutAddress": "0xAaaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa",
"nonce": "8",
"deadline": "1714207200",
"signature": "0x..."
}
- TypeScript
- Python
- cURL
set-payout.ts
const setPayoutTypes = {
SetPayout: [
{ name: "user", type: "address" },
{ name: "payout", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
} as const;
export async function setPayout(payoutAddress: `0x${string}`): Promise<void> {
const nonce = await readNonce(account.address); // same helper as setFee
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
const signature = await account.signTypedData({
domain,
types: setPayoutTypes,
primaryType: "SetPayout",
message: {
user: account.address,
payout: payoutAddress,
nonce,
deadline,
},
});
await axios.post(
`${process.env.SODAPOP_BASE_URL}/integration/fees/set-payout-meta`,
{
ownerAddress: account.address,
chainId: 8453,
payoutAddress,
nonce: nonce.toString(),
deadline: deadline.toString(),
signature,
},
{
headers: {
"Content-Type": "application/json",
"x-depay-api-key": process.env.SODAPOP_API_KEY ?? "",
},
},
);
}
set_payout.py
def set_payout(payout_address: str) -> None:
nonce = read_nonce(account.address) # same helper as set_fee
deadline = int(time.time()) + 3600
typed_data = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"SetPayout": [
{"name": "user", "type": "address"},
{"name": "payout", "type": "address"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint256"},
],
},
"primaryType": "SetPayout",
"domain": {
"name": "PaymentFactory",
"version": "1",
"chainId": 8453,
"verifyingContract": FACTORY,
},
"message": {
"user": account.address,
"payout": Web3.to_checksum_address(payout_address),
"nonce": nonce,
"deadline": deadline,
},
}
signature = Account.sign_typed_data(
os.environ["OWNER_PRIVATE_KEY"], full_message=typed_data
).signature.hex()
response = requests.post(
f"{os.environ['SODAPOP_BASE_URL']}/integration/fees/set-payout-meta",
json={
"ownerAddress": account.address,
"chainId": 8453,
"payoutAddress": payout_address,
"nonce": str(nonce),
"deadline": str(deadline),
"signature": signature,
},
headers={"x-depay-api-key": os.environ["SODAPOP_API_KEY"]},
timeout=30,
)
response.raise_for_status()
curl -X POST "$BASE_URL/integration/fees/set-payout-meta" \
-H 'Content-Type: application/json' \
-H 'x-depay-timestamp: <TIMESTAMP_MS>' \
-H 'x-depay-signature: <REQUEST_SIGNATURE>' \
-d '{
"ownerAddress": "'"$OWNER_ADDRESS"'",
"chainId": 8453,
"payoutAddress": "<PAYOUT_ADDRESS>",
"nonce": "8",
"deadline": "1714207200",
"signature": "<SET_PAYOUT_EIP712_SIGNATURE>"
}'
Validation Rules
- Nonce must match on-chain expected value.
- Deadline must be valid at submission time.
- EIP-712 owner signature must match payload.
Common Failure Cases
- nonce mismatch
- expired deadline
- invalid owner signature
Both meta-transactions share a single incrementing nonce. If you submit
set-meta and set-payout-meta back to back, re-read the nonce between calls
instead of reusing the cached value.