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

# Verify swap settlement

> Check transaction acceptance, pending output, and the exact token record returned by a testnet claim.

Use this guide after the [SDK quickstart](./first-swap/sdk) or [CLI quickstart](./first-swap/cli). All examples select testnet and read existing state. Run them from the project directory that holds your saved account.

A completed trade has an accepted swap, an accepted claim for that swap, and returned token records available to the account. Check these separately:

| Check | What it establishes |
| - | - |
| Both transactions report `accepted` | The ledger accepted the swap and claim. |
| The swap and claim reference the same swap ID | The claim belongs to the trade you are checking. |
| `swap_outputs[swap_id]` is absent after acceptance | No pending output remains for that swap. Absence alone does not prove a swap ever existed. |
| An unspent ETH record matches the claim's output commitment and amount | The account can find the exact proceeds from this claim. A total balance alone cannot identify their source. |

Keep the swap transaction ID, claim transaction ID, swap ID, and actual received amount from the execution output. Use the swap ID including its `field` suffix. Do not substitute the quoted estimate or minimum for the received amount.

## Check both transactions

The public [Provable testnet explorer](https://testnet.explorer.provable.com/) lets you look up a transaction ID without a wallet. Its API returns the confirmed transaction wrapper, including `status`, `transaction`, and `finalize`. Replace each placeholder with your transaction ID:

```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
curl --fail-with-body --silent --show-error \
  "https://api.explorer.provable.com/v1/testnet/transaction/confirmed/SWAP_TRANSACTION_ID"
curl --fail-with-body --silent --show-error \
  "https://api.explorer.provable.com/v1/testnet/transaction/confirmed/CLAIM_TRANSACTION_ID"
```

Inspect `status` in each JSON response. HTTP success alone is not transaction acceptance.

| Result | Next action |
| - | - |
| `accepted` for both | Verify the matching output record below. |
| `rejected` for the swap | Inspect the rejection and input-record state before preparing a new trade. |
| Swap accepted, claim rejected | Recover the existing swap after checking whether its output remains claimable. |
| Not found, timeout, or another lookup error | The outcome is unknown. Check testnet, the ID, and connectivity, then repeat the lookup. Do not resubmit based on this result. |

For TypeScript SDK 0.11.1, the equivalent lookup is `await publicClient.getConfirmedTransaction({ id })`. Keep `publicClient` from `aleo.createAleoClient({ privateKey })`; the wallet client extended with `shieldSwapActions` does not expose this method by default. See [Transaction execution](../developers/transaction-execution#confirm-finalization) for polling and retry rules.

## Verify the output record

This TypeScript verifier targets the quickstarts' **USDCx to ETH testnet trade** and the 0.11.1 SDK packages. It checks the ETH proceeds; a trade with an input refund also needs its USDCx refund record checked. Run verification before spending or combining the returned ETH record. A later spend can make this unspent-record check fail even though settlement succeeded.

The verifier uses the configured ledger gateway and account scanner. It is independent of CLI history summaries, but is not a standalone cryptographic ledger proof. Record plaintext stays in memory and is never printed.

### 1. Load your existing account

Create `verify-client.ts` using the tab for the quickstart you followed. Do not run setup again or generate a new account.

<Tabs>
  <Tab title="TypeScript SDK">
    Install the package that provides the transaction types:

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    npm install @provablehq/veil-core@0.11.1
    ```

    Reuse the `client.ts` from the SDK quickstart:

    ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    export { client, publicClient } from "./client.ts"
    ```

    If you followed an earlier version of that guide, update these two lines in `client.ts` first:

    ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    const { walletClient, publicClient, account } = aleo.createAleoClient({ privateKey })
    export { account, publicClient }
    ```

    If you used the quickstart's JavaScript option, import `./client.js` instead and install `tsx` with `npm install --save-dev tsx`. Keep the verifier files as TypeScript and run them with the command below.
  </Tab>

  <Tab title="CLI">
    The TypeScript verifier imports the CLI session helper. Install its dependencies and `tsx` in the directory containing your saved `.shield-swap/` account:

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    npm init -y
    npm pkg set type=module
    npm install @provablehq/shield-swap-cli@0.11.1 \
      @provablehq/shield-swap-sdk@0.11.1 @provablehq/veil-core@0.11.1
    npm install --save-dev tsx
    ```

    The CLI's session helper loads the same saved testnet account. The standalone public action reads through that client's ledger transport:

    ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    import { loadSession } from "@provablehq/shield-swap-cli/session"
    import { getConfirmedTransaction } from "@provablehq/veil-core"

    export const { client } = await loadSession({ network: "testnet" })
    export const publicClient = {
      getConfirmedTransaction: (params: Parameters<typeof getConfirmedTransaction>[1]) =>
        getConfirmedTransaction(client, params),
    }
    ```
  </Tab>
</Tabs>

### 2. Create the verifier

Save this as `verify-settlement.ts`. It performs one verification pass. If confirmation or scanning has not caught up, it exits unsuccessfully; you can rerun it without creating a transaction.

```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
import assert from "node:assert/strict"
import type { Execution } from "@provablehq/veil-core"
import { parseTokenRecordInfo, parseUnits, formatUnits } from "@provablehq/shield-swap-sdk"
import { client, publicClient } from "./verify-client.ts"

const [swapTx, claimTx, swapId, receivedEth] = process.argv.slice(2)
assert(swapTx && claimTx && swapId && receivedEth,
  "Pass swap transaction ID, claim transaction ID, swap ID, and received ETH")
assert(/^\d+field$/.test(swapId), "Swap ID must include its field suffix")
assert(swapTx !== claimTx, "Swap and claim must be different transactions")

try {
  const swap = await publicClient.getConfirmedTransaction({ id: swapTx })
  const claim = await publicClient.getConfirmedTransaction({ id: claimTx })
  assert.equal(swap.status, "accepted", "Swap is not accepted")
  assert.equal(claim.status, "accepted", "Claim is not accepted")
  assert.equal(swap.transaction.id, swapTx, "Swap transaction ID mismatch")
  assert.equal(claim.transaction.id, claimTx, "Claim transaction ID mismatch")
  console.log("Swap: accepted; claim: accepted")

  const swapCalls = (swap.transaction.execution as Execution | undefined)?.transitions ?? []
  const claimCalls = (claim.transaction.execution as Execution | undefined)?.transitions ?? []
  const swapCall = swapCalls.find((t) =>
    t.program === "shield_swap.aleo" &&
    ["swap", "swap_multi_hop"].includes(t.function) &&
    t.outputs?.some((o) => o.type === "public" && o.value === swapId))
  const claimCall = claimCalls.find((t) =>
    t.program === "shield_swap.aleo" &&
    ["claim_swap_output", "claim_swap_output_no_refund"].includes(t.function) &&
    t.inputs?.[2]?.type === "public" && t.inputs[2].value === swapId)
  assert(swapCall && claimCall, "Transactions do not reference the supplied swap ID")

  const eth = await client.tokenData("ETH")
  const expected = parseUnits(receivedEth, eth.decimals)
  assert(expected > 0n, "Received ETH must be positive")
  // In these claim entry points, inputs 4 and 5 are token_out and amount_out.
  assert.equal(claimCall.inputs?.[4]?.value, eth.id, "Claim output token is not ETH")
  assert.equal(claimCall.inputs?.[5]?.value, `${expected}u128`,
    "Received amount does not match the accepted claim")

  const pending = await client.getSwapOutput({ swapId })
  assert.equal(pending, null, "Pending swap output still exists; check ledger state")
  console.log("Swap ID: matched; pending output: absent")

  const program = eth.underlyingProgram ?? eth.ammTokenProgram
  assert(program, "ETH token metadata has no output program")
  const commitments = new Set(claimCalls
    .filter((t) => t.program === program)
    .flatMap((t) => t.outputs ?? [])
    .filter((o) => ["record", "record_with_dynamic_id"].includes(o.type))
    .map((o) => o.id))
  assert(commitments.size > 0, "Claim has no ETH record output")

  const records = await client.requestRecords({
    program,
    statusFilter: "unspent",
    includePlaintext: true,
  })
  const matched = records.some((record) => {
    if (record.spent !== false || record.programName !== program ||
        !record.commitment || !commitments.has(record.commitment) ||
        !("recordPlaintext" in record) ||
        typeof record.recordPlaintext !== "string" || !record.recordPlaintext) return false
    const info = parseTokenRecordInfo(record.recordPlaintext)
    return info !== null && !info.recipientBound && info.amount === expected &&
      (info.tokenId === undefined || info.tokenId === eth.id)
  })
  assert(matched,
    "No matching unspent ETH record. Check scanner progress, account, or later spends")
  console.log(`ETH record: unspent; commitment matched; amount ${formatUnits(expected, eth.decimals)}`)
  console.log("VERIFIED: swap accepted, claim accepted, pending output cleared, ETH record available")
} catch (error) {
  console.error(error instanceof assert.AssertionError
    ? error.message
    : "Verification read failed. Check connectivity and inputs, then rerun verification.")
  process.exitCode = 1
}
```

The commitment comparison identifies an output created by this claim, even if the account already held ETH. Amounts use `bigint` base units throughout, avoiding floating-point rounding.

### 3. Run and inspect the result

Replace all four placeholders. `RECEIVED_ETH` is a decimal string without the `ETH` symbol.

```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
npx tsx verify-settlement.ts SWAP_TRANSACTION_ID CLAIM_TRANSACTION_ID SWAP_ID RECEIVED_ETH
```

A successful run ends with:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
VERIFIED: swap accepted, claim accepted, pending output cleared, ETH record available
```

If a check fails, inspect the last completed stage. A scanner delay, wrong account, wrong ID, or later record spend can prevent verification. An empty `getUnclaimedSwaps()` result or an absent mapping alone is insufficient evidence. Repeat read-only checks or follow [Failures and recovery](./failures-and-recovery); do not rerun the original swap to obtain a verification result.
