# Broadcast Transaction

Submit the client's signature for a non-custodial send so Aurea can put it on-chain.

## Overview

When [Create Transaction](https://docs.aureahub.com/docs/tx-create.md) returns `requiresClientSigning: true`, sign the returned hash on the client and send the signature components `v`, `r` and `s` here. The API rebuilds the stored transaction (or permit), checks that the signature recovers to the wallet address and submits it.

It handles both standard and gasless sends, and updates the transaction record.

## Broadcast Signed Transaction

### `POST /v1/transactions/{id}/broadcast`

Authentication: bearer token required.

Checks a client signature for a prepared send and submits the transaction on-chain.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes | Transaction `id` returned by Create 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 the Create Transaction response. |
| `v` | integer | yes | `0` or `1` if you signed `txHashToSign`; `27` or `28` if you signed `permitHashToSign` (gasless). |
| `r` | string | yes | `0x` followed by 64 hex characters. |
| `s` | string | yes | `0x` followed by 64 hex characters. |

**Responses**

`200` OK

```json
{
  "id": "0b9d4c1e-3f6a-4b8e-9d2c-7a5e1f3b6c90",
  "txHash": "0x7f3a…",
  "chain": "gnosis",
  "type": "send",
  "status": "pending",
  "fromAddress": "0x2f4B…",
  "toAddress": "0x52908400098527886E0F7030069857D2E4169EE7",
  "token": { "address": null, "symbol": "xDAI", "name": null, "decimals": 18 },
  "amount": "10000000000000000",
  "amountUsd": null,
  "amountEur": null,
  "fee": { "amount": null, "usd": null },
  "gasUsed": null,
  "gasPrice": null,
  "blockNumber": null,
  "blockTimestamp": null,
  "createdAt": "2026-09-11T10:00:00.000Z",
  "updatedAt": "2026-09-11T10:00:20.000Z"
}
```

`400` Bad Request

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "Transaction is not in pending state (current: confirmed)"
}
```

`409` Superseded

```json
{
  "statusCode": 409,
  "error": "BlockchainError",
  "message": "A newer transaction was prepared for this wallet: this one can no longer be broadcast",
  "code": "PREPARED_TX_SUPERSEDED",
  "retryable": false,
  "details": { "code": "PREPARED_TX_SUPERSEDED", "retryable": false }
}
```

`409` Cancelled

```json
{
  "statusCode": 409,
  "error": "ConflictError",
  "message": "This operation was cancelled: what it prepared can no longer be broadcast",
  "details": { "code": "OPERATION_CANCELLED", "operationId": "1d7a3c52-8e4b-4f19-a0c6-2b9e7d5f3a18" }
}
```

`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 }
}
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/transactions/0b9d4c1e-3f6a-4b8e-9d2c-7a5e1f3b6c90/broadcast \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "preparedTxId": "5e0f7b8a-1c2d-4e3f-8a9b-0c1d2e3f4a5b",
    "v": 1,
    "r": "0x…64 hex characters…",
    "s": "0x…64 hex characters…"
  }'
```

What happens:

1. The transaction must belong to the caller (`404` `Transaction not found`) and still be `pending`, and `preparedTxId` must be the one stored on it (`400`).
2. **Standard send:** the API re-derives the signing hash from the stored unsigned transaction, recovers the signer from `v`, `r`, `s` and requires it to equal the wallet address. `maxFeePerGas` must not exceed the configured cap (500 gwei by default). The signed transaction is then sent to the network.
3. **Gasless send:** the API re-derives the EIP-712 permit digest and checks the signer; a relayer submits the permit-and-transfer transaction and waits for one confirmation.
4. The record is updated with the on-chain `txHash` and `status: "pending"`. Follow it with [Get Tx Status](https://docs.aureahub.com/docs/tx-status.md).

A prepared send can be broadcast once and expires 5 minutes after it was created. Rate limit: 20 requests per minute.

- **A signature from another key spends nothing** on a standard send: it is refused with `422` `WRONG_SIGNER`, and the same `preparedTxId` with the right signature still goes through, once.
- **A gasless permit is the exception:** a permit signed by another key is refused with `422` `WRONG_SIGNER` and spent, so the user can prepare a new send straight away with [Create Transaction](https://docs.aureahub.com/docs/tx-create.md).
- With an `Idempotency-Key`, the same key and request answer with the first broadcast instead of sending it again — see [Idempotency](https://docs.aureahub.com/docs/idempotency.md).
- **One unsigned preparation per wallet and chain.** Preparing another send, approval or swap for the same wallet on the same chain while a standard send is still unsigned gives the newer one its nonce: the send can no longer be broadcast (`409` `PREPARED_TX_SUPERSEDED`), and nothing is spent. Broadcast the newer one.
- `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.
- **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)).

## Signing

Sign the 32-byte hash itself — do not add an EIP-191 message prefix. `txHashToSign` is the keccak-256 hash of the unsigned EIP-1559 transaction; `permitHashToSign` is an EIP-712 digest. With ethers v6:

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

// tx = response of POST /v1/transactions/ with requiresClientSigning === true
function signPreparedSend(tx, privateKey) {
  const key = new SigningKey(privateKey);
  if (tx.gasless) {
    const sig = key.sign(tx.permitHashToSign);
    return { preparedTxId: tx.preparedTxId, v: sig.v, r: sig.r, s: sig.s };        // v: 27 or 28
  }
  const sig = key.sign(tx.txHashToSign);
  return { preparedTxId: tx.preparedTxId, v: sig.yParity, r: sig.r, s: sig.s };    // v: 0 or 1
}

async function broadcast(token, tx, privateKey) {
  const res = await fetch('https://api.aureahub.com/v1/transactions/' + tx.id + '/broadcast', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' },
    body: JSON.stringify(signPreparedSend(tx, privateKey)),
  });
  const body = await res.json();
  if (!res.ok) throw new Error((body.code || res.status) + ': ' + body.message);
  return body; // transaction with txHash and status 'pending'
}
```

> ℹ️ Wallets with `keyManagementScheme` `mpc_tss` produce the signature through the 2-of-3 MPC ceremony rather than with a single local key. The values you submit are the same: `preparedTxId`, `v`, `r`, `s`.

## Errors

Errors from this endpoint that carry a `code` have the body `{ "statusCode", "error": "BlockchainError", "message", "code", "retryable", "details" }`.

| Status | code | Cause |
| --- | --- | --- |
| 400 | — | `Transaction is not in pending state (current: …)`; `preparedTxId does not match the transaction record` (gasless: `… the stored preparedPermitId`); `Prepared transaction not found`. |
| 404 | — | `Transaction not found` — missing, or not owned by the caller. |
| 404 | PREPARED_TX_NOT_FOUND | The prepared transaction or gasless permit no longer matches a stored row. A gasless permit that belongs to a different transaction returns `422` with this code. |
| 409 | PREPARED_TX_ALREADY_USED | Already broadcast — or, for a gasless send, its permit was spent by a signature from another key. |
| 409 | PREPARED_TX_SUPERSEDED | `A newer transaction was prepared for this wallet: this one can no longer be broadcast` — standard send: a newer preparation for the same wallet and chain took its nonce while it was unsigned. Checked after `PREPARED_TX_NOT_FOUND`, `PREPARED_TX_ALREADY_USED` and `PREPARED_TX_EXPIRED`. |
| 409 | PREPARED_TX_CANCELLED | `This transaction's operation was cancelled: it can no longer be broadcast` — its initiator cancelled it ([Cancel an Operation](https://docs.aureahub.com/docs/operation-cancel.md)). Nothing is marked used. Checked after `PREPARED_TX_ALREADY_USED`. |
| 409 | OPERATION_CANCELLED | The same, found from its operation before anything runs: `This operation was cancelled: what it prepared can no longer be broadcast`, as `"error": "ConflictError"` with `details.code` and `details.operationId`. |
| 410 | PREPARED_TX_EXPIRED | More than 5 minutes since creation. Create a new send. |
| 422 | — | Body failed validation, for example `v` other than 0, 1, 27 or 28 (`Request validation failed`). |
| 422 | SIGNATURE_DECODE_FAILED | `v`, `r`, `s` could not be decoded. |
| 422 | WRONG_SIGNER | The signature does not recover to the wallet address. |
| 422 | GAS_CAP_EXCEEDED | Standard send whose `maxFeePerGas` is above the cap (500 Gwei by default): `maxFeePerGas (… Gwei) exceeds the allowed cap of 500 Gwei`. Nothing is sent. |
| 500 | TX_SUBMISSION_FAILED | The relayer could not submit a gasless send (`retryable: true`). |

---

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