Skip to main content

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 from factory.nonces(ownerAddress) on-chain
  • deadline — unix seconds (milliseconds are auto-normalized)
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 ?? "",
},
},
);
}

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..."
}
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 ?? "",
},
},
);
}

Validation Rules

  1. Nonce must match on-chain expected value.
  2. Deadline must be valid at submission time.
  3. 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.