# Execute Gasless Swap

Swap EUR.e for native xDAI on Gnosis, with the token approval supplied as an ERC-2612 permit instead of an approve transaction.

## Overview

The gasless flow exists for one pair: **EUR.e → native xDAI on Gnosis**. The user does not send an `approve()` transaction. Instead, Aurea submits an ERC-2612 permit on-chain — from its relayer or through a sponsored relay — whenever the allowance is insufficient, and then runs the quote's swap transaction.

- **Availability** — gasless swaps sit behind a deployment-level feature flag that is off by default. While it is off, this endpoint and Sign EIP-712 Permit return `503` `Gasless swaps are currently disabled`. A quote only contains a `gasless` object when the flag is on and the pair qualifies — use that object as your signal.
- **Network** — execution is wired to Gnosis (chain ID `100`); this handler only logs `isTestnet`.
- **Result** — depending on the wallet, the call either completes the swap or returns a transaction for the user to sign (see Responses by Path).

## Gasless Sequence

1. [Get Quote](https://docs.aureahub.com/docs/swap-quote.md) with `fromChain` and `toChain` both `gnosis`, `fromToken` = the EUR.e address and `toToken` = `0x0000000000000000000000000000000000000000`. Continue only if the response has a `gasless` object.
2. [Sign EIP-712 Permit](https://docs.aureahub.com/docs/swap-sign-permit.md) with `spenderAddress` = `quote.transactionData.to` and `amount` = the EUR.e amount in wei. The response is your `permitSignature` object.
3. **Execute Gasless** (this endpoint) with the same `amount`, the EUR.e `tokenAddress`, `quote.transactionData` and `permitSignature`.
4. If the response has `requiresClientSigning: true`, sign `txHashToSign` and call [Broadcast Gasless Tx](https://docs.aureahub.com/docs/swap-broadcast-gasless.md) before `expiresAt` (5 minutes). Otherwise the swap has already completed.

> ℹ️ The permit is checked against `owner` = the wallet address, `spender` = `transactionData.to` and `value` = `amount`. Request the permit with exactly those values.

## Endpoint

### `POST /v1/swap/execute-gasless`

Authentication: bearer token required.

Executes an EUR.e to xDAI swap on Gnosis using an ERC-2612 permit.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `walletId` | string (uuid) | yes | Wallet holding the EUR.e. Must belong to the authenticated user. |
| `quoteId` | string | yes | `quoteId` from Get Quote; stored with the transaction. |
| `amount` | string | yes | EUR.e amount to swap, in wei, digits only (`^\d+$`). |
| `tokenAddress` | string | yes | EUR.e contract address, `0x` + 40 hex characters. |
| `transactionData` | object | yes | `transactionData` from the quote. Here `to` (a `0x` address), `data`, `value` and `gasLimit` are all required. |
| `permitSignature` | object | yes | The object returned by Sign EIP-712 Permit: `v` (27 or 28), `r`, `s`, `deadline`, `nonce`, `permitRequired`. Its fields are optional in the schema; permit checks are skipped when `permitRequired` is `false` or `r`/`s` are all zeros. |
| `signingMode` | string | no | `server` (default). See the note below about `client`. |
| `isTestnet` | boolean | no | Accepted; only logged by this handler. |
| `toAmount` | string | no | Stored in transaction metadata and echoed as `toAmount` in a completed response (`"0"` when omitted). |
| `fromTokenSymbol` | string | no | Stored in transaction metadata. |
| `fromTokenDecimals` | integer | no | Stored in transaction metadata. |
| `toTokenSymbol` | string | no | Stored in transaction metadata. |
| `toTokenDecimals` | integer | no | Stored in transaction metadata. |
| `toTokenAddress` | string | no | Stored in transaction metadata. |

**Responses**

`200` Completed

```json
{
  "requiresClientSigning": false,
  "transactionId": "…",
  "txHash": "0x…",
  "status": "confirmed",
  "executionMethod": "gelato-sponsored",
  "routingMethod": "lifi",
  "gasSponsored": true,
  "gasCostWei": "…",
  "gasCostDeducted": "…",
  "netAmountReceived": "0",
  "fromAmount": "1000000000000000000",
  "toAmount": "…",
  "gasUsed": "…",
  "gasPrice": "…"
}
```

`200` Needs signature

```json
{
  "requiresClientSigning": true,
  "transactionId": "…",
  "preparedTxId": "…",
  "txHashToSign": "0x…",
  "chainId": 100,
  "nonce": 12,
  "maxFeePerGas": "…",
  "maxPriorityFeePerGas": "…",
  "gasLimit": "…",
  "expiresAt": "…"
}
```

`422` Daily Limit

```json
{
  "statusCode": 422,
  "error": "ValidationError",
  "message": "Daily gasless swap limit exceeded. Maximum 5 swaps per day.",
  "details": { "currentCount": 5, "limit": 5 }
}
```

`404` Not Found

```json
{
  "statusCode": 404,
  "error": "NotFoundError",
  "message": "Wallet not found"
}
```

> ℹ️ The schema also accepts `signingMode: "client"`, which requires `permitSignature.preparedPermitId` and a `metaTxSignature` object (`signature`, `preparedMetaTxId`). The current handler does not read `signingMode`, `preparedPermitId` or `metaTxSignature`, and no swap endpoint issues those ids — use the default server mode.

## Responses by Path

- **Completed** (`requiresClientSigning: false`) — always for custodial wallets. Non-custodial wallets get it too when their xDAI balance does not cover the swap gas, or when Aurea's relayer is low on funds; their swap is then submitted through the sponsored relay. The call returns after the swap transaction is mined, with `status: "confirmed"`, `executionMethod` (`self-pay` or `gelato-sponsored`), `routingMethod` (`direct`, `custom-executor` or `lifi`) and `gasSponsored` (`true` for `gelato-sponsored`). `gasCostWei` and `gasCostDeducted` both equal `gasUsed` × `gasPrice`. `netAmountReceived` is currently always `"0"`; read the wallet balance for the xDAI actually received.
- **Needs signature** (`requiresClientSigning: true`) — non-custodial wallets (`client_side`, `client_side_pending`, `mpc_tss`) that hold enough xDAI to pay the swap gas. If the allowance is insufficient, Aurea first submits the permit from its relayer. It then prepares the swap as an unsigned EIP-1559 transaction from the user's wallet. Sign `txHashToSign`, call [Broadcast Gasless Tx](https://docs.aureahub.com/docs/swap-broadcast-gasless.md), and track the swap with `transactionId`.

## Limits & Errors

- `503` `Gasless swaps are currently disabled`.
- `422` daily limit per user — `Daily gasless swap limit exceeded. Maximum N swaps per day.` N is deployment-specific and defaults to 5.
- `422` permit problems — `Permit deadline has expired`, `Permit nonce mismatch. Expected …, got …`, `Invalid permit signature`. Permits from Sign EIP-712 Permit have a 15-minute deadline.
- `400` `Estimated gas cost (… wei) exceeds maximum allowed (… wei)` — the maximum is deployment-specific and defaults to 0.01 xDAI.
- `400` `Insufficient EUR.e balance. Required: … wei, Available: … wei`.
- `400` `Request validation failed` — the body does not match the schema.
- `404` `Wallet not found`.
- If execution fails after the transaction record was created, the record is marked `failed` and the response is a blockchain error carrying `code` and `retryable`; its HTTP status depends on the error.

## Implementation

```javascript
const API = 'https://api.aureahub.com';
const EURE = '0x420CA0f9B9b604cE0fd9C18EF134C705e5Fa3430'; // EUR.e on Gnosis — Sign Permit reads this contract
const XDAI = '0x0000000000000000000000000000000000000000';

async function post(token, path, body) {
  const res = await fetch(API + path, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
    body: JSON.stringify(body)
  });
  const json = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${json.code ?? ''}: ${json.message}`);
  return json;
}

// signDigest(hash) must return { v, r, s } with v = 0 or 1 (non-custodial wallets only)
async function gaslessSwap(token, walletId, amountWei, signDigest) {
  const quote = await post(token, '/v1/swap/quote', {
    walletId, fromChain: 'gnosis', toChain: 'gnosis',
    fromToken: EURE, toToken: XDAI,
    fromAmount: amountWei // forwarded to LI.FI unchanged
  });
  if (!quote.gasless) throw new Error('Gasless swap not available for this quote');

  const permitSignature = await post(token, '/v1/swap/sign-permit', {
    walletId,
    quoteId: quote.quoteId,
    spenderAddress: quote.transactionData.to,
    amount: amountWei
  });

  const result = await post(token, '/v1/swap/execute-gasless', {
    walletId,
    quoteId: quote.quoteId,
    amount: amountWei,
    tokenAddress: EURE,
    transactionData: quote.transactionData,
    permitSignature,
    toAmount: quote.toAmount,
    toTokenSymbol: quote.toToken.symbol
  });

  if (!result.requiresClientSigning) return result; // completed, status 'confirmed'

  const { v, r, s } = await signDigest(result.txHashToSign);
  const sent = await post(token, '/v1/swap/broadcast-gasless', { preparedTxId: result.preparedTxId, v, r, s });
  return { transactionId: result.transactionId, txHash: sent.txHash }; // poll /v1/transactions/{id}/status
}
```

---

Web version: https://docs.aureahub.com/#swap-gasless
