# 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. |

**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)"
}
```

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

## 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. |
| 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 | TX_SUBMISSION_FAILED | The relayer could not submit a gasless send (`retryable: true`). |

---

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