Essentials

Gas sponsorship

Octo can pay the network fees for your users' Stellar transactions out of your master wallet, so your users can hold and move stablecoins without ever buying XLM for gas. Every sponsored transaction is logged with per-wallet spend controls so the feature can't be abused.

Read the Security model for the threat analysis and signing-path invariants that protect your master wallet during sponsorship.

How it works

Stellar natively supports fee-bump transactions: one account (your master wallet) pays the fee for another account's transaction. Here's the flow:

  1. Your user signs a payment or path-payment transaction XDR (the inner transaction).
  2. Your backend POSTs that XDR to Octo's /sponsor endpoint.
  3. Octo validates the op types, checks your spend limits, wraps the XDR in a fee-bump envelope signed by your master wallet, and submits it to the Stellar network.
  4. Octo returns the outcome immediately and fires a transaction.sponsored webhook when you have endpoints registered.

Enable & configure sponsorship

PUT/v1/wallets/:id/sponsorship

Enable or disable gas sponsorship for a wallet and set per-transaction fee caps and the daily budget. Requires a dashboard login token.

FieldTypeDescription
enabledrequiredbooleanTurn sponsorship on or off for this wallet.
per_tx_fee_cap_stroopsintegerMaximum fee (in stroops) the master wallet will pay per sponsored transaction. Must be ≤ daily_budget_stroops. Defaults to 100 000 (0.01 XLM) if omitted.
daily_budget_stroopsintegerMaximum total fees (in stroops) the master wallet will spend on sponsorship per calendar day (UTC). Defaults to 100 000 000 (10 XLM) if omitted.
Request
curl -X PUT http://localhost:8080/v1/wallets/<WALLET_ID>/sponsorship \\
  -H "authorization: Bearer eyJ…" \\
  -H "content-type: application/json" \\
  -d '{
    "enabled": true,
    "per_tx_fee_cap_stroops": 100000,       // 0.01 XLM
    "daily_budget_stroops": 50000000        // 5 XLM
  }'
Response (200)
{
  "statusCode": 200,
  "message": "OK",
  "data": {
    "wallet_id": "52775…",
    "enabled": true,
    "per_tx_fee_cap_stroops": 100000,       // 0.01 XLM
    "daily_budget_stroops": 50000000,       // 5 XLM
    "created_at": "2026-06-15T10:30:00Z",
    "updated_at": "2026-06-15T12:45:00Z"
  }
}

Before any config is saved a wallet is disabled with no caps (both per_tx_fee_cap_stroops and daily_budget_stroops are null). Set them explicitly to bound spend.

Read current config

GET/v1/wallets/:id/sponsorship

Returns the current sponsorship config (or defaults if none has been saved). Requires a dashboard login token.

Request
curl http://localhost:8080/v1/wallets/<WALLET_ID>/sponsorship \\
  -H "authorization: Bearer eyJ…"
Response (200)
{
  "statusCode": 200,
  "message": "OK",
  "data": {
    "wallet_id": "52775…",
    "enabled": true,
    "per_tx_fee_cap_stroops": 100000,       // 0.01 XLM
    "daily_budget_stroops": 50000000,       // 5 XLM
    "created_at": "2026-06-15T10:30:00Z",
    "updated_at": "2026-06-15T12:45:00Z"
  }
}

Sponsor a transaction

POST/v1/wallets/:id/sponsor

Submit a user-signed inner transaction XDR. Octo validates it, wraps it in a fee-bump signed by the master wallet, and submits the result to Horizon. Accepts both JWT login tokens and API keys.

FieldTypeDescription
transaction_xdrrequiredstringBase64-encoded TransactionEnvelope XDR of the user's signed inner transaction.
max_base_fee_stroopsrequiredintegerMaximum fee (in stroops) the master wallet will pay for the fee-bump. Must be > 0 and ≤ the per-tx cap.
Only Payment, PathPaymentStrictSend, and PathPaymentStrictReceive operations are allowed in the inner XDR. Account Merge, Set Options, and all sponsorship-related ops are rejected. The inner transaction source must not be the master wallet itself.
Request
curl -X POST http://localhost:8080/v1/wallets/<WALLET_ID>/sponsor \\
  -H "authorization: Bearer octo_sk_test_abc123…" \\
  -H "content-type: application/json" \\
  -d '{
    "transaction_xdr": "AAAAAgAAAAD…",
    "max_base_fee_stroops": 100000           // 0.01 XLM
  }'
Response (201 — confirmed)
{
  "statusCode": 201,
  "message": "Created",
  "data": {
    "id": "3f1a9b2e…",
    "status": "confirmed",
    "inner_tx_hash": "7f18b2…",
    "fee_bump_tx_hash": "a1c3d4…",
    "fee_stroops": 100000                     // 0.01 XLM
  }
}
Response (201 — on-chain failure)
{
  "statusCode": 201,
  "message": "Created",
  "data": {
    "id": "3f1a9b2e…",
    "status": "failed",
    "inner_tx_hash": "7f18b2…",
    "fee_bump_tx_hash": null,
    "fee_stroops": 100000                     // 0.01 XLM
  }
}

Sponsored transactions history

GET/v1/wallets/:id/sponsored-transactions

Retrieve the paginated history of sponsored transactions for a wallet. Requires a dashboard login token.

FieldTypeDescription
limitintegerRows per page (default 50, max 200).
statusstringFilter by status: pending, confirmed, or failed.
beforestringCursor for page-back; use the next_cursor from the previous response.
Request
curl "http://localhost:8080/v1/wallets/<WALLET_ID>/sponsored-transactions?limit=50&status=confirmed" \\
  -H "authorization: Bearer eyJ…"
Response (200)
{
  "statusCode": 200,
  "message": "OK",
  "data": {
    "rows": [
      {
        "id": "3f1a9b2e…",
        "wallet_id": "52775…",
        "inner_tx_hash": "7f18b2…",
        "fee_bump_tx_hash": "a1c3d4…",
        "fee_stroops": 100000,                // 0.01 XLM
        "status": "confirmed",
        "error": null,
        "created_at": "2026-06-15T14:32:00Z"
      }
    ],
    "next_cursor": "3f1a9b2e…"
  }
}

Spend controls

Every wallet has two guardrails that prevent runaway sponsorship costs:

  • Per-transaction fee cap (per_tx_fee_cap_stroops) — the most the master wallet will ever pay for a single fee-bump. Set it low enough that a burst of sponsor requests can't drain your wallet in one go.
  • Daily budget (daily_budget_stroops) — the total fees the master wallet will pay across all sponsored transactions in a UTC calendar day. Each new sponsor request is checked against today's sum of confirmed fees; if it would push spending over the budget, Octo returns a 429.

Worked example

Suppose your wallet is configured with:

  • Per-tx cap: 100 000 stroops (0.01 XLM)
  • Daily budget: 5 000 000 stroops (0.5 XLM)

You can sponsor up to 50 transactions at 0.01 XLM each today. If you reach the budget cap before midnight UTC, further sponsor requests receive 429 Too Many Requests with the error code budget_exceeded. At midnight UTC the counter resets automatically.

The budget check is an unconditional guard — it rejects the request before Octo signs and submits the fee-bump. No sponsor request can slip through once the daily budget is exhausted.

Error reference

HTTP statusError codeMeaning
400invalid_xdrThe transaction_xdr is not valid Stellar XDR, or it is a fee-bump envelope (not an inner transaction).
400op_not_allowedThe inner XDR contains an operation type that is not on the allowlist (only Payment, PathPaymentStrictSend, and PathPaymentStrictReceive are permitted).
400self_sponsorshipThe inner transaction source account matches the master wallet — sponsoring yourself is rejected as a security safeguard.
403sponsorship_disabledGas sponsorship is not configured, or is explicitly disabled, for this wallet.
409duplicate_inner_txThis inner transaction XDR has already been sponsored. Each inner transaction hash is unique — re-submitting the same XDR is rejected to prevent double-sponsoring.
429budget_exceededThe daily sponsorship budget for this wallet would be exceeded by this request. The counter resets at midnight UTC.

Webhook events

If you have webhook endpoints registered for a wallet, Octo fires a transaction.sponsored event after every sponsor request is finalized. Delivery uses the same HMAC-SHA256 signed POST pattern as deposits.

POST to your URL
X-Octo-Signature: <hmac-sha256 hex>
Content-Type: application/json

{
  "event": "transaction.sponsored",
  "data": {
    "wallet_id": "52775…",
    "inner_tx_hash": "7f18b2…",
    "fee_bump_tx_hash": "a1c3d4…",
    "fee_stroops": 100000,
    "status": "confirmed",
    "created_at": "2026-06-15T14:32:00Z"
  }
}
  • fee_bump_tx_hash is null when the Horizon submission itself fails.
  • Delivery is best-effort and runs asynchronously — the HTTP response to the /sponsor call reflects the outcome before the webhook fires.
  • Verify the signature the same way you do for deposits; see Webhooks for the verification snippet.
Treat webhook delivery as a notification, not a source of truth. Always reconcile against the completed status in the sponsor response or the GET /sponsored-transactions history.

Audit trail

Every sponsorship action — config changes, successful submissions, on-chain failures, and policy rejections — is recorded in Octo's audit log under the sponsorship category. You can review these entries on the Audit log page. Rejected requests (bad XDR, forbidden op types, budget overruns) are also logged so abuse attempts always leave a trace.