> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rialto.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Gasless Relayer

> Let the taker sign Permit2 while Rialto submits the swap and pays gas.

## Overview

The gasless flow lets the taker sign Permit2 while Rialto submits the onchain swap
transaction. The taker still authorizes the exact swap with a Permit2 witness
signature, but the relayer wallet pays the network gas.

## Constraints

| Constraint | Behavior |
| - | - |
| Settlement | Requires settlement: permit2 and a returned permit2 payload. Pass settlement=permit2 if you want the quote request to fail instead of falling back to allowance mode. |
| Token type | ERC20 sells only. Native ETH sells require the taker-submitted flow. |
| Permit owner | Pass permit2\_owner=taker on the quote. For gasless, permit2\_owner must equal taker. |
| Relay opt-in | Pass gasless\_relay=true. Permit2 settlement alone does not authorize Rialto to relay the transaction. |
| Recipient | The quote controls the recipient through the Permit2 witness. Do not edit it. |
| Allowance | The taker must still approve Permit2 if issues.allowance is non-null. |
| Balance | Do not submit if issues.balance is non-null. |

## Step 1: request a gasless quote

Add `permit2_owner` and `gasless_relay=true` to the standard quote request:

```bash theme={null}
API_KEY='rialto_live_example.redacted_secret'
TAKER='<taker_wallet_address>'

curl -sS "https://rialto-trade-api.rialto.xyz/quote?sell_token=0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168&buy_token=0xc93a8c440CEa26D7445dF01729f193b27965099f&sell_amount=1&taker=$TAKER&permit2_owner=$TAKER&gasless_relay=true&settlement=permit2&slippage_bps=50&chain_id=4663" \
  -H "Authorization: Bearer $API_KEY"
```

The response includes quote\_id, the permit2 typed data, issues, and the tx Rialto
will submit after receiving the signature.

## Step 2: approval and signature

If issues.allowance is non-null, the taker approves issues.allowance.spender for
at least the raw sell\_amount. For gasless Permit2 quotes the spender is the
Permit2 contract. Then have the taker sign the permit2 typed data exactly as
returned.

## Step 3: submit to the relayer

```http theme={null}
POST /gasless/submit
```

Auth: requires an API key with swap access.

| Field | Description |
| - | - |
| quote\_id | UUID returned by the quote. The quote must have been requested with permit2\_owner. |
| signature | 0x-prefixed 65-byte Permit2 signature over the typed data returned by the quote. |
| idempotency\_key | Optional client-generated unique string for one relay attempt, such as your internal order id or a UUID. Reuse the same value only when retrying the same quote submission; do not reuse it across different quotes or users. |

```bash theme={null}
curl -sS 'https://rialto-trade-api.rialto.xyz/gasless/submit' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "quote_id": "b7b0a3d8-9f6a-4f4a-92d2-1b0e4b5d0c6a",
    "signature": "0xabc123...",
    "idempotency_key": "partner-order-123"
  }'
```

Example response:

```json theme={null}
{
  "relay_id": "f3f6a35f-93b9-4629-9a2e-02d2b80648ef",
  "quote_id": "b7b0a3d8-9f6a-4f4a-92d2-1b0e4b5d0c6a",
  "status": "submitted",
  "chain_id": 4663,
  "tx_hash": "0x6b8f...",
  "error": null
}
```

## Step 4: poll relay status

```http theme={null}
GET /gasless/status/{relay_id}
```

Auth: requires an API key with swap access.

| Status | Meaning |
| - | - |
| accepted | Request accepted and queued. |
| submitted | Relayer broadcasted the transaction. |
| confirmed | Transaction mined successfully. |
| failed | Transaction was submitted but failed or could not be confirmed. |
| expired | Request expired before successful relay. |
| rejected | Request failed validation before relay. |

```bash theme={null}
curl -sS 'https://rialto-trade-api.rialto.xyz/gasless/status/f3f6a35f-93b9-4629-9a2e-02d2b80648ef' \
  -H "Authorization: Bearer $API_KEY"
```

## Python example

This example requests a gasless quote, signs Permit2, submits the signature,
and polls until the relay reaches a terminal state.

```bash theme={null}
python3 -m pip install requests web3 eth-account

export API_KEY='rialto_live_example.redacted_secret'
export RPC_URL='<your-rh-rpc-url>'
export PRIVATE_KEY='0xREDACTED_TAKER_PRIVATE_KEY'
python3 rialto_gasless_swap.py
```

```python theme={null}
import os
import time
from urllib.parse import urlencode

import requests
from eth_account import Account
from eth_account.messages import encode_typed_data
from web3 import Web3

API_BASE = "https://rialto-trade-api.rialto.xyz"
CHAIN_ID = 4663
SELL_TOKEN = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168"
BUY_TOKEN = "0xc93a8c440CEa26D7445dF01729f193b27965099f"
SELL_AMOUNT = "1"
SLIPPAGE_BPS = 50

ERC20_ABI = [
    {
        "name": "approve",
        "type": "function",
        "stateMutability": "nonpayable",
        "inputs": [
            {"name": "spender", "type": "address"},
            {"name": "value", "type": "uint256"},
        ],
        "outputs": [{"name": "", "type": "bool"}],
    }
]


def require_env(name):
    value = os.getenv(name)
    if not value:
        raise RuntimeError(f"missing {name}")
    return value


def tx_fees(w3):
    latest = w3.eth.get_block("latest")
    priority = w3.to_wei("0.01", "gwei")
    return {
        "maxPriorityFeePerGas": priority,
        "maxFeePerGas": int(latest["baseFeePerGas"]) * 2 + priority,
    }


api_key = require_env("API_KEY")
private_key = require_env("PRIVATE_KEY")
w3 = Web3(Web3.HTTPProvider(require_env("RPC_URL")))
taker = Account.from_key(private_key).address

params = {
    "sell_token": SELL_TOKEN,
    "buy_token": BUY_TOKEN,
    "sell_amount": SELL_AMOUNT,
    "taker": taker,
    "permit2_owner": taker,
    "gasless_relay": True,
    "settlement": "permit2",
    "slippage_bps": SLIPPAGE_BPS,
    "chain_id": CHAIN_ID,
}
quote_response = requests.get(
    f"{API_BASE}/quote?{urlencode(params)}",
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=30,
)
quote_response.raise_for_status()
quote = quote_response.json()

if quote.get("settlement") != "permit2" or not quote.get("permit2"):
    raise RuntimeError("gasless requires a Permit2 quote")
if quote.get("issues", {}).get("balance"):
    raise RuntimeError(f"insufficient balance: {quote['issues']['balance']}")

allowance = quote.get("issues", {}).get("allowance")
if allowance:
    token = Web3.to_checksum_address(quote["sell_token"])
    spender = Web3.to_checksum_address(allowance["spender"])
    amount = int(quote["sell_amount"])
    approve_tx = w3.eth.contract(address=token, abi=ERC20_ABI).functions.approve(
        spender, amount
    ).build_transaction(
        {
            "from": taker,
            "chainId": CHAIN_ID,
            "nonce": w3.eth.get_transaction_count(taker),
            **tx_fees(w3),
        }
    )
    approve_tx["gas"] = int(w3.eth.estimate_gas(approve_tx) * 1.2)
    signed_approval = Account.sign_transaction(approve_tx, private_key)
    approval_hash = w3.eth.send_raw_transaction(signed_approval.raw_transaction)
    receipt = w3.eth.wait_for_transaction_receipt(approval_hash)
    if receipt.status != 1:
        raise RuntimeError(f"approval reverted: {approval_hash.hex()}")

typed_data = {
    "domain": quote["permit2"]["domain"],
    "types": quote["permit2"]["types"],
    "primaryType": quote["permit2"]["primaryType"],
    "message": quote["permit2"]["message"],
}
signed_permit = Account.sign_message(
    encode_typed_data(full_message=typed_data),
    private_key,
)
signature = "0x" + bytes(signed_permit.signature).hex()

submit_response = requests.post(
    f"{API_BASE}/gasless/submit",
    headers={"Authorization": f"Bearer {api_key}"},
    json={
        "quote_id": quote["quote_id"],
        "signature": signature,
        "idempotency_key": f"partner-{quote['quote_id']}",
    },
    timeout=60,
)
submit_response.raise_for_status()
relay = submit_response.json()
print("relay_id:", relay["relay_id"])
print("initial_status:", relay["status"])

terminal = {"confirmed", "failed", "expired", "rejected"}
while relay["status"] not in terminal:
    time.sleep(5)
    status_response = requests.get(
        f"{API_BASE}/gasless/status/{relay['relay_id']}",
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=30,
    )
    status_response.raise_for_status()
    relay = status_response.json()
    print("status:", relay["status"], "tx_hash:", relay.get("tx_hash"))

if relay["status"] != "confirmed":
    raise RuntimeError(f"relay did not confirm: {relay}")
```

A full runnable Python example for the gasless flow is in the API repo:
[https://github.com/rialto-plds/rialto-api-docs/blob/main/RIALTO\_SWAP\_API.md](https://github.com/rialto-plds/rialto-api-docs/blob/main/RIALTO_SWAP_API.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.