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

# Quickstart

> Get a private trade executing on Solana in under 30 minutes.

This guide walks through a complete private trade end-to-end: environment setup, depositing funds, building a swap transaction via your DEX aggregator, executing privately via Vanish, and committing the result.

<Info>
  You'll need an API key to follow this guide. Contact the Vanish team via [Discord](https://discord.gg/vanishtrade) to get one during onboarding.
</Info>

***

## Prerequisites

<CodeGroup>
  ```bash TypeScript theme={null}
  npm install @solana/web3.js tweetnacl
  ```

  ```toml Rust theme={null}
  [dependencies]
  solana-sdk    = "1.18"
  solana-client = "1.18"
  base64        = "0.22"
  bincode       = "1"
  reqwest       = { version = "0.12", features = ["json"] }
  serde_json    = "1"
  tokio         = { version = "1", features = ["full"] }
  chrono        = "0.4"
  ```
</CodeGroup>

***

## 1. Environment Setup

Before starting, make sure you have the following available:

* **Vanish API key** - provisioned during onboarding
* **Solana RPC endpoint** - any standard Solana RPC URL
* **User keypair** - the Ed25519 keypair for the wallet you're trading from
* **Solana balance** - enough SOL to cover the deposit and transaction fees

Store sensitive values - your API key and keypair - in environment variables. Never expose them in source code or client-side bundles.

***

## 2. Set Up the Client

Set up your Vanish HTTP client and Solana connection. These are used throughout the rest of the guide.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Connection, Keypair } from '@solana/web3.js';

  const VANISH_URL  = 'https://core-api.vanish.trade';
  const connection  = new Connection(process.env.SOLANA_RPC_URL!);
  const userKeypair = Keypair.fromSecretKey(
    Uint8Array.from(JSON.parse(process.env.SOLANA_KEYPAIR!))
  );

  async function vanish(path: string, options?: RequestInit) {
    const res = await fetch(`${VANISH_URL}${path}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': process.env.VANISH_API_KEY!,
        ...options?.headers,
      },
    });
    if (!res.ok) {
      throw new Error(`Vanish ${res.status}: ${await res.text()}`);
    }
    return res.json();
  }
  ```

  ```rust Rust theme={null}
  use reqwest::Client;
  use solana_client::rpc_client::RpcClient;
  use solana_sdk::signature::Keypair;
  use serde_json::Value;

  const VANISH_URL: &str = "https://core-api.vanish.trade";

  // initialise once and pass through your application
  let client   = Client::new();
  let rpc      = RpcClient::new(std::env::var("SOLANA_RPC_URL").unwrap());
  let api_key  = std::env::var("VANISH_API_KEY").unwrap();
  let keypair_bytes: Vec<u8> = serde_json::from_str(
      &std::env::var("SOLANA_KEYPAIR").unwrap()
  ).unwrap();
  let user_keypair = Keypair::from_bytes(&keypair_bytes).unwrap();

  async fn vanish(
      client: &Client,
      api_key: &str,
      path: &str,
      body: Option<&Value>,
  ) -> Result<Value, reqwest::Error> {
      let mut req = client
          .request(
              if body.is_some() { reqwest::Method::POST } else { reqwest::Method::GET },
              format!("{}{}", VANISH_URL, path),
          )
          .header("x-api-key", api_key)
          .header("Content-Type", "application/json");

      if let Some(b) = body {
          req = req.json(b);
      }

      req.send().await?.json().await
  }
  ```
</CodeGroup>

***

## 3. Sign Requests

Several endpoints require a signed message proving ownership of `user_address`. Sign with the user's Ed25519 keypair and base64-encode the result. The message format varies by endpoint - see [Signing Requests](/guide/integration#signing-requests) for all formats.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import * as nacl from 'tweetnacl';
  import { Keypair } from '@solana/web3.js';

  function signMessage(message: string, keypair: Keypair): string {
    const sig = nacl.sign.detached(new TextEncoder().encode(message), keypair.secretKey);
    return Buffer.from(sig).toString('base64');
  }

  // timestamp must be Date.now().toString() — milliseconds, not seconds
  function readSignature(timestamp: string, keypair: Keypair): string {
    return signMessage(
      `By signing, I hereby agree to Vanish's Terms of Service and agree to be bound by them (docs.vanish.trade/legal/TOS)\n\nDetails: read:${timestamp}`,
      keypair
    );
  }

  // all args must match exactly what you pass to /trade/create
  function tradeSignature(
    source: string, target: string, amount: string,
    loanSol: string, timestamp: string, jitoTip: string,
    keypair: Keypair
  ): string {
    return signMessage(
      `By signing, I hereby agree to Vanish's Terms of Service and agree to be bound by them (docs.vanish.trade/legal/TOS)\n\nDetails: trade:${source}:${target}:${amount}:${loanSol}:${timestamp}:${jitoTip}`,
      keypair
    );
  }
  ```

  ```rust Rust theme={null}
  use solana_sdk::signature::{Keypair, Signer};
  use base64::{engine::general_purpose, Engine as _};

  fn sign_message(message: &str, keypair: &Keypair) -> String {
      let sig = keypair.sign_message(message.as_bytes());
      general_purpose::STANDARD.encode(sig.as_ref())
  }

  // timestamp must be chrono::Utc::now().timestamp_millis() — milliseconds, not seconds
  fn read_signature(timestamp: &str, keypair: &Keypair) -> String {
      sign_message(
          &format!(
              "By signing, I hereby agree to Vanish's Terms of Service and agree to be bound by them (docs.vanish.trade/legal/TOS)\n\nDetails: read:{timestamp}"
          ),
          keypair,
      )
  }

  fn trade_signature(
      source: &str, target: &str, amount: &str,
      loan_sol: &str, timestamp: &str, jito_tip: &str,
      keypair: &Keypair,
  ) -> String {
      sign_message(
          &format!(
              "By signing, I hereby agree to Vanish's Terms of Service and agree to be bound by them (docs.vanish.trade/legal/TOS)\n\nDetails: trade:{source}:{target}:{amount}:{loan_sol}:{timestamp}:{jito_tip}"
          ),
          keypair,
      )
  }
  ```
</CodeGroup>

***

## 4. Fund Your Account

Before trading, you need a Vanish balance. This involves three steps: fetching a deposit address, sending funds on-chain, then committing the transaction to Vanish.

<Steps>
  <Step title="Get a deposit address">
    <CodeGroup>
      ```typescript TypeScript theme={null}
      // 11111111111111111111111111111111 is the SOL native mint
      // replace with the token's mint address for SPL deposits
      const { address: depositAddress } = await vanish(
        `/deposit_address?token_address=11111111111111111111111111111111`
      );
      console.log('Deposit to:', depositAddress);
      ```

      ```rust Rust theme={null}
      // 11111111111111111111111111111111 is the SOL native mint
      let res = vanish(
          client, api_key,
          "/deposit_address?token_address=11111111111111111111111111111111",
          None,
      ).await?;
      let deposit_address = res["address"].as_str().unwrap();
      println!("Deposit to: {}", deposit_address);
      ```
    </CodeGroup>

    See [`GET /deposit_address`](/api-reference/funds/deposit_address) for full reference. Always fetch a fresh address before each deposit - addresses may rotate to ensure maximum privacy.
  </Step>

  <Step title="Send SOL on-chain">
    Send funds to the deposit address and wait for confirmation.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      import { PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';

      const tx = new Transaction().add(
        SystemProgram.transfer({
          fromPubkey: userKeypair.publicKey,
          toPubkey:   new PublicKey(depositAddress),
          lamports:   100_000_000, // 0.1 SOL
        })
      );

      const depositTxId = await sendAndConfirmTransaction(connection, tx, [userKeypair]);
      console.log('Deposit tx:', depositTxId);
      ```

      ```rust Rust theme={null}
      use solana_sdk::{pubkey::Pubkey, system_instruction, transaction::Transaction, signature::Signer};

      let to       = deposit_address.parse::<Pubkey>().unwrap();
      let ix       = system_instruction::transfer(&user_keypair.pubkey(), &to, 100_000_000); // 0.1 SOL
      let blockhash = rpc.get_latest_blockhash()?;
      let tx = Transaction::new_signed_with_payer(
          &[ix],
          Some(&user_keypair.pubkey()),
          &[&user_keypair],
          blockhash,
      );

      let deposit_tx_id = rpc.send_and_confirm_transaction(&tx)?.to_string();
      println!("Deposit tx: {}", deposit_tx_id);
      ```
    </CodeGroup>
  </Step>

  <Step title="Commit the deposit to Vanish">
    Once confirmed on-chain, notify Vanish by calling [`POST /commit`](/api-reference/commit) with the transaction signature.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const commit = await vanish('/commit', {
        method: 'POST',
        body: JSON.stringify({ tx_id: depositTxId }),
      });

      console.log('Deposit status:', commit.status);
      // completed  = balance updated, ready to trade
      // pending    = compliance screening in progress, check back shortly
      // rejected   = failed screening, funds will be refunded automatically
      ```

      ```rust Rust theme={null}
      let commit = vanish(
          client, api_key, "/commit",
          Some(&serde_json::json!({ "tx_id": deposit_tx_id })),
      ).await?;

      println!("Deposit status: {}", commit["status"]);
      // completed  = balance updated, ready to trade
      // pending    = compliance screening in progress, check back shortly
      // rejected   = failed screening, funds will be refunded automatically
      ```
    </CodeGroup>

    <Info>
      If the status is `pending`, poll [`/commit`](/api-reference/commit) again with the same `tx_id` until it resolves to `completed`.
    </Info>
  </Step>
</Steps>

***

## 5. Build a Swap Transaction

Vanish wraps your swap instructions - it does not build the route itself. Fetch an unsigned transaction from your DEX aggregator of choice and pass it to Vanish.

<Info>
  **Critical:** When building the swap transaction, set the **one-time wallet** as the transaction signer - not the user's wallet. Pass the one-time wallet address wherever your aggregator asks for the signing wallet or user public key.
</Info>

<Steps>
  <Step title="Get a one-time wallet from Vanish">
    <CodeGroup>
      ```typescript TypeScript theme={null}
      const { address: oneTimeWallet } = await vanish('/trade/one-time-wallet');
      ```

      ```rust Rust theme={null}
      let otw = vanish(client, api_key, "/trade/one-time-wallet", None).await?;
      let one_time_wallet = otw["address"].as_str().unwrap();
      ```
    </CodeGroup>

    See [`GET /trade/one-time-wallet`](/api-reference/trade/one-time-wallet) for full reference. Never reuse this address - fetch a fresh one for every trade.
  </Step>

  <Step title="Fetch a quote (Jupiter example)">
    <CodeGroup>
      ```typescript TypeScript theme={null}
      const SOL_MINT    = '11111111111111111111111111111111';
      const TARGET_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; // USDC
      const AMOUNT      = '100000000'; // 0.1 SOL in lamports

      const quoteRes = await fetch(
        `https://quote-api.jup.ag/v6/quote` +
        `?inputMint=${SOL_MINT}` +
        `&outputMint=${TARGET_MINT}` +
        `&amount=${AMOUNT}` +
        `&slippageBps=50`  // 0.5% slippage tolerance
      );
      const quote = await quoteRes.json();
      ```

      ```rust Rust theme={null}
      const TARGET_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDC
      const AMOUNT: &str      = "100000000"; // 0.1 SOL in lamports

      let quote_url = format!(
          "https://quote-api.jup.ag/v6/quote\
           ?inputMint=11111111111111111111111111111111\
           &outputMint={TARGET_MINT}\
           &amount={AMOUNT}\
           &slippageBps=50"
      );
      let quote: Value = reqwest::get(&quote_url).await?.json().await?;
      ```
    </CodeGroup>
  </Step>

  <Step title="Get the unsigned swap transaction (Jupiter example)">
    Pass the one-time wallet as the signer so the transaction is built around it, not the user's wallet.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const swapRes = await fetch('https://quote-api.jup.ag/v6/swap', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          quoteResponse:    quote,
          userPublicKey:    oneTimeWallet,   // must be the one-time wallet
          wrapAndUnwrapSol: true,
          dynamicComputeUnitLimit: true,
        }),
      });

      const { swapTransaction } = await swapRes.json();
      // swapTransaction is base64-encoded and ready for Vanish
      ```

      ```rust Rust theme={null}
      let swap_body = serde_json::json!({
          "quoteResponse":           quote,
          "userPublicKey":           one_time_wallet,  // must be the one-time wallet
          "wrapAndUnwrapSol":        true,
          "dynamicComputeUnitLimit": true,
      });

      let swap_res: Value = client
          .post("https://quote-api.jup.ag/v6/swap")
          .json(&swap_body)
          .send()
          .await?
          .json()
          .await?;

      let swap_transaction = swap_res["swapTransaction"].as_str().unwrap();
      ```
    </CodeGroup>
  </Step>
</Steps>

***

## 6. Execute the Trade

Submit the swap transaction to Vanish via [`POST /trade/create`](/api-reference/trade/create). Vanish wraps and submits it via Jito, returning a `tx_id`.

This step uses `oneTimeWallet`, `swapTransaction`, `TARGET_MINT`, and `AMOUNT` from the previous step.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const SOL_MINT  = '11111111111111111111111111111111';
  const LOAN_SOL  = '12000000'; // recommended loan_additional_sol — unused amount is refunded
  const JITO_TIP  = '1000000';  // 0.001 SOL — minimum recommended tip

  const timestamp     = Date.now().toString();
  const userSignature = tradeSignature(
    SOL_MINT, TARGET_MINT, AMOUNT,
    LOAN_SOL, timestamp, JITO_TIP,
    userKeypair
  );

  const trade = await vanish('/trade/create', {
    method: 'POST',
    body: JSON.stringify({
      user_address:         userKeypair.publicKey.toBase58(),
      source_token_address: SOL_MINT,
      target_token_address: TARGET_MINT,
      amount:               AMOUNT,
      swap_transaction:     swapTransaction,  // base64-encoded unsigned tx from your aggregator
      one_time_wallet:      oneTimeWallet,
      loan_additional_sol:  LOAN_SOL,
      jito_tip_amount:      JITO_TIP,
      split_repay:          1,
      timestamp,
      user_signature:       userSignature,
      // omit prefer_non_jito to route via Jito (default)
    }),
  });

  console.log('Trade submitted. tx_id:', trade.tx_id);
  ```

  ```rust Rust theme={null}
  const SOL_MINT: &str = "11111111111111111111111111111111";
  const LOAN_SOL: &str = "12000000"; // recommended loan_additional_sol — unused amount is refunded
  const JITO_TIP: &str = "1000000"; // 0.001 SOL — minimum recommended tip

  let timestamp      = chrono::Utc::now().timestamp_millis().to_string();
  let user_signature = trade_signature(
      SOL_MINT, TARGET_MINT, AMOUNT,
      LOAN_SOL, &timestamp, JITO_TIP,
      &user_keypair,
  );

  let body = serde_json::json!({
      "user_address":         user_keypair.pubkey().to_string(),
      "source_token_address": SOL_MINT,
      "target_token_address": TARGET_MINT,
      "amount":               AMOUNT,
      "swap_transaction":     swap_transaction,  // base64-encoded unsigned tx from your aggregator
      "one_time_wallet":      one_time_wallet,
      "loan_additional_sol":  LOAN_SOL,
      "jito_tip_amount":      JITO_TIP,
      "split_repay":          1,
      "timestamp":            &timestamp,
      "user_signature":       user_signature,
  });

  let trade = vanish(client, api_key, "/trade/create", Some(&body)).await?;
  println!("Trade submitted. tx_id: {}", trade["tx_id"]);
  ```
</CodeGroup>

***

## 7. Commit the Trade

Always call [`POST /commit`](/api-reference/commit) after every trade - whether it succeeded, failed, or expired on-chain. This resolves the user's balance from its pending state.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const commit = await vanish('/commit', {
    method: 'POST',
    body: JSON.stringify({ tx_id: trade.tx_id }),
  });

  console.log('Status:', commit.status);
  console.log('Balance changes:', commit.balance_changes);
  ```

  ```rust Rust theme={null}
  let commit = vanish(
      client, api_key, "/commit",
      Some(&serde_json::json!({ "tx_id": trade["tx_id"] })),
  ).await?;

  println!("Status: {}", commit["status"]);
  ```
</CodeGroup>

<Warning>
  [`/commit`](/api-reference/commit) must be called for every transaction - success, failure, or expiry. Without it, the user's balance remains in a pending state indefinitely.
</Warning>

| Status      | Meaning                                                                   |
| ----------- | ------------------------------------------------------------------------- |
| `completed` | Trade settled: balance updated                                            |
| `pending`   | On-chain confirmation or compliance check in progress: poll again shortly |
| `failed`    | Transaction rejected on-chain: balance released                           |
| `expired`   | Not confirmed in time: balance released                                   |

***

## 8. Non-Jito Variant (Self-Broadcast)

By default, Vanish submits via Jito bundle and returns only a `tx_id`. If you want to broadcast yourself - for custom retry logic or a low-latency RPC - add the `prefer_non_jito` object to the same [`/trade/create`](/api-reference/trade/create) request from step 6.

<Info>
  Use `split_repay: 1` on the non-Jito route. Higher values increase transaction size and risk exceeding Solana's limit.
</Info>

Add this to your [`/trade/create`](/api-reference/trade/create) request body:

```json theme={null}
"prefer_non_jito": {
  "compute_unit_price": "71429",
  "compute_unit_limit": "1400000",
  "custom_tip_address": "optional — your tip wallet address",
  "custom_tip_amount":  "optional — tip amount in lamports"
}
```

Then broadcast the returned `transaction` yourself instead of using the `tx_id` directly:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { VersionedTransaction } from '@solana/web3.js';

  // trade.transaction is the fully signed tx returned by Vanish — broadcast it yourself
  const txBytes = Buffer.from(trade.transaction, 'base64');
  const tx      = VersionedTransaction.deserialize(txBytes);
  const txId    = await connection.sendRawTransaction(tx.serialize(), {
    skipPreflight: false, // simulate on RPC before submitting — catches obvious failures early
    maxRetries:    3,
  });
  await connection.confirmTransaction(txId, 'confirmed');

  // Then commit as normal using txId
  const commit = await vanish('/commit', {
    method: 'POST',
    body: JSON.stringify({ tx_id: txId }),
  });
  ```

  ```rust Rust theme={null}
  use solana_sdk::transaction::VersionedTransaction;

  // trade["transaction"] is the fully signed tx returned by Vanish — broadcast it yourself
  let tx_bytes = general_purpose::STANDARD.decode(trade["transaction"].as_str().unwrap())?;
  let tx: VersionedTransaction = bincode::deserialize(&tx_bytes)?;
  let sig = rpc.send_and_confirm_transaction(&tx)?;

  // Then commit as normal using the on-chain signature
  let commit = vanish(
      client, api_key, "/commit",
      Some(&serde_json::json!({ "tx_id": sig.to_string() })),
  ).await?;
  ```
</CodeGroup>

***

## 9. Check Balances

See [`POST /account/balances`](/api-reference/account/get_balances) for full reference.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const timestamp = Date.now().toString();
  const signature = readSignature(timestamp, userKeypair);

  const balances = await vanish('/account/balances', {
    method: 'POST',
    body: JSON.stringify({
      user_address: userKeypair.publicKey.toBase58(),
      timestamp,
      signature,
    }),
  });

  // [{ token_address, balance, program_id }, ...]
  // balance is in lamports for SOL, base units for SPL tokens
  console.log(balances);
  ```

  ```rust Rust theme={null}
  let timestamp = chrono::Utc::now().timestamp_millis().to_string();
  let signature = read_signature(&timestamp, &user_keypair);

  let balances = vanish(client, api_key, "/account/balances", Some(&serde_json::json!({
      "user_address": user_keypair.pubkey().to_string(),
      "timestamp":    &timestamp,
      "signature":    signature,
  }))).await?;

  // [{ token_address, balance, program_id }, ...]
  println!("{}", balances);
  ```
</CodeGroup>

***

## 10. Recover Interrupted Flows

If your app crashes after submitting a trade but before calling `/commit`, the user's balance is held in a pending state. Call [`POST /account/pending`](/api-reference/account/get_pending_actions) on startup to catch and resolve these.

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function recoverPendingCommits(userKeypair: Keypair) {
    const timestamp = Date.now().toString();
    const signature = readSignature(timestamp, userKeypair);

    const pending = await vanish('/account/pending', {
      method: 'POST',
      body: JSON.stringify({
        user_address: userKeypair.publicKey.toBase58(),
        timestamp,
        signature,
      }),
    });

    for (const action of pending) {
      const commit = await vanish('/commit', {
        method: 'POST',
        body: JSON.stringify({ tx_id: action.tx_id }),
      });
      if (!commit.already_processed) {
        console.log(`Resolved ${action.action_type}:`, commit.status);
      }
    }
  }
  ```

  ```rust Rust theme={null}
  async fn recover_pending_commits(
      client: &Client,
      api_key: &str,
      user_keypair: &Keypair,
  ) -> Result<(), reqwest::Error> {
      let timestamp = chrono::Utc::now().timestamp_millis().to_string();
      let signature = read_signature(&timestamp, user_keypair);

      let pending = vanish(client, api_key, "/account/pending", Some(&serde_json::json!({
          "user_address": user_keypair.pubkey().to_string(),
          "timestamp":    &timestamp,
          "signature":    signature,
      }))).await?;

      for action in pending.as_array().unwrap_or(&vec![]) {
          let commit = vanish(
              client, api_key, "/commit",
              Some(&serde_json::json!({ "tx_id": action["tx_id"] })),
          ).await?;

          if !commit["already_processed"].as_bool().unwrap_or(false) {
              println!("Resolved {}: {}", action["action_type"], commit["status"]);
          }
      }

      Ok(())
  }
  ```
</CodeGroup>

***

## Lamport Reference

All SOL amounts in the Vanish API are in lamports. SPL token amounts use the token's own decimal precision.

| Amount                                   | Lamports        |
| ---------------------------------------- | --------------- |
| 1 SOL                                    | `1,000,000,000` |
| 0.012 SOL (loan\_additional\_sol)        | `12,000,000`    |
| 0.001 SOL (minimum recommended Jito tip) | `1,000,000`     |

***

## Review Your Integration

Before going live, work through these pages to complete your integration. Each one is the full reference for its part of the API.

<Steps>
  <Step title="Integration Guide">
    The [Integration Guide](/guide/integration) is the full reference for every flow - deposit, trade, and withdraw. Go through it to verify:

    * Every field in your `/trade/create` request is correct, including `split_repay` and `loan_additional_sol`
    * Your signing format matches the exact message string for each endpoint type (read, trade, withdraw)
    * You understand the interrupted flow recovery pattern using [`/account/pending`](/api-reference/account/get_pending_actions)
  </Step>

  <Step title="Error Handling">
    The [Error Handling](/guide/handling) page covers what happens when things go wrong. Go through it to verify:

    * You handle all five `/commit` statuses (`completed`, `pending`, `failed`, `expired`, `rejected`) correctly
    * You're polling `/commit` with the same `tx_id` when status is `pending`, not creating a new transaction
    * You call [`/account/pending`](/api-reference/account/get_pending_actions) on startup to catch any uncommitted transactions
  </Step>

  <Step title="FAQ">
    The [FAQ](/guide/faq) covers the most common integration mistakes. Go through it to verify:

    * You're fetching a fresh one-time wallet for every trade - never reusing one
    * Your swap transaction is **unsigned** before being passed to `/trade/create`
    * Your timestamps are in **milliseconds**, not seconds
    * Your signature is **base64-encoded**, not base58 or hex
    * You understand the same-wallet-in, same-wallet-out compliance rule for withdrawals
  </Step>
</Steps>
