# Sign EIP-712 Permit

Gasless swaps, step 2 — Aurea produces the EUR.e ERC-2612 permit for the wallet and returns its signature components.

## Overview

This endpoint does **not** return typed data for the client to sign. Aurea signs the EIP-712 `Permit` itself and returns `{ v, r, s, deadline, nonce, permitRequired }`. Pass that object unchanged as `permitSignature` to [Execute Gasless](https://docs.aureahub.com/docs/swap-gasless.md).

1. Aurea reads the wallet's current EUR.e allowance for `spenderAddress`.
2. If the allowance already covers `amount`, no permit is needed and nothing is signed. The response has `permitRequired: false`, `v: 27`, all-zero `r` and `s`, the current permit `nonce` and a `deadline` 15 minutes ahead. This path works for any wallet, including non-custodial ones.
3. Otherwise Aurea signs with the wallet key it holds, using the token's current permit nonce and a `deadline` 900 seconds (15 minutes) from now, and returns `permitRequired: true`. If Aurea holds no key material for the wallet, the request fails with `400` `Cannot sign permit for watch-only wallet`.

> ⚠️ Use `spenderAddress` = the quote's `transactionData.to` and the same `amount` you will send to Execute Gasless — Execute Gasless validates the permit against those values. `quoteId` is only logged, and `isTestnet` is not passed to the signer. Gasless swaps must be enabled on the deployment; otherwise the endpoint returns `503`.

## Endpoint

### `POST /v1/swap/sign-permit`

Authentication: bearer token required.

Signs an ERC-2612 permit for EUR.e server-side and returns v, r, s, deadline and nonce.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `walletId` | string (uuid) | yes | Wallet that owns the EUR.e. Must belong to the authenticated user. |
| `quoteId` | string | yes | Quote the permit is for. Only logged. |
| `spenderAddress` | string | yes | Permit spender, `0x` + 40 hex characters — the quote's `transactionData.to`. |
| `amount` | string | yes | Permit value in wei, digits only (`^\d+$`). |
| `isTestnet` | boolean | no | Accepted; not passed to the signer. |

**Responses**

`200` Signed

```json
{
  "v": 28,
  "r": "0x…",
  "s": "0x…",
  "deadline": 1757585700,
  "nonce": 3,
  "permitRequired": true
}
```

`200` Allowance sufficient

```json
{
  "v": 27,
  "r": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "s": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "deadline": 1757585700,
  "nonce": 3,
  "permitRequired": false
}
```

`400` Bad Request

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "Cannot sign permit for watch-only wallet"
}
```

`404` Not Found

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

When gasless swaps are disabled, the endpoint returns `503` with `Gasless swaps are currently disabled`. A body that does not match the schema returns `400` `Request validation failed`.

## Permit Details

- **Token** — allowance and nonce are read from the EUR.e contract `0x420CA0f9B9b604cE0fd9C18EF134C705e5Fa3430` on Gnosis (chain ID `100`).
- **Domain** — `name` `Monerium EURe`, `version` `1`, `chainId` `100`, `verifyingContract` `0x420CA0f9B9b604cE0fd9C18EF134C705e5Fa3430`. For wallets flagged as testnet, the signing domain uses `chainId` `10200` and `verifyingContract` `0xFD6F7A6a5c21A3f503EBaE7a473639974379c351`.
- **Type** — `Permit(address owner, address spender, uint256 value, uint256 nonce, uint256 deadline)`, where `owner` is the wallet address.

## Implementation

```javascript
// quote = response of POST /v1/swap/quote that contains a gasless object
async function getPermit(token, { walletId, quote, amountWei }) {
  const res = await fetch('https://api.aureahub.com/v1/swap/sign-permit', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
      walletId,
      quoteId: quote.quoteId,
      spenderAddress: quote.transactionData.to, // the permit is validated against this spender
      amount: amountWei                         // wei, digits only
    })
  });

  const body = await res.json();
  if (!res.ok) throw new Error(`${res.status}: ${body.message}`);

  // { v, r, s, deadline, nonce, permitRequired } — pass as permitSignature to /v1/swap/execute-gasless
  return body;
}
```

---

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