# Signing Primitives

Every cryptographic signature Aurea asks the client to produce, with a copy-pasteable TypeScript example.

## Overview

Aurea never holds the private keys for non-custodial users, so the client is responsible for producing well-formed signatures. Each flow uses exactly one signing primitive — pick the right one.

## SIWE (EIP-4361)

**When:** signing in to Gnosis Pay for EURe — `GET /v1/gnosis/auth/nonce?address=…`, then `POST /v1/gnosis/auth/challenge`, both with the user's bearer token. **Format:** an EIP-4361 (Sign-In With Ethereum) message containing the nonce, with the domain and URI Gnosis Pay expects. **Signer:** `personal_sign` over the message. Submit the exact message you signed, together with the signature and the address.

```typescript
import { SiweMessage } from 'siwe';

const auth = { Authorization: `Bearer ${token}` };

// 1. Nonce for the signing address
const nonceResponse = await fetch(`/v1/gnosis/auth/nonce?address=${wallet.address}`, { headers: auth })
  .then(r => r.json());

// 2. Build and sign the EIP-4361 message
const message = new SiweMessage({
  domain: GNOSIS_PAY_SIWE_DOMAIN, // as required by Gnosis Pay
  uri: GNOSIS_PAY_SIWE_URI,       // as required by Gnosis Pay
  address: wallet.address,
  version: '1',
  chainId: 100,                   // Gnosis
  nonce: NONCE,                   // from nonceResponse
  issuedAt: new Date().toISOString(),
}).prepareMessage();
const signature = await wallet.signMessage(message);

// 3. Submit exactly what was signed
await fetch('/v1/gnosis/auth/challenge', {
  method: 'POST',
  headers: { ...auth, 'Content-Type': 'application/json' },
  body: JSON.stringify({ message, signature, address: wallet.address })
});
```

## EIP-712 Typed Data

**When:** ERC-2612 permits. Note that for gasless swaps `POST /v1/swap/sign-permit` does not return typed data: Aurea signs the EUR.e permit server-side and returns `{ v, r, s, deadline, nonce, permitRequired }`, which is passed as `permitSignature` to `POST /v1/swap/execute-gasless`. **Format:** ERC-2612 `Permit` struct on the token contract.

```typescript
const domain = {
  name: 'USD Coin',
  version: '2',
  chainId: 1,
  verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
};

const types = {
  Permit: [
    { name: 'owner',    type: 'address' },
    { name: 'spender',  type: 'address' },
    { name: 'value',    type: 'uint256' },
    { name: 'nonce',    type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
  ]
};

const value = {
  owner:    wallet.address,
  spender:  ROUTER_ADDRESS,
  value:    amount,      // smallest units, string
  nonce:    permitNonce, // from the token contract
  deadline: Math.floor(Date.now() / 1000) + 3600,
};

const signature = await wallet.signTypedData(domain, types, value);
// Split with ethers' Signature.from(signature) for { v, r, s }. Note: POST /v1/swap/broadcast-gasless takes no permit — only { preparedTxId, v, r, s } for a prepared swap transaction
```

## EIP-191 personal_sign

**When:** DApp `sign-personal-message` (for wallets without a server-held key, sign the returned `messageHash` as a raw digest — it already includes this prefix), Monerium link-message, IBAN signing message. **Format:** `"\x19Ethereum Signed Message:\n" + len(message) + message` (`ethers` does this for you).

```typescript
const { message } = await fetch('/v1/gnosis/monerium/link-message', {
  headers: { Authorization: `Bearer ${token}` }
}).then(r => r.json());

const signature = await wallet.signMessage(message);
// POST /v1/gnosis/monerium/link-safe { signature }
```

## EIP-1559 Transactions

**When:** non-custodial EVM sends. `POST /v1/transactions/` builds a type-2 (EIP-1559) transaction server-side and returns `txHashToSign` (keccak-256 of the unsigned transaction); the client signs that hash and submits only `v`, `r`, `s`.

```typescript
import { SigningKey } from 'ethers';

// 1. Create: the API builds and stores the unsigned EIP-1559 transaction
const tx = await fetch('/v1/transactions/', {
  method: 'POST',
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ walletId, chain: 'gnosis', toAddress, amount: '10000000000000000' })
}).then(r => r.json());
// tx: { id, requiresClientSigning: true, gasless: false, preparedTxId, txHashToSign, nonce, maxFeePerGas, maxPriorityFeePerGas, chainId, expiresAt, ... }

// 2. Client signs the 32-byte hash (no EIP-191 prefix)
const sig = new SigningKey(privateKey).sign(tx.txHashToSign);

// 3. Broadcast the signature (v = 0 or 1) before tx.expiresAt
await fetch(`/v1/transactions/${tx.id}/broadcast`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ preparedTxId: tx.preparedTxId, v: sig.yParity, r: sig.r, s: sig.s })
});
```

## Solana VersionedTransaction

**When:** not for swaps. Solana-source swaps are signed by Aurea: `POST /v1/swap/execute` (alias `POST /v1/swap/execute-solana`) receives the base64 `VersionedTransaction` from the quote's `transactionData.data`, signs it with the wallet's server-held keypair and returns the Solana signature as `txHash` with status `confirmed`. Non-custodial Solana wallets are rejected with `400`, and `POST /v1/swap/broadcast` does not accept Solana transactions. **Format:** base64-encoded `VersionedTransaction`.

```typescript
// Solana-source swap (quote.sourceChainType === 'solana'): no client signature
const res = await fetch('https://api.aureahub.com/v1/swap/execute', {
  method: 'POST',
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    walletId,
    chain: 'solana',
    quoteId: quote.quoteId,
    transactionData: quote.transactionData // data = base64 VersionedTransaction from LI.FI
  })
}).then(r => r.json());
// res.status === 'confirmed'; res.txHash is the Solana signature
```

---

Web version: https://docs.aureahub.com/#signing
