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

# Make your first swap with an SDK

> Fund a testnet account and swap 1 USDCx for ETH with JavaScript, TypeScript, or Python.

Choose your path: **SDK quickstart** · [CLI quickstart](./cli)

Swap 1 USDCx for ETH on testnet. Each swap takes two transactions: a request and a claim. Your trade is complete when the claim succeeds and the returned token records are available.

Choose a language below. The matching tabs will use your choice throughout the page.

<Tabs>
  <Tab title="TypeScript" id="typescript">
    Use Node.js 22 or later and npm with `@provablehq/shield-swap-sdk` 0.11.1.

    Create four files:

    * `client.ts` saves your account and configures the SDK.
    * `fund.ts` requests test tokens.
    * `first-swap.ts` quotes the trade, submits it, and claims the output.
    * `verify.ts` checks transaction acceptance and balances without submitting a trade.

    To run these examples as JavaScript, use `.js` filenames, change local imports to `"./client.js"`, and replace `npx tsx` with `node` in the run commands.

    This code always uses testnet, regardless of the network selected in the docs.
  </Tab>

  <Tab title="Python" id="python">
    Use Python 3.10 or later with `shield-swap-sdk` 0.5.1.

    Create three files:

    * `client.py` saves your account and configures the SDK.
    * `fund.py` requests test tokens.
    * `first-swap.py` quotes the trade, submits it, and claims the output.

    This code creates a testnet profile and checks its network each time it runs. The network selected in the docs has no effect on it.
  </Tab>
</Tabs>

Keep funding in a separate script so you can retry it without submitting another trade.

## 1. Create the project and account

<Tabs>
  <Tab title="TypeScript">
    Create a project and install the SDK:

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

    `"type": "module"` enables ES modules and top-level `await`. `tsx` runs TypeScript; you can skip that dependency if you use JavaScript.

    Create `.gitignore` before continuing:

    ```gitignore theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    node_modules/
    .shield-swap/
    ```

    Create `client.ts`. It generates a testnet account on first use and reuses the saved key on later runs.

    ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
    import { loadNetwork } from "@provablehq/veil-aleo-sdk"
    import { shieldSwapActions } from "@provablehq/shield-swap-sdk"
    import { swapFileStore } from "@provablehq/shield-swap-sdk/node"

    const aleo = await loadNetwork("testnet")
    const stateDirectory = ".shield-swap/testnet"
    const keyFile = `${stateDirectory}/private-key.txt`

    mkdirSync(stateDirectory, { recursive: true })
    if (!existsSync(keyFile)) {
      writeFileSync(keyFile, aleo.generateAccount().privateKey, {
        mode: 0o600,
        flag: "wx",
      })
    }

    const privateKey = readFileSync(keyFile, "utf8").trim()
    const { walletClient, publicClient, account } = aleo.createAleoClient({ privateKey })

    export { account, publicClient }
    export const client = walletClient.extend(
      shieldSwapActions({
        api: {},
        blindedIdentities: swapFileStore(`${stateDirectory}/swaps.json`),
      }),
    )

    console.log("Testnet account:", account.address)
    ```

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    npx tsx client.ts
    ```

    The script prints your account address. It saves the private key to `.shield-swap/testnet/private-key.txt` and never prints the key.

    `createAleoClient({ privateKey })` sets up the gateway, record scanner, delegated proving, and fee payment. You don't need Provable API credentials or endpoint configuration. `swapFileStore` saves the data needed to recover claims. Use `publicClient` to check transaction status and the extended wallet `client` for swap and record actions.

    Keep `.shield-swap/` between sessions and run all commands from this project directory. To use an existing testnet account, create `.shield-swap/testnet/private-key.txt` with your key before the first run.

    The sign-in message includes the current Terms of Use and related disclosures. Successful authentication records acceptance and grants API access. No invite code or `.env` file is required.
  </Tab>

  <Tab title="Python">
    Create the project and install the SDK:

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    mkdir shield-first-swap
    cd shield-first-swap
    python3 -m venv .venv
    source .venv/bin/activate
    python -m pip install "shield-swap-sdk==0.5.1"
    ```

    Keep the virtual environment active for the remaining commands. If you open a new shell, run `source .venv/bin/activate` from this directory again.

    Create `.gitignore`:

    ```gitignore theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    .venv/
    .shield-swap/
    __pycache__/
    ```

    Create `client.py` to set up an account and sign in to Shield Swap. Later runs reuse the saved account.

    ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    from aleo import testnet
    from aleo_shield_swap import Profile, ShieldSwap

    profile = Profile.load_or_create(".shield-swap", network="testnet")
    if profile.network != "testnet":
        raise SystemExit("This quickstart requires a testnet profile")

    dex = ShieldSwap.from_profile(profile.home)
    private_key = testnet.PrivateKey.from_string(profile.private_key)
    dex.api.authenticate(
        profile.address,
        lambda message: str(private_key.sign(message.encode())),
    )
    print("Testnet account:", profile.address)
    ```

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    python client.py
    ```

    The script prints your account address. It saves the private key to `.shield-swap/profile.json` and never prints the key. `from_profile` sets up the signer, scanner, and persistent swap journal.

    You do not need Provable API credentials for the default gateway. Each script authenticates a new session with the saved account.

    Keep `.shield-swap/` between sessions and run all commands from this project directory. An existing profile keeps its saved network and endpoint. The network check stops the script if the profile uses mainnet.

    <Accordion title="Use an existing testnet account">
      Before the first run, save your private key outside this project and set `SHIELD_SWAP_PRIVATE_KEY_FILE` to its path:

      ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
      export SHIELD_SWAP_PRIVATE_KEY_FILE=/path/to/my-key.txt
      python client.py
      ```

      The profile imports the key when it is first created and uses the saved key on later runs.
    </Accordion>

    The sign-in message includes the current Terms of Use and related disclosures. Successful authentication records acceptance and grants API access. No invite code or `.env` file is required.
  </Tab>
</Tabs>

## 2. Fund the account

Delegated proving pays testnet fees, so the account does not need a public ALEO balance.

<Tabs>
  <Tab title="TypeScript">
    Create `fund.ts`. It signs in and checks your USDCx balance, then requests test tokens only if you need them.

    ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    import { formatUnits, parseUnits } from "@provablehq/shield-swap-sdk"
    import { account, client } from "./client.ts"

    console.log("Authenticating with Shield Swap...")
    await client.authenticateShieldSwap()

    console.log("Checking USDCx balance...")
    const token = await client.tokenData("USDCx")
    const requiredAmount = parseUnits("1", token.decimals)
    let balances = await client.getBalances({ tokens: [token.id] })
    if ((balances[token.id]?.private ?? 0n) < requiredAmount) {
      console.log("Requesting test tokens and waiting for their records...")
      const drop = await client.api.confirmAirdrop(account.address)
      if (drop.status === "rate_limited") console.log(drop.message)
      else console.table(drop.job.results)
      balances = await client.getBalances({ tokens: [token.id] })
    }

    const available = balances[token.id]?.private ?? 0n
    if (available < requiredAmount) {
      throw new Error("Not funded yet. Wait for indexing or the faucet cooldown, then rerun fund.ts")
    }
    console.log(`Ready: ${formatUnits(available, token.decimals)} USDCx available`)
    ```

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    npx tsx fund.ts
    ```

    Wait for `Ready: ... USDCx available` before continuing. The first run can take several minutes: `confirmAirdrop` waits for the faucet job and scans for its token records. If a transfer is incomplete or a rate limit leaves you short of USDCx, the balance check stops the script.

    If the API returns `terms_required`, run `fund.ts` again to sign in with a new challenge. Referral-code redemption does not grant API access. Funding never submits a swap.

    <Accordion title="Connection timeout while funding">
      `fetch failed` with cause `UND_ERR_CONNECT_TIMEOUT` means Node timed out while connecting to the API. Check the last progress message to see which stage failed. This error does not indicate an invalid key or failed terms acceptance.

      Check connectivity from the same terminal without loading your account:

      ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
      node --input-type=module -e 'const r = await fetch("https://api.testnet.swap.shield.fi/tokens?limit=1"); console.log(r.status)'
      ```

      A `200` response confirms that the public token endpoint is reachable. Rerun `fund.ts`. If the connection still times out, check your network, VPN or proxy configuration, and API availability. Increasing the faucet polling timeout or swap confirmation timeout does not change this connection timeout.
    </Accordion>
  </Tab>

  <Tab title="Python">
    Create `fund.py`. It requests test tokens only if no single spendable record covers 1 USDCx.

    ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    from client import dex

    token = dex.api.get_token("USDCx")
    if not dex.has_swap_balance(token.address, "1"):
        funding = dex.confirm_airdrop()
        if not funding.success:
            raise SystemExit(funding.error)
        if not dex.has_swap_balance(token.address, "1"):
            raise SystemExit("No record covers 1 USDCx yet. Check funding before continuing.")

    print("Ready: a record covering 1 USDCx is available")
    ```

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    python fund.py
    ```

    Wait for `Ready: a record covering 1 USDCx is available`. `has_swap_balance` checks for a single record that covers the amount, without reserving it. A balance split across smaller records won't pass this check.

    `confirm_airdrop` waits up to ten minutes for the faucet job and its records. A rate limit, failed transfer, or record timeout stops the script. If it reports pending transfers, inspect the transaction IDs in the error before requesting another airdrop. Funding never submits a swap.
  </Tab>
</Tabs>

The airdrop delivers spendable token records. If a later swap needs one larger record, see [Preparing token records](../preparing-token-records).

## 3. Create the swap client

Add the code from steps 3 to 5 to one swap script, then run it at the end of step 5. This first step creates the client without submitting a swap.

<CodeGroup>
  ```typescript TypeScript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
  import { createInterface } from "node:readline/promises"
  import { formatUnits } from "@provablehq/shield-swap-sdk"
  import { client } from "./client.ts"

  await client.authenticateShieldSwap()
  console.log("Swap session ready")
  ```

  ```python Python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
  from client import dex


  def format_units(amount: int, decimals: int) -> str:
      if decimals == 0:
          return str(amount)
      digits = str(amount).zfill(decimals + 1)
      return f"{digits[:-decimals]}.{digits[-decimals:]}".rstrip("0").rstrip(".")


  print("Swap session ready")
  ```
</CodeGroup>

<Tabs>
  <Tab title="TypeScript">
    Save this as `first-swap.ts`. Importing `client.ts` reuses the account and recovery store from step 1.
  </Tab>

  <Tab title="Python">
    Save this as `first-swap.py`. Importing `client.py` reuses the account and journal from step 1. The formatting helper converts claim amounts from base units without floating-point rounding.
  </Tab>
</Tabs>

## 4. Quote and review

Append the code for your language to the swap script.

Both SDKs can select a route through one to three pools. Slippage applies to the final output.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    const quote = await client.quote({
      from: "USDCx",
      to: "ETH",
      amountIn: "1",
      slippageBps: 50,
    })

    console.table({
      network: quote.network,
      sell: `${formatUnits(quote.amountIn, quote.from.decimals)} ${quote.from.symbol}`,
      buy: quote.to.symbol,
      expectedOutput: formatUnits(quote.expectedOut, quote.to.decimals),
      minimumOutput: formatUnits(quote.minOut, quote.to.decimals),
      slippage: "0.5%",
      pools: quote.hops.length,
    })

    const prompt = createInterface({ input: process.stdin, output: process.stdout })
    const answer = await prompt.question("Submit this testnet swap? Type swap: ")
    prompt.close()
    if (answer.trim() !== "swap") process.exit(0)
    ```

    `amountIn: "1"` means one USDCx. Use a decimal string for token units or a `bigint` for raw base units; JavaScript numbers are not accepted.

    A quote needs a usable output estimate and a positive minimum output. It expires 60 seconds after the request starts. If it expires before submission, the SDK stops. Request and review a new quote without editing or extending the expired one.
  </Tab>

  <Tab title="Python">
    ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    quote = dex.quote(
        token_in="USDCx",
        token_out="ETH",
        amount_in="1",
        slippage_bps=50,
    )

    print(f"network: {quote.network}")
    print(f"sell: {quote.amount_in} USDCx")
    print("buy: ETH")
    print(f"expectedOutput: {quote.estimated_amount_out}")
    print(f"minimumOutput: {quote.minimum_amount_out}")
    print("slippage: 0.5%")
    print(f"pools: {len(quote.hops)}")

    answer = input("Submit this testnet swap? Type swap: ")
    if answer.strip() != "swap":
        raise SystemExit(0)
    ```

    `amount_in="1"` means one USDCx. `quote` accepts a decimal string or `Decimal` in token units, not an integer or float. It returns the input amount, estimated output, and minimum output as decimal strings you can display directly.

    A quote needs a usable output estimate and a positive minimum output. Python v0.5.1 doesn't expire quotes automatically, so request and review a fresh quote if you pause before submitting. A quote doesn't reserve liquidity or guarantee the estimated price.
  </Tab>
</Tabs>

`50` basis points is 0.5% slippage. You haven't submitted anything yet. A swap spends one token record, so a balance split across smaller records may need [record preparation](../preparing-token-records) even if the total covers the trade.

## 5. Submit and claim

Append the code for your language to the swap script.

<CodeGroup>
  ```typescript TypeScript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
  const handle = await client.swap({ quote })
  console.log("Swap transaction:", handle.transactionId)
  console.log("Swap ID:", handle.swapId)

  await client.waitForSwapOutput({ handle, timeout: 60_000 })
  const claim = await client.claimSwapOutput({ handle })

  console.log("Claim transaction:", claim.transactionId)
  console.log(`Received ${formatUnits(claim.amountOut, quote.to.decimals)} ${quote.to.symbol}`)
  if (claim.amountRemaining > 0n) {
    console.log(
      `Refunded ${formatUnits(claim.amountRemaining, quote.from.decimals)} ${quote.from.symbol}`,
    )
  }
  ```

  ```python Python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
  handle = dex.swap(quote).delegate(wait=True)
  print("Swap transaction:", handle.transaction_id)

  claim = dex.claim_swap_output(handle, timeout=60).delegate(wait=True)

  print("Claim transaction:", claim.transaction_id)
  print(f"Received {format_units(claim.amount_out, quote.token_out_decimals)} ETH")
  if claim.amount_remaining > 0:
      print(f"Refunded {format_units(claim.amount_remaining, quote.token_in_decimals)} USDCx")
  ```
</CodeGroup>

You can claim the output after the swap finalizes. If the script times out, check the transaction status before submitting again; the transaction may still succeed. Don't rerun the swap script to finish an existing trade.

<Tabs>
  <Tab title="TypeScript">
    `swap({ quote })` chooses single- or multi-hop execution and keeps the quoted minimum output. In this example, `waitForSwapOutput` polls for up to 60 seconds until the request is confirmed and its output is ready, without submitting a transaction. `claimSwapOutput` resolves program imports automatically.
  </Tab>

  <Tab title="Python">
    `swap(quote)` prepares the quoted route with its minimum output. Call `.delegate(wait=True)` to submit it and wait for confirmation.

    `claim_swap_output(handle, timeout=60)` waits up to 60 seconds for the output to be ready before preparing the claim. The following `.delegate(wait=True)` submits one claim and waits for confirmation. The SDK resolves the required program imports and records confirmed claims in the journal automatically.
  </Tab>
</Tabs>

Run the completed script:

<CodeGroup>
  ```bash TypeScript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
  npx tsx first-swap.ts
  ```

  ```bash Python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
  python first-swap.py
  ```
</CodeGroup>

The script prints `Swap transaction:`, `Claim transaction:`, and `Received ...`. The claim returns the output amount and any unspent input as a refund.

The scanner may take longer to find the returned records. If they remain unavailable, see [Missing output records](../failures-and-recovery#missing-output-records). Keep `.shield-swap/`, and do not resubmit an accepted claim.

## 6. Verify the result

Save both transaction IDs and the received amount. Check that the network accepted the transactions and that your account can find the returned records. Verification reads existing state, so running it won't start another swap.

<Tabs>
  <Tab title="TypeScript">
    Create `verify.ts`:

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

    const ids = process.argv.slice(2)
    assert(ids.length === 2, "Pass the swap and claim transaction IDs")
    for (const [index, id] of ids.entries()) {
      const label = index === 0 ? "Swap" : "Claim"
      const tx = await publicClient.getConfirmedTransaction({ id })
      assert.equal(tx.status, "accepted", `${label} is not accepted`)
      console.log(`${label}: accepted`)
    }

    for (const symbol of ["USDCx", "ETH"]) {
      const token = await client.tokenData(symbol)
      const balances = await client.getBalances({ tokens: [token.id] })
      console.log(`${symbol}: ${formatUnits(balances[token.id]?.private ?? 0n, token.decimals)}`)
    }
    ```

    Replace the two placeholders with the IDs printed by `first-swap.ts`:

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

    Expect `Swap: accepted` and `Claim: accepted`. If you started with 10 USDCx and 0.006 ETH and had no other activity or refund, expect balances of 9 USDCx and `0.006 + received ETH`. Use the amount you actually received; it can differ from the quote estimate.

    Call `getConfirmedTransaction` on `publicClient`; it isn't available on `client`. If the lookup fails or times out, check your connection and transaction IDs, then rerun verification. Until you know the outcome, don't submit another swap or claim.

    Run the [settlement verifier](../verify-settlement#verify-the-output-record) to confirm that the claim belongs to this swap and its output record is unspent. It also checks that `swap_outputs[swap_id]` has been cleared.
  </Tab>

  <Tab title="Python">
    Use the two transaction IDs printed by `first-swap.py` with the [public transaction lookup](../verify-settlement#check-both-transactions). Both responses must report `status: accepted`.

    This lookup checks ledger acceptance without changing your Python environment. You'll still need to check that the scanner has found the returned records. If they're unavailable, follow [Missing output records](../failures-and-recovery#missing-output-records).
  </Tab>
</Tabs>

## Finish an interrupted swap

Use the same saved account and recovery data. Do not restart `first-swap.ts` or `first-swap.py` to recover a submitted trade.

<Tabs>
  <Tab title="TypeScript">
    Create `recover.ts`:

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

    await client.authenticateShieldSwap()
    const { swaps } = await client.getUnclaimedSwaps()
    for (const swap of swaps) {
      if (!swap.claimable || !swap.handle) continue
      const claim = await client.claimSwapOutput({ handle: swap.handle })
      console.log("Recovered claim:", claim.transactionId)
    }
    ```

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    npx tsx recover.ts
    ```

    This script claims ready outputs without starting another swap. See [Recover pending claims](../../sdk/swaps#recover-pending-claims) for history reconciliation.
  </Tab>

  <Tab title="Python">
    Create `recover.py`:

    ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    from aleo_shield_swap import SwapOutputNotFinalizedError
    from client import dex

    for handle in dex.journal.pending_claims():
        try:
            claim = dex.claim_swap_output(handle, timeout=60).delegate(wait=True)
        except SwapOutputNotFinalizedError:
            print("Output unavailable; check transaction status:", handle.transaction_id)
            continue
        print("Recovered claim:", claim.transaction_id)
    ```

    ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
    python recover.py
    ```

    This script reads pending swap handles from the journal and claims their outputs when ready. It does not submit new swaps or collect liquidity fees. Other errors stop the script; check transaction status before retrying.

    `pending_claims()` omits handles without a resolved swap ID. Inspect those transactions before attempting recovery.
  </Tab>
</Tabs>

An empty list alone doesn't tell you whether the trade failed. The request may still be pending, or the output may already have been claimed. If you can't confirm the outcome, follow [Failures and recovery](../failures-and-recovery).

## Next steps

See [Trader workflow](../trader-workflow) for the full integration lifecycle. For TypeScript, read [Swap with the TypeScript SDK](../../sdk/swaps) for recovery and concurrent swaps, or [Connect a wallet](../../sdk/typescript-client#connect-a-wallet).

The [Python SDK reference](https://pypi.org/project/shield-swap-sdk/0.5.1/) covers additional trading and liquidity methods.
