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

# Signing Flow Guide

> Register a passkey, create a wallet, sign a transaction, and broadcast — using the prepare/confirm pattern

<Warning>
  This guide results in real on-chain transactions when using production credentials. Use the sandbox environment (`https://testnet.relayer.fi/v1`) while testing. Blockchain transactions are irreversible once broadcast.
</Warning>

The Signing Kit uses a **prepare → stamp → confirm** pattern. Every signing operation (wallet creation, address creation, transaction signing) follows the same three steps:

1. **Prepare** — your backend calls the API to get an unsigned activity/transaction
2. **Stamp** — the user signs the payload with a passkey (WebAuthn, browser-side)
3. **Confirm** — your backend sends the stamped payload back; the enclave validates and processes it

The passkey stamping in step 2 must happen in a browser context that has access to WebAuthn. Server-only flows (cURL) can call prepare and confirm, but the stamping itself requires user interaction.

## Prerequisites

* A Relayer API key with the Signing module enabled (see [Authentication](/get-started/authentication))
* A user with a registered passkey in your workspace (see step 1 below)

<CodeGroup>
  ```bash Environment theme={null}
  export RELAYER_API_KEY="rk_client_key_v1_your_key_here"
  export RELAYER_BASE_URL="https://testnet.relayer.fi/v1"  # sandbox; use https://api.relayer.fi/v1 for production
  ```
</CodeGroup>

## Step 1 — Register a passkey (one-time setup)

The first time a user enrolls in your workspace, they register a passkey. This creates the wallet workspace and binds the user's WebAuthn credential to it.

The full flow happens in the browser via the Relayer dashboard or your own WebAuthn-capable frontend. The API calls involved:

```
POST /v1/signing/passkeys/challenge   → returns WebAuthn challenge
POST /v1/signing/passkeys/register    → registers the credential and creates the workspace
```

<Note>
  For end-user onboarding (Embedder integrators), the Widget Kit handles passkey enrollment automatically. See [Widget Kit](/widget/overview).
</Note>

## Step 2 — Create a wallet

<Steps>
  <Step title="Prepare the wallet creation">
    Your backend asks the API for an unsigned wallet-creation activity.

    ```bash cURL theme={null}
    curl -X POST $RELAYER_BASE_URL/v1/signing/wallets/prepare \
      -H "Authorization: ApiKey $RELAYER_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "name": "My Wallet" }'
    ```

    The response includes the unsigned activity payload your frontend needs to stamp.
  </Step>

  <Step title="User stamps with passkey">
    Your frontend prompts the user with a WebAuthn biometric challenge (Face ID, fingerprint, security key). The browser produces a stamped activity.

    ```typescript Frontend (browser) theme={null}
    // Pseudo-code — exact stamping API depends on the WebAuthn library
    const stamped = await passkey.stamp(unsignedActivity);
    ```
  </Step>

  <Step title="Confirm the wallet creation">
    Your backend sends the stamped activity back to the API.

    ```bash cURL theme={null}
    curl -X POST $RELAYER_BASE_URL/v1/signing/wallets/confirm \
      -H "Authorization: ApiKey $RELAYER_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "stampedActivity": "..."
      }'
    ```

    The response contains the `walletId`. Save it.
  </Step>
</Steps>

## Step 3 — Generate an address

Same prepare/stamp/confirm pattern, this time for `CREATE_WALLET_ACCOUNTS`:

```
POST /v1/signing/wallets/accounts/prepare   { walletId, curve, addressFormat }
  → user stamps the returned activity with their passkey
POST /v1/signing/wallets/accounts/confirm   { stampedActivity }
  → returns { address, addressId, walletId }
```

<Tip>
  Use `addressFormat: "ADDRESS_FORMAT_ETHEREUM"` for all EVM-compatible networks (Ethereum, Polygon, Arbitrum, Base, etc.). Use `addressFormat: "ADDRESS_FORMAT_SOLANA"` for Solana.
</Tip>

## Step 4 — Sign a transaction

<Steps>
  <Step title="Prepare the transaction">
    ```bash cURL theme={null}
    curl -X POST $RELAYER_BASE_URL/v1/transactions/prepare \
      -H "Authorization: ApiKey $RELAYER_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "walletId": "wallet_abc123",
        "from": "0xYourWalletAddress",
        "to": "0xRecipientAddress",
        "value": "10000000000000000",
        "network": "sepolia"
      }'
    ```

    The response includes:

    * `transactionId` — store this for later
    * `unsignedTransaction` (hex) — pass to the frontend for passkey signing

    The transaction is persisted with status `awaiting_signature`.
  </Step>

  <Step title="User signs with passkey">
    Your frontend signs the unsigned transaction hex with the user's passkey:

    ```typescript Frontend (browser) theme={null}
    const signedTx = await passkey.signTransaction(unsignedTransaction);
    ```
  </Step>

  <Step title="Confirm — and broadcast">
    Send the signed hex to confirm. The API validates the signature against the original unsigned transaction via hash comparison, persists the signed transaction, and broadcasts to the blockchain.

    ```bash cURL theme={null}
    curl -X POST $RELAYER_BASE_URL/v1/transactions/confirm \
      -H "Authorization: ApiKey $RELAYER_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "transactionId": "tx_def456",
        "signedTransaction": "0x..."
      }'
    ```

    The response contains `txHash` once the transaction is in the mempool.
  </Step>

  <Step title="Check status">
    Poll the broadcast status to confirm on-chain confirmation.

    ```bash cURL theme={null}
    curl $RELAYER_BASE_URL/v1/transactions/broadcast/$TRANSACTION_ID \
      -H "Authorization: ApiKey $RELAYER_API_KEY"
    ```

    **Status values:** `broadcast`, `confirmed`, `failed`. Once confirmed, the response includes `blockNumber`.
  </Step>
</Steps>

## Separating signing from broadcasting

If you need to gate the broadcast step (multi-step approval, scheduled submission), don't use confirm-with-broadcast. Instead:

1. `POST /v1/transactions/prepare` → unsigned tx
2. User stamps with passkey on the frontend
3. `POST /v1/transactions/confirm` → signs and persists (no broadcast yet) — TODO: confirm whether your tenant supports this gated mode
4. `POST /v1/transactions/broadcast/{transactionId}` later, when conditions are met

Alternatively, sign externally and submit raw:

```bash cURL theme={null}
curl -X POST $RELAYER_BASE_URL/v1/transactions/broadcast/raw \
  -H "Authorization: ApiKey $RELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "rawSignedTransaction": "0x...", "network": "mainnet" }'
```

## Cancelling before broadcast

A transaction in `awaiting_signature` status can be cancelled:

```bash cURL theme={null}
curl -X DELETE $RELAYER_BASE_URL/v1/transactions/sign/$TRANSACTION_ID \
  -H "Authorization: ApiKey $RELAYER_API_KEY"
```

<Warning>
  Once `POST /v1/transactions/confirm` has broadcast the transaction, cancellation is no longer possible. The transaction is in the mempool.
</Warning>

## Approval gate (high-value transactions)

If your workspace has approval enabled and the transaction value exceeds the threshold, `POST /v1/transactions/confirm` returns **HTTP 202** with `{ approvalId }` instead of broadcasting. The transaction stays pending until an authorized team member stamps approval:

```
GET  /v1/signing/approvals                              — Approver lists pending requests
POST /v1/signing/approvals/{id}/approve-with-passkey    — Approver stamps approval
```

Once approved, the transaction is broadcast automatically.

## Next Steps

<CardGroup cols={2}>
  <Card title="Endpoints" icon="code" href="/signing/endpoints">
    Full endpoint reference for wallets, transactions, passkeys, recovery, policies, and approvals.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Interactive schemas and try-it playground.
  </Card>

  <Card title="Glossary" icon="book-open" href="/shared/glossary">
    Wallet, passkey, policy, and other domain terms.
  </Card>

  <Card title="Error Reference" icon="triangle-exclamation" href="/shared/error-reference">
    API error codes and handling patterns.
  </Card>
</CardGroup>
