Skip to content

Funding-signed transactions

Some transactions must come from the user's own address — the contract checks msg.sender, the signature scheme binds to the account, or the nonce matters. Gas sponsorship through a relayer doesn't fit there, because the relayer's address would be the sender. Funding-signed submissions solve this: you sign the transaction locally and send Photon the raw bytes; Photon tops up the sender's native balance so the transaction can pay for itself, then broadcasts it unchanged.

Photon never sees a private key and cannot modify what you signed — it only funds the sender and forwards the exact raw transaction to the network.

How it works

When you call Submit a signed transaction, Photon:

  1. Reads the current native balance of fromAddress.
  2. If the balance is below minBalanceRequired, transfers exactly the deficit to fromAddress and waits for that funding transfer to confirm. If the balance already meets the target, nothing is transferred.
  3. Broadcasts your signedTx as-is and waits for confirmation.
  4. Records the result on the transaction record (transactionType: "funding-signed").

The gas of your own transaction is paid from the sender's (topped-up) balance, not by the relayer.

Pricing

Your cost is the funding outlay times 1.08 — the amount transferred plus the funding transfer's gas, with an 8% service fee on top. That total is converted to USD and recorded as the transaction's costUSD.

If the sender's balance already meets minBalanceRequired, there is no funding transfer and no fee.

Request fields

Field Type Description
signedTx string The raw signed transaction as 0x-prefixed hex. Broadcast unchanged.
fromAddress string The transaction's sender — the address Photon tops up.
minBalanceRequired string Base-10 integer string in the chain's smallest native unit (wei on EVM). The balance fromAddress must hold before broadcast.
chainId number Target chain.
label string, optional Free-form tag shown on the transaction record.
region string, optional Relaying region — see Region routing.

Warning

minBalanceRequired must cover the full gas cost of your signed transaction plus any native value it transfers. If it is too low, the broadcast fails with an insufficient-funds error from the network. Use the quote endpoint below to size it instead of guessing.

Size the balance with a quote

Estimate transaction cost accepts the unsigned intent (to, callData, value, chainId) and returns a minBalanceRequired sized for the worst-case native cost including the 8% fee headroom:

curl -X POST https://photon.example.com/api/quote \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "0x3dbE34f2C21b3B2980d4dc53f3c7E51e39663F49", "callData": "0xd0e30db0", "value": "0", "chainId": 84532}'
{
  "success": true,
  "data": {
    "costUSD": 0.0009,
    "minBalanceRequired": "459000000000000"
  },
  "timestamp": "2026-07-29T09:15:12.345Z"
}

Sign your transaction locally, then pass the quoted value through.

Submit

Requires an api-key header — Get API access.

curl -X POST https://photon.example.com/api/submit-signed-tx \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signedTx": "0x02f8b183014a3480843b9aca00847735940083015f9094...",
    "fromAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    "minBalanceRequired": "459000000000000",
    "chainId": 84532,
    "label": "user-claim-7"
  }'
const res = await fetch("https://photon.example.com/api/submit-signed-tx", {
  method: "POST",
  headers: { "api-key": "YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({
    signedTx: "0x02f8b183014a3480843b9aca00847735940083015f9094...",
    fromAddress: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    minBalanceRequired: "459000000000000",
    chainId: 84532,
    label: "user-claim-7",
  }),
});
const body = await res.json();

Response — 201 Created:

{
  "success": true,
  "data": {
    "txId": "68fa3450539a3c9d28bbca33"
  },
  "timestamp": "2026-07-29T09:15:12.345Z"
}

No estimatedCostUSD is returned here — the final costUSD appears on the transaction record once the transaction executes. Track it with Get transaction status.

Next