---
updatedAt: 2026-08-31T12:24:21.000Z
agentTools:
  projectIndex: https://docs.cybrid.xyz/llms.txt
---

# Off-Ramping USDC to a Customer's External Wallet

```python Python
#
# Constants
#

CUSTOMER_TYPE_INDIVIDUAL = "individual"
CUSTOMER_TYPE_BUSINESS = "business"

IDENTITY_VERIFICATION_TYPE_KYC = "kyc"
IDENTITY_VERIFICATION_TYPE_BANK_ACCOUNT = "bank_account"

IDENTITY_VERIFICATION_METHOD_ID_AND_SELFIE = "id_and_selfie"
IDENTITY_VERIFICATION_METHOD_ACCOUNT_OWNERSHIP = "account_ownership"

IDENTITY_VERIFICATION_EXPECTED_BEHAVIOUR_PASSED_IMMEDIATELY = "passed_immediately"

STATE_CREATED = "created"
STATE_PENDING = "pending"
STATE_COMPLETED = "completed"
STATE_SETTLING = "settling"
STATE_WAITING = "waiting"
STATE_UNVERIFIED = "unverified"
STATE_VERIFIED = "verified"

OUTCOME_PASSED = "passed"
OUTCOME_FAILED = "failed"

ASSET_CODE_USD = "USD"
ASSET_CODE_USDC = "USDC"

TRADING_PAIR_USDC_USD = "USDC-USD"

ACCOUNT_TYPE_FIAT = "fiat"
ACCOUNT_TYPE_TRADING = "trading"

DEPOSIT_BANK_ACCOUNT_TYPE_MAIN = "main"

WORKFLOW_TYPE_PLAID = "plaid"

WORKFLOW_KIND_TOKEN_CREATE = "link_token_create"

EXTERNAL_BANK_ACCOUNT_KIND_PLAID = "plaid"

LANGUAGE_EN = "en"

LINK_CUSTOMIZATION_DEFAULT = "default"

QUOTE_PRODUCT_TYPE_FUNDING = "funding"
QUOTE_PRODUCT_TYPE_TRADING = "trading"
QUOTE_PRODUCT_TYPE_CRYPTO_TRANSFER = "crypto_transfer"

QUOTE_SIDE_DEPOSIT = "deposit"
QUOTE_SIDE_WITHDRAWAL = "withdrawal"
QUOTE_SIDE_BUY = "buy"

TRANSFER_TYPE_FUNDING = "funding"
TRANSFER_TYPE_CRYPTO = "crypto"

#
# Helpers
#

logger = logging.getLogger()

def create_quote(
    api_client,
    customer: Customer,
    product_type: str,
    side: str,
    deliver_amount: int | None = None,
    receive_amount: int | None = None,
    symbol: str | None = None,
    asset: str | None = None,
) -> Quote:
    if deliver_amount is not None:
        amount = deliver_amount
    if receive_amount is not None:
        amount = receive_amount

    if symbol is not None:
        logger.info(f"Creating {side} {product_type} quote for {symbol} of {amount}")
    if asset is not None:
        logger.info(f"Creating {side} {product_type} quote for {asset} of {amount}")

    kwargs = {
        "product_type": product_type,
        "customer_guid": customer.guid,
        "side": side,
    }

    if symbol is not None:
        kwargs["symbol"] = symbol
    if asset is not None:
        kwargs["asset"] = asset
    if deliver_amount is not None:
        kwargs["deliver_amount"] = deliver_amount
    if receive_amount is not None:
        kwargs["receive_amount"] = receive_amount

    api_instance = QuotesBankApi(api_client)
    post_quote = PostQuote(**kwargs)

    try:
        # https://docs.cybrid.xyz/reference/createquote
        api_response = api_instance.create_quote(post_quote)
        logger.info("Created quote.")
        return api_response
    except cybrid_api_bank.OpenApiException as e:
        logger.error(f"An API error occurred when creating quote: {e}")
        raise e
        
        
def create_transfer(
    api_client: cybrid_api_bank.ApiClient,
    quote: Quote,
    transfer_type: str,
    source_platform_account: Account | None = None,
    destination_platform_account: Account | None = None,
    external_wallet: ExternalWallet | None = None,
    external_bank_account: ExternalBankAccount | None = None,
    source_participant: Customer | Bank | None = None,
    destination_participant: Customer | Bank | None = None,
) -> Transfer:
    logger.info(f"Creating {transfer_type} transfer...")

    api_instance = TransfersBankApi(api_client)

    transfer_params = {
        "quote_guid": quote.guid,
        "transfer_type": transfer_type,
    }

    if source_platform_account is not None:
        transfer_params["source_account_guid"] = source_platform_account.guid
    if destination_platform_account is not None:
        transfer_params["destination_account_guid"] = destination_platform_account.guid
    if external_wallet is not None:
        transfer_params["external_wallet_guid"] = external_wallet.guid
    if external_bank_account is not None:
        transfer_params["external_bank_account_guid"] = external_bank_account.guid
    if source_participant is not None:
        if isinstance(source_participant, Bank):
            type = "bank"
        else:
            type = "customer"

        transfer_params["source_participants"] = [
            PostTransferParticipant(
                type=type,
                amount=quote.deliver_amount,
                guid=source_participant.guid,
            )
        ]
    if destination_participant is not None:
        if isinstance(destination_participant, Bank):
            type = "bank"
        else:
            type = "customer"

        transfer_params["destination_participants"] = [
            PostTransferParticipant(
                type=type,
                amount=quote.receive_amount,
                guid=destination_participant.guid,
            )
        ]

    post_transfer = PostTransfer(**transfer_params)

    try:
        # https://docs.cybrid.xyz/reference/createtransfer
        api_response = api_instance.create_transfer(post_transfer)
        logger.info("Created transfer.")
        return api_response
    except cybrid_api_bank.OpenApiException as e:
        logger.error(f"An API error occurred when creating transfer: {e}")
        raise e


def get_transfer(api_client: cybrid_api_bank.ApiClient, guid: str) -> Transfer:
    logger.info("Getting transfer %s", guid)

    api_instance = TransfersBankApi(api_client)

    try:
        # https://docs.cybrid.xyz/reference/gettransfer
        transfer = api_instance.get_transfer(guid)
        logger.info("Got transfer %s", transfer.guid)
        return transfer
    except cybrid_api_bank.OpenApiException as e:
        logger.error(f"An API error occurred when getting transfer: {e}")
        raise e


def wait_for_transfer(
    api_client: cybrid_api_bank.ApiClient,
    transfer: Transfer,
    expected_states: list[str],
) -> None:
    sleep_count = 0
    transfer_state = transfer.state
    final_states = expected_states
    while transfer_state not in final_states and sleep_count < Config.TIMEOUT:
        time.sleep(1)
        sleep_count += 1
        transfer = get_transfer(api_client, transfer.guid)
        transfer_state = transfer.state
    if transfer_state not in final_states:
        raise BadResultError(f"Transfer has invalid state: {transfer_state}")

    logger.info(f"Transfer successfully completed with state {transfer_state}")
    
    
def get_account(api_client: cybrid_api_bank.ApiClient, guid: str) -> Account:
    logger.info("Getting account: %s", guid)

    api_instance = AccountsBankApi(api_client)

    try:
        # https://docs.cybrid.xyz/reference/getaccount
        account = api_instance.get_account(guid)
        logger.info("Got account: %s", account.guid)
        return account
    except cybrid_api_bank.OpenApiException as e:
        logger.error(f"An API error occurred when getting account: {e}")
        raise e
        
        
#
# Off-ramping crypto (USDC) to a customer's external wallet
#
# This recipe will off-ramp USDC from a customer's trading account to their external
# wallet.
#

#
# First, a quote of `product_type` `crypto_transfer` must be created with the side set to
# `withdrawal`.
#
# Here we are asking for 25 USDC to be sent to  the customer's external wallet off the
# platform.
#
# **Note:** Creating a quote is a _synchronous_ operation.
#
withdrawal_quote = create_quote(
    api_client,
    customer,
    product_type=QUOTE_PRODUCT_TYPE_CRYPTO_TRANSFER,
    side=QUOTE_SIDE_WITHDRAWAL,
    receive_amount=25_000_000,
    asset=ASSET_CODE_USDC,
)

#
# Create a transfer quote
#
# The next step is to initiate a `crypto` transfer to execute the withdrawal from
# the customer's USDC trading account to their external wallet.
#
# In Sandbox, this will complete within a few seconds. In reality, this operation
# will depend on the blockhain and number of confirmations that the platform waits before
# the transfer is considered complete.
#
# For all transfers, source and destination participant(s) must be specified. This
# ensures that all transfers comply with the Travel Rule (i.e., all ultimate originating
# beneficiaries and ultimate receiving beneficiaries are known). For a customer withdrawing
# funds from their trading account to their own external wallet, the source and destination
# is simply just the customer themselves.
#
# To enhance the responsiveness of your application to state changes, you can also register for
# and receive webhooks for transfer state changes See the article here:
# https://docs.cybrid.xyz/docs/webhooks.
#

# Step 1: Initiate the transfer
deposit_transfer = create_transfer(
    api_client,
    withdrawal_quote,
    transfer_type=TRANSFER_TYPE_CRYPTO,
    external_wallet=external_wallet,
    source_participant=customer,
    destination_participant=customer,
)

# Step 2: Wait for the transfer to settle in the `completed` state.
wait_for_transfer(api_client, deposit_transfer, expected_states=[STATE_COMPLETED])

# Once the deposit is `completed`, you can check that the customer has 25 less USDC in their
# trading account by checking the available balance on the account.
trading_account = get_account(api_client, trading_account.guid)
logger.info(
    "Available balance in the trading account: %s", trading_account.platform_balance
)
```

# Generate a crypto_transfer quote

<!-- python@228-251 -->

Generate a quote with `side` `withdrawal` and `product_type` `crypto_transfer` to off-ramp 25 USDC to the customer's external wallet.

# Execute the transfer

<!-- python@253-282 -->

Execute the generated quote specifying the customer's external wallet as the destination.

# Wait for the transfer to be `completed`

<!-- python@284-285 -->

Wait for the transfer to settle in the `completed` state before proceeding.

# Check the customer's trading account balance

<!-- python@287-292 -->

Once the transfer has entered the `completed` state, check that the customer now has 25 less USDC.