# Broadcast Approve Tx

Non-custodial approvals, step 2 — submit the signature for the `txHashToSign` returned by Approve Token.

## Overview

When [Approve Token](https://docs.aureahub.com/docs/swap-approve.md) returns `requiresClientSigning: true`, sign its `txHashToSign` with the wallet key and send the signature here. You do not send a signed raw transaction. The API rebuilds the EIP-1559 transaction it prepared, checks that the signature recovers to the wallet's address, and broadcasts it.

- `v` is the recovery parity, `0` or `1`; `r` and `s` are `0x` + 64 hex characters.
- The prepared transaction expires 5 minutes after Approve Token created it (`expiresAt`).
- The response is `{ txHash, status: "pending" }`. Approvals do not create a transaction record, so confirm the approval with [Check Allowance](https://docs.aureahub.com/docs/swap-allowance.md) before executing the swap.
- Rate limit: 10 requests per 10 seconds.

- **A signature from another key spends nothing:** it is refused with `422` `WRONG_SIGNER`, and the same `preparedTxId` with the right signature still goes through, once.
- **The node's refusal** (a nonce already used, a transaction already known, a fee too low…) answers `422` with `code` `RPC_ERROR`, the node's own message and `details.rpcCode`. The prepared transaction has been used: call Approve Token again to prepare a new one.

## Endpoint

### `POST /v1/swap/broadcast-approve`

Authentication: bearer token required.

Validates a client signature for a prepared ERC-20 approval and broadcasts the transaction.

**Headers**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | no | Optional — required for a tenant on the institutional security profile (`400` `IDEMPOTENCY_KEY_REQUIRED` without it). Up to 255 letters, digits, - or _. The same key with the same request answers with the first answer (`idempotency-replayed: true`) instead of running it again; see [Idempotency](https://docs.aureahub.com/docs/idempotency.md) |

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `preparedTxId` | string (uuid) | yes | `preparedTxId` from Approve Token. |
| `v` | integer | yes | Signature recovery parity: `0` or `1`. |
| `r` | string | yes | `0x` + 64 hex characters. |
| `s` | string | yes | `0x` + 64 hex characters. |

**Responses**

`200` OK

```json
{
  "txHash": "0x…",
  "status": "pending"
}
```

`404` Not Found

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

`422` Wrong Signer

```json
{
  "statusCode": 422,
  "error": "BlockchainError",
  "message": "Signature does not correspond to the expected wallet address",
  "code": "WRONG_SIGNER",
  "retryable": false,
  "details": { "code": "WRONG_SIGNER", "retryable": false }
}
```

`422` Node Refused

```json
{
  "statusCode": 422,
  "error": "BlockchainError",
  "message": "nonce too low: next nonce 8, tx nonce 7",
  "code": "RPC_ERROR",
  "retryable": false,
  "details": { "code": "RPC_ERROR", "retryable": false, "rpcCode": -32000 }
}
```

The node's message and code above are an example: they are whatever the network's node answered.

## Errors

- `400` `Request validation failed` — the body does not match the schema.
- `404` `Prepared transaction not found` — unknown `preparedTxId`, or it belongs to another user.
- `409` `PREPARED_TX_ALREADY_USED` — this prepared transaction was already submitted. `409` `PREPARED_TX_SUPERSEDED` — a newer send, approval or swap was prepared for the same wallet on the same chain while this one was unsigned (`A newer transaction was prepared for this wallet: this one can no longer be broadcast`); nothing is spent.
- `409` `OPERATION_CANCELLED` — its operation was cancelled by its initiator ([Cancel an Operation](https://docs.aureahub.com/docs/operation-cancel.md)): `This operation was cancelled: what it prepared can no longer be broadcast`, with `details.operationId`; from the prepared transaction itself, `409` `PREPARED_TX_CANCELLED` (`This transaction's operation was cancelled: it can no longer be broadcast`). Nothing is spent: prepare a new one.
- `410` `PREPARED_TX_EXPIRED` — more than 5 minutes have passed; call Approve Token again.
- `422` `SIGNATURE_DECODE_FAILED` — `v`, `r`, `s` could not be decoded. `422` `WRONG_SIGNER` — the signature does not recover to the wallet address; nothing is spent. `422` `GAS_CAP_EXCEEDED` — the prepared `maxFeePerGas` is above the cap (500 Gwei by default). `422` `RPC_ERROR` — the node refused the transaction.
- **Institutional tenants only:** this route needs an `Idempotency-Key`, and the tenant's policy decides again before the transaction is sent. It never holds anything: a rule asking for an approval refuses with `403` `POLICY_APPROVAL_REQUIRED`, unless the operation was approved for that rule ([Approvals](https://docs.aureahub.com/docs/guide-approvals.md), [Policy Sets](https://docs.aureahub.com/docs/guide-policy-sets.md)).

Blockchain errors (`409`, `410`, `422`) carry `code` and `retryable` next to `statusCode`, `error` and `message`.

## Implementation

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

// prepared = response of POST /v1/swap/approve with requiresClientSigning: true
async function broadcastApproval(token, prepared, privateKey) {
  const sig = new SigningKey(privateKey).sign(prepared.txHashToSign);

  const res = await fetch('https://api.aureahub.com/v1/swap/broadcast-approve', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
      preparedTxId: prepared.preparedTxId,
      v: sig.yParity, // 0 or 1
      r: sig.r,
      s: sig.s
    })
  });

  const body = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${body.code ?? ''}: ${body.message}`);
  return body.txHash; // then poll /v1/swap/check-allowance
}
```

---

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