> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spiralstake.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing the transaction

> How to execute the unsigned bundle a build returns.

Every build returns an unsigned bundle:

```json theme={null}
{
  "approvals": [{ "to": "0x…token", "data": "0x…" }],
  "tx": { "to": "0x…contract", "data": "0x…", "value": "0" },
  "meta": { "expiresAt": "…", "signingUrl": "https://app.spiralstake.xyz/…" }
}
```

## Order of operations

<Steps>
  <Step title="Send approvals first">
    Sign and broadcast each entry in `approvals[]` from the user's wallet. Empty for native ETH.
  </Step>

  <Step title="Then the transaction">
    Sign and broadcast `tx` (`to`, `data`, `value`) from the same wallet.
  </Step>
</Steps>

## Two paths

<CardGroup cols={2}>
  <Card title="In-app signing" icon="signature">
    Prompt the connected wallet (wagmi/viem/ethers, WalletConnect, or a Safe) with the raw `approvals` + `tx`. Full control of your UX.
  </Card>

  <Card title="Hosted handoff" icon="link">
    Redirect the user to `meta.signingUrl` — the Spiral app, prefilled, where they review and sign. Zero signing code on your side.
  </Card>
</CardGroup>

## Freshness

<Warning>
  `meta.expiresAt` marks when the embedded swap calldata goes stale (\~60 seconds). Build **right before** the user signs. If they hesitate past `expiresAt`, rebuild — signing a stale bundle will simply revert on-chain (funds are never at risk, but the transaction fails).
</Warning>

## Safety guarantees

* Approvals are scoped to the exact contract, for the exact amount — never infinite.
* Every swap carries a `minTokenOut` floor (≥ 99% of quote) — it can't be sandwiched.
* The transaction targets the strategy you chose and is bound to `userAddress`.
