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

# Add and Withdraw Funds

The ramp client builds a headless fiat on-ramp and off-ramp around a Swig
wallet. Every ramp call runs on your server. Your application decides how to
present quotes, the launch URL, and order state to a user.

`swig.ramp` covers both directions. Direction is carried by the buy or sell
order you pass, so there is no separate on-ramp or off-ramp client.

## Required configuration

`getOptions` / `get_options`, `getQuotes` / `get_quotes`, and `createOrder` /
`create_order` require both fields below.

| Field | Value |
| :- | :- |
| `configurationId` / `configuration_id` | the organization's ramp configuration id |
| `environment` | `sandbox` or `production` |

`getOrder` / `get_order`, `prepareTransfer` / `prepare_transfer`, and
`submitTransfer` / `submit_transfer` identify the stored order in the route and
require neither field.

The provider environment is not a Solana cluster. A sandbox order still settles
on mainnet.

## Amounts and assets

Amounts are integers in the smallest unit — `minorUnits` for fiat (cents for
USD, whole yen for JPY) and `baseUnits` for crypto. They cross the wire as
decimal strings so a value above 2^53 survives, and an amount finer than the
currency's minor unit is refused rather than rounded. In TypeScript, use a
`bigint` or decimal string above the safe-integer range; unsafe `number` values
are rejected before the request is sent.

Crypto is a two-case asset: native SOL, or an SPL mint.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  const sol = { type: 'sol' } as const;
  const usdc = { type: 'token', mint: usdcMint } as const;
  ```

  ```python Python theme={null}
  sol = NativeSolAsset()
  usdc = SplTokenAsset(mint=usdc_mint)
  ```
</CodeGroup>

## Read the options

Read the options first, so currency, payment-method, and asset values come from
the API rather than a hardcoded list. `decimals` and `exponent` are the scales
you need to build a valid amount.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  const options = await swig.ramp.getOptions({
    configurationId,
    environment: 'sandbox',
    direction: 'buy',
    countryCode: 'US',
  });
  ```

  ```python Python theme={null}
  options = await swig.ramp.get_options(
      configuration_id=configuration_id,
      environment="sandbox",
      direction="buy",
      country_code="US",
  )
  ```
</CodeGroup>

You get `countries` (each with `subdivisions`), `fiatCurrencies` /
`fiat_currencies`, `paymentMethods` / `payment_methods`, and `assets`.

## Quote a route

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  const quotes = await swig.ramp.getQuotes({
    configurationId,
    environment: 'sandbox',
    location: { countryCode: 'US' },
    order: {
      type: 'buy',
      spend: { currencyCode: 'USD', minorUnits: 10_000n },
      receive: { type: 'token', mint: usdcMint },
    },
  });
  ```

  ```python Python theme={null}
  quotes = await swig.ramp.get_quotes(
      configuration_id=configuration_id,
      environment="sandbox",
      location=RampLocation(country_code="US"),
      order=RampBuyOrderRequest(
          spend=FiatAmountInput(currency_code="USD", minor_units=10_000),
          receive=SplTokenAsset(mint=usdc_mint),
      ),
  )
  ```
</CodeGroup>

Each quote carries a `route` and a `details` arm holding the priced amounts, the
total fee, and the exchange rate.

<Warning>
  Quotes carry no identifier and must never be cached. Pass the chosen `route`
  to order creation; it is re-priced there.
</Warning>

## Create an order

`requestId` / `request_id` is your idempotency key and is unique within the
configuration. Repeating it returns the stored order; repeating it with
different inputs is refused. Mint it before the first attempt and reuse it
across retries — the SDK retries this call for you on a transient failure.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  const order = await swig.ramp.createOrder({
    requestId: crypto.randomUUID(),
    configurationId,
    environment: 'sandbox',
    context: {
      customerId,
      swigConfigAddress,
      location: { countryCode: 'US' },
    },
    route: quotes[0].route,
    order: {
      type: 'buy',
      spend: { currencyCode: 'USD', minorUnits: 10_000n },
      receive: { type: 'token', mint: usdcMint },
    },
  });
  ```

  ```python Python theme={null}
  order = await swig.ramp.create_order(
      request_id=str(uuid.uuid4()),
      configuration_id=configuration_id,
      environment="sandbox",
      context=RampOrderContext(
          customer_id=customer_id,
          swig_config_address=swig_config_address,
          location=RampLocation(country_code="US"),
      ),
      route=quotes[0].route,
      order=RampBuyOrderRequest(
          spend=FiatAmountInput(currency_code="USD", minor_units=10_000),
          receive=SplTokenAsset(mint=usdc_mint),
      ),
  )
  ```
</CodeGroup>

A buy sends the customer to `order.launchUrl` / `order.launch_url`.

<Warning>
  A launch URL is a user-specific session URL. Hand it to the customer who owns
  the order and keep it out of logs and analytics.
</Warning>

## Follow the order

Poll the order until its status is final. A read of a non-final order also
reconciles it against the provider, so polling is what advances it.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  const current = await swig.ramp.getOrder({ orderId: order.id });
  ```

  ```python Python theme={null}
  current = await swig.ramp.get_order(order_id=order.id)
  ```
</CodeGroup>

Statuses are `unspecified`, `creating`, `creation-uncertain`,
`awaiting-customer`, `awaiting-transfer`, `processing`, `settling`, `settled`,
`declined`, `cancelled`, `failed`, and `refunded`. `refunded` can follow
`settled`, so a settled order is not necessarily the last word.

## Sell: create, check out, transfer

A sell is its own order. The examples above create a buy, and a transfer
prepared against one is refused with `only a sell needs a transfer`, so quote
the sell direction and create a second order for it.

Create that order on `mainnet`. A transfer prepared on any other network is
refused with `ramp transfers settle on mainnet only`, and the order context's
network wins over the client's default. The provider environment is separate: a
`sandbox` order still settles on mainnet.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  const sellQuotes = await swig.ramp.getQuotes({
    configurationId,
    environment: 'sandbox',
    location: { countryCode: 'US' },
    order: {
      type: 'sell',
      sell: { asset: { type: 'token', mint: usdcMint }, baseUnits: 10_000_000n },
      receiveFiatCurrencyCode: 'USD',
    },
  });

  const sellOrder = await swig.ramp.createOrder({
    requestId: crypto.randomUUID(),
    configurationId,
    environment: 'sandbox',
    context: {
      customerId,
      swigConfigAddress,
      network: 'mainnet',
      location: { countryCode: 'US' },
    },
    route: sellQuotes[0].route,
    order: {
      type: 'sell',
      sell: { asset: { type: 'token', mint: usdcMint }, baseUnits: 10_000_000n },
      receiveFiatCurrencyCode: 'USD',
    },
  });
  ```

  ```python Python theme={null}
  sell_quotes = await swig.ramp.get_quotes(
      configuration_id=configuration_id,
      environment="sandbox",
      location=RampLocation(country_code="US"),
      order=RampSellOrderRequest(
          sell=CryptoAmountInput(
              asset=SplTokenAsset(mint=usdc_mint), base_units=10_000_000
          ),
          receive_fiat_currency_code="USD",
      ),
  )

  sell_order = await swig.ramp.create_order(
      request_id=str(uuid.uuid4()),
      configuration_id=configuration_id,
      environment="sandbox",
      context=RampOrderContext(
          customer_id=customer_id,
          swig_config_address=swig_config_address,
          network="mainnet",
          location=RampLocation(country_code="US"),
      ),
      route=sell_quotes[0].route,
      order=RampSellOrderRequest(
          sell=CryptoAmountInput(
              asset=SplTokenAsset(mint=usdc_mint), base_units=10_000_000
          ),
          receive_fiat_currency_code="USD",
      ),
  )
  ```
</CodeGroup>

Send the customer to the sell order's launch URL. The provider assigns the
deposit while they are in that checkout, so the transfer cannot be prepared
until they have engaged with it.

<Warning>
  A launch URL is a user-specific session URL. Hand it to the customer who owns
  the order and keep it out of logs and analytics.
</Warning>

Poll the order until it reaches `awaiting-transfer` and carries a deposit.
Preparing earlier is refused with `the deposit address is not ready`.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  const finished = ['settled', 'declined', 'cancelled', 'failed', 'refunded'];

  let current = await swig.ramp.getOrder({ orderId: sellOrder.id });
  while (current.type === 'sell' && !current.deposit) {
    if (finished.includes(current.status)) {
      throw new Error(`sell order ${current.status} before a deposit`);
    }
    await new Promise((resolve) => setTimeout(resolve, 5_000));
    current = await swig.ramp.getOrder({ orderId: sellOrder.id });
  }
  ```

  ```python Python theme={null}
  FINISHED = {"settled", "declined", "cancelled", "failed", "refunded"}

  current = await swig.ramp.get_order(order_id=sell_order.id)
  while isinstance(current, RampSellOrder) and current.deposit is None:
      if current.status in FINISHED:
          raise RuntimeError(f"sell order {current.status} before a deposit")
      await asyncio.sleep(5)
      current = await swig.ramp.get_order(order_id=sell_order.id)
  ```
</CodeGroup>

Now prepare, sign, and submit the transfer that moves the crypto out of the
Swig. Your application owns the signing step; the SDK never holds a key.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  import { signPreparedTransaction } from '@swig-wallet/developer-sdk/signers';

  const prepared = await swig.ramp.prepareTransfer({
    orderId: sellOrder.id,
    requesterAuthority: { ed25519: { publicKey: requester } },
    feePayer,
  });

  const signed = await signPreparedTransaction(prepared.preparedTransaction, {
    signTransaction,
  });

  const transfer = await swig.ramp.submitTransfer({
    orderId: sellOrder.id,
    transferId: prepared.transfer.transferId,
    signedTransaction: signed.transaction,
  });
  ```

  ```python Python theme={null}
  from swig_developer_sdk.signers import sign_prepared_transaction

  prepared = await swig.ramp.prepare_transfer(
      order_id=sell_order.id,
      requester_authority={"ed25519": {"publicKey": requester}},
      fee_payer=fee_payer,
  )

  signed = await sign_prepared_transaction(
      prepared.prepared_transaction,
      sign_transaction=sign_transaction,
  )

  transfer = await swig.ramp.submit_transfer(
      order_id=sell_order.id,
      transfer_id=prepared.transfer.transfer_id,
      signed_transaction=signed.transaction,
  )
  ```
</CodeGroup>

The transfer is built from the provider's canonical record rather than the
quote, so a repriced amount funds the deposit correctly. Transfer states are
`unspecified`, `prepared`, `submitted`, `landed`, `failed`, and `expired`; a
transfer settles only at `confirmed` or better.

The prepared transaction is handed over exactly once. If you broadcast it and
then lose it, submit again without a signed transaction to resolve the attempt
that is already live.

<CodeGroup dropdown>
  ```typescript TypeScript theme={null}
  await swig.ramp.submitTransfer({
    orderId: sellOrder.id,
    transferId: prepared.transfer.transferId,
  });
  ```

  ```python Python theme={null}
  await swig.ramp.submit_transfer(
      order_id=sell_order.id,
      transfer_id=prepared.transfer.transfer_id,
  )
  ```
</CodeGroup>

Neither client retries submission. A submission that does not come back
`landed` raises, and the error says which attempt you still own: keep the order
and transfer ids, resolve the same attempt again while it is still confirming,
and prepare a replacement only once one is reported as failed. See
[Submit a transfer](/developer-api/ramp#submit-a-transfer) for the exact
responses.

## Routes behind the clients

| Client method | Route |
| :- | :- |
| `getOptions` / `get_options` | `GET /wallet/api/ramp/options` |
| `getQuotes` / `get_quotes` | `POST /wallet/api/ramp/quotes` |
| `createOrder` / `create_order` | `POST /wallet/api/ramp/orders` |
| `getOrder` / `get_order` | `GET /wallet/api/ramp/orders/{order_id}` |
| `prepareTransfer` / `prepare_transfer` | `POST /wallet/api/ramp/orders/{order_id}/transfer/prepare` |
| `submitTransfer` / `submit_transfer` | `POST /wallet/api/ramp/orders/{order_id}/transfer/submit` |

Your application owns the API key, the customer identifier it sends as
`customerId`, and the signing key for a sell. Keep the API key on the server and
out of any browser bundle. See the [Ramp API](/developer-api/ramp) for the raw
REST contract.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.