# Off-ramp Use Case Build Guide *If you haven’t already, [Sign Up for Sandbox Access](https://rail.iom/contact-us) to get your client ID and secret to work through this Build Guide!* ## What is the off-ramp use case? With the off-ramp use case, your customers (or their payors) can send in digital assets, swap them for fiat, hold or withdraw the fiat to an external account via the supported payment rails. We support a number of different digital assets today. [Click here](/guides/assets) for the list of supported assets. Off-Ramp Flow Diagram ## Build Guide This guide should take no longer than 10 minutes and will allow you to execute a off-ramp in Sandbox. More specifically, you'll open USDC and USD accounts for an already onboarded customer, deposit USDC, exchange it to USD and then withdraw the USD to an external account. By the end of this guide you will have: 1. [Authenticated your request](#1--authenticating-your-request) 2. [Created the fiat and crypto accounts for the customer](#2-creating-the-fiat-and-crypto-accounts) 3. [Deposited the crypto](#3-depositing-crypto) 4. [Exchanged crypto for fiat](#4-exchanging-crypto-for-fiat) 5. [Withdrawn fiat to an external account](#5-withdrawing-fiat) Let’s go! ## 1. Authenticating your request Every request you make to a Rail API endpoint requires an `AUTH_TOKEN`. We secure our endpoints using standards based OAuth2 Client Credentials Grant and scopes. This makes authenticating secure and easy. To obtain the `AUTH_TOKEN` , you will need to authorize your account using your `BASE_64_ENCODED_CLIENTID_AND_SECRET` with the scopes you require: ```bash curl --location --request POST 'https://auth.layer2financial.com/oauth2/ausbdqlx69rH6OjWd696/v1/token?grant_type=client_credentials&scope=accounts:read+settlements:read+customers:read+customers:write+accounts:write+withdrawals:read+withdrawals:write+adjustments:read+adjustments:write+exchanges:read+exchanges:write+transfers:read+transfers:write+deposits:read+deposits:write+applications:read+applications:write' \ --header 'Accept: application/json' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Cache-Control: no-cache' \ --header 'Authorization: Basic {BASE_64_ENCODED_CLIENTID_AND_SECRET}' ``` This gives you a response object containing your `AUTH_TOKEN:` ```javascript { "token_type":"Bearer", "expires_in":43200, "access_token": {AUTH_TOKEN}, "scope":"customers:read customers:write accounts:write accounts:read exchanges:read exchanges:write withdrawals:read withdrawals:write deposits:read deposits:write" } ``` The scopes we’ll need for this tutorial are: - `customers:read` and `customers:write` so we can create our customer and get information about them. - `accounts:read` and `accounts:write` so we can create our accounts and get information about them. - `exchanges:read` and `exchanges:write` so we can create an exchange operation and get information about it - `deposits:read` and `deposits:write` so we can create a deposit operation and get information about it - `withrawals:read` and `withdrawals:write` so we can create a withdrawal operation and get information about it The full list of scopes are available [here](/guides/authentication). With that done, we can get on to setting up accounts for a individual customer we have pre-created for you: Daniel Lee, with a customer_id of `PK001`. To learn how to create a customer, check out [Applications](/guides/applications). ## 2. Creating the fiat and crypto accounts We need two accounts, a **fiat deposit account** and a **crypto deposit account**. To create these two accounts we need: - The `customer_id` from above. For this example that is `PK001`. - An `account_id` for each account that will be the unique identifier for that specific account. Here we’ll use `PK001_USD.001` as our fiat account id and `PK001_USDC.001` for our crypto account id. - The `product_id`: This is the type of account to be opened. For the fiat account, it is `DEPOSIT_BASIC`. For crypto, it is `DEPOSIT_BASIC`. Different products have different capabilities. [Click here](/guides/products) to learn more about the various products we support. - The `asset_type_id`: The asset type that the accounts are going to use. Our fiat account is in USD, so the `asset_type_id` is `FIAT_TESTNET_USD`. For the USDC account, the `asset_type_id` is `ETHEREUM_GOERLI_USDC`. [Click here](/guides/assets) to learn more about the various assets we support. ### Creating our fiat account For our fiat account, we’re going to call the `/accounts/deposits` endpoint: ```bash curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/accounts/deposits' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' \ --data-raw '{ "customer_id": "PK001", "account_to_open": { "account_id": "PK001_USD.001", "product_id": "DEPOSIT_BASIC", "asset_type_id": "FIAT_TESTNET_USD" } }' ``` The response contains the `account_id` that you passed in : ```jsx { data: { id: "PK001_USD.001" } } ``` Again, we can use that `account_id` and a `GET` request to the `accounts/deposits/${account_id}` endpoint to retrieve information about this account: ```bash curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/PK001_USD.001' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' ``` The response object will have the status of the account, product and asset type ids, and the current and available balance: ```jsx { "data": { "id": "PK001_USD.001", "status": "OPEN", "asset_type_id": "FIAT_TESTNET_USD", "product_id": "DEPOSIT_BASIC", "current_balance": 0, "available_balance": 0 } } ``` ### Creating our crypto account For our crypto account, we’re going to call the `/accounts/deposits` endpoint as well: ```bash curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/accounts/deposits' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' \ --data-raw '{ "customer_id": "PK001", "account_to_open": { "account_id": "PK001_USDC.001", "product_id": "DEPOSIT_BASIC", "asset_type_id": "ETHEREUM_GOERLI_USDC" } }' ``` The response contains the `account_id` that you passed in : ```javascript { "data": { "id": "PK001_USDC.001" } } ``` You can use that `ACCOUNT_ID` and a `GET` request to the `/accounts/deposits/` endpoint to retrieve information about this account: ```bash curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/PK001_USDC.001' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' ``` The response object will have the status of the account, product and asset type ids, and the current and available balance: ```jsx { "data": { "id": "PK001_USDC.001", "status": "OPEN", "asset_type_id": "ETHEREUM_GOERLI_USDC", "product_id": "DEPOSIT_BASIC", "current_balance": 0, "available_balance": 0 } } ``` That’s it for creating our accounts. Now let's start transacting! ## 3. Depositing crypto Next, we have to deposit crypto. you can do that by calling the `deposits` endpoint. ```bash curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/deposits' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' \ --data-raw '{ "deposit_type": "PUSH", "deposit_destination": { "destination_account_id": "PK001_USDC.001" } }' ``` You will get back the following response with the crypto deposit instructions that you can supply to your customers. Once the crypto funds are deposited, the corresponding account will be credited. > > NOTE: It is critical that the funds are sent in the `asset_type_id` to the `address` specified on the `blockchain` in the instructions. Given the immutable nature of the blockchain, we cannot guarantee recovery of the funds otherwise. ```javascript { "data": { "id": "c6bdf4c0-96e6-4b73-b64c-2fc5e277147d", "status": "REQUESTED", "created_timestamp": "2023-07-08T19:05:41.81739-04:00", "deposit_type": "PUSH", "deposit_destination": { "destination_account_id": "PK001_USDC.001", "asset_type_id": "ETHEREUM_GOERLI_USDC" }, "customer_name": "PK001", "deposit_source": { "deposit_instructions": [ { "instruction_type": "CRYPTO", "asset_type_id": "ETHEREUM_GOERLI_USDC", "address": "0x1AcC19BE42f38669b684E819206A82d95D026eb6", "blockchain": "ethereum", "network": "goerli" } ] } } } ``` Before you can go to the next step, all you need is the `address` from above to send a 5 USDC deposit in on the goerli network. With the `address`, you can deposit funds into this account using any Goerli faucet, such as the [Alchemy Goerli faucet](https://goerlifaucet.com/) (requires signup) or [All That Node](https://www.allthatnode.com/faucet/ethereum.dsrv) (no signup needed). You can also send a note at [support@layer2financial.com](mailto:support@layer2financial.com). Once you’ve deposited funds, you can check they are in the account by querying the `accounts/deposits/{ACCOUNT_ID}` endpoint : ```bash curl --location --request GET 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/PK001_USDC.001' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' ``` The current_balance should have changed to the amount you added to the account: ```javascript { data: { id: "PK001_USDC.001", status: "OPEN", asset_type_id: "ETHEREUM_GOERLI_USDC", product_id: "DEPOSIT_FIAT_CRYPTO", current_balance: 5.000000000000000000, available_balance: 5.000000000000000000 } } ``` If the `available_balance` is showing 0, the transaction is still going through all the AML and Fraud checks. Once the transaction is fully confirmed, the `available_balance` will be updated. You can also check the account balance through the [Dashboard](https://management-sandbox.layer2financial.com/customers/PK001/account/PK001_USDC.001) Now that the USDC is in the fiat account, lets exchange it for some USDC! ## 4. Exchanging crypto for fiat To exchange the crypto to fiat, you have to call the `/exchanges/market` endpoint, passing in the crypto account as the source, and the fiat account as the destination. ```bash curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/exchanges/market' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' \ --data-raw '{ "source_account_id": "PK001_USDC.001", "destination_account_id": "PK001_USD.001", "amount": 5, "action": "FIX_SOURCE" }' ``` This will give us the response with the details of the `REQUESTED` exchange. Save the `id` of the exchange. You will need it for the `EXCHANGE_ID` in the next operation ```jsx { "data": { **"id": "c3ac732e-fae0-4585-a7c3-71e24b8e7293"**, "status": "REQUESTED", "created_timestamp": "2022-11-11T09:32:14.450333", "action": "FIX_SOURCE", "source_details'": { "source_account_id": "PK001_USDC.001", "asset_type_id": "ETHEREUM_GOERLI_USDC", "amount_to_debit": 5 }, "destination_details": { "destination_account_id": "PK001_USD.001", 'asset_type_id': 'FIAT_TESTNET_USD', 'amount_to_credit': 4.9 } } } ``` Given its a market order, the above is NOT a quote. It is merely the latest market price. When you accept the exchange, it will be sent to the liquidity partners to execute at the current market price, which may be different. We can use the `id` in the URL to accept this exchange: ```bash curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/exchanges/{EXCHANGE_ID}/accept' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' ``` If the exchange is accepted, you’ll get this response: ```javascript "data": { "id": "119bee2c-9f03-41c5-9545-1b5c7ea42764", "status": "ACCEPTED", "created_timestamp": "2023-07-08T23:24:33.937001-04:00", "action": "FIX_SOURCE", "source_details": { "source_account_id": "PK001_USDC.001", "asset_type_id": "ETHEREUM_GOERLI_USDC", "amount_to_debit": 5.000000000000000000 }, "destination_details": { "destination_account_id": "PK001_USD.001", "asset_type_id": "FIAT_TESTNET_USD" } } ``` At this point, the exchange operation has been accepted but NOT executed. You will see a Pending transaction in the source account. You can see the list of transactions by calling the following. ``` curl --location 'https://sandbox.layer2financial.com/api/v1/accounts/deposits/PK001_USDC.001/transactions' \ --header 'Authorization: Bearer {AUTH_TOKEN}' ``` You will see a response that looks as follows. You will see the Trade is still in pending status. . ```javascript { "data": { "transactions": [ { "id": "6f9acb3d-7370-497a-b70b-eb319a8e9247", "value": 5.000000000000000000, "transaction_date": "2023-06-21T18:46:04.482Z", "transaction_posted_date": "2023-06-21T18:46:04.482Z", "transaction_status": "POSTED", "description": "TRANSFER_IN", "transaction_type": "TRANSFER_IN" }, { "id": "79095047-6945-404f-a98d-d7af42544dc5", "value": -5.000000000000000000, "transaction_date": "2023-07-09T03:29:38.060Z", "transaction_posted_date": "2023-07-09T03:29:38.060Z", "transaction_status": "PENDING", "description": "TRADE", "transaction_type": "TRADE" } ] }, "links": { "self": "/v1/accounts/deposits/PK001_USDC.001/transactions?page=0&page_size=100&order=ASC", "first": "/v1/accounts/deposits/PK001_USDC.001/transactions?page=0&page_size=100&order=ASC", "prev": "/v1/accounts/deposits/PK001_USDC.001/transactions?page=0&page_size=100&order=ASC", "next": "/v1/accounts/deposits/PK001_USDC.001/transactions?page=0&page_size=100&order=ASC", "last": "/v1/accounts/deposits/PK001_USDC.001/transactions?page=0&page_size=100&order=ASC" } } ``` Once it is complete, the `transaction_status` will flip to `POSTED` on the source account, and there will be a corresponding posted transaction in the destination account as well, in the case the `PK001_USD.001` account. Now that exchange is complete, lets see how to withdraw the USD funds to an external account. ## 5. Withdrawing fiat You can leave the fiat in the account, or you can withdraw the fiat to an external account. Withdrawing any funds on Rail has 3 steps: 1. create a counterparty 2. setup the withdrawal and 3. accept the withdrawal. **Create a Counterparty** To create the counterparty, call the [`counterparties` endpoint](/api-docs/openapi/rail-spec#operation/createCounterparty). In this case, we will create a `FIAT_US` counterparty, called Zarina Inc. on the `PK001` customer. Counterparties are always at a customer level. ```bash curl --location 'https://sandbox.layer2financial.com/api/v1/counterparties' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' \ --data '{ "customer_id": "PK001", "counterparty_type": "FIAT_US", "is_international": false, "supported_rails": [ "ACH" ], "profile": { "name": "Zarina Inc." }, "account_information": { "asset_type_id": "FIAT_TESTNET_USD", "account_number": "894375894", "type": "CHECKING", "routing_number": "125061207" } }' ``` A successful 200 response as follows will provide you the `id`, which is the `counterparty_id` you will need to setup the withdrawal. ```javascript "data": { "id": "ee0367e7-fdb0-4272-b062-c04d2a8014b5" } ``` **Setup the Withdrawal** To setup the withdrawal, use the `/withdrawals` endpoint by passing the above id as the `COUNTERPARTY_ID`. ```bash curl --location 'https://sandbox.layer2financial.com/api/v1/withdrawals' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' \ --data '{ "withdrawal_rail": "ACH", "description": "Withdraw usd", "source_account_id": "PK001_USD.001", "amount":"4.9", "destination_counterparty_id": "{COUNTERPARTY_ID}", "memo": "withdrawal" }' ``` A `200` response will give you the id of the withdrawal. This will be the `WITHDRAWAL_ID` in the subsequent step. ```javascript { "data": { "id": "887804df-13c2-4019-835b-08c92ed77c72", "status": "REQUESTED" } } ``` **Accept Withdrawal** Now its time to accept the withdrawal, by calling the `withdrawals/{WITHDRAWAL_ID}/accept` endpoint. ```bash curl --location --request POST 'https://sandbox.layer2financial.com/api/v1/withdrawals/{WITHDRAWAL_ID}/accept' \ --header 'Authorization: Bearer {AUTH_TOKEN}' \ --header 'Content-Type: application/json' \ ``` If its successful, you will get the following response. ```javascript { 'data': { "id":"c67e81e5-3784-45d7-a2b9-b0c7f19a466f", "status":"ACCEPTED", } } ``` Similar to the exchange operation, the withdrawal is not executed just yet. You will see a transaction in `PENDING` status on the account until the withdrawal has been executed and funds are on their way. Once that happens, the transaction will switch to `POSTED` status. ## Summary Let’s review: 1. **Authenticate** -Authenticate using the OAuth Endpoint to get the `AUTH_TOKEN`. 2. **Create accounts for the customer** - Create a fiat deposit account and crypto deposit account using the [`accounts/deposits` endpoint](/api-docs/openapi/rail-spec#operation/openAccount). 3. **Send funds into crypto account** - Setup a deposit into the crypto deposit account and get deposit instructions using the [`deposits` endpoint](/api-docs/openapi/rail-spec#operation/createDeposits). 4. **Exchange to fiat** - Setup the crypto to fiat exchange using the [`exchanges` endpoint](/api-docs/openapi/rail-spec#operation/createExchangeMarket). You have to accept the exchange using the [`exchanges/{EXCHANGE_ID}/accept` endpoint](/api-docs/openapi/rail-spec#operation/acceptExchange). 5. **Withdraw fiat** - Setup the fiat withdrawal to an external account using the [`withdrawals` endpoint](/api-docs/openapi/rail-spec#operation/createWithdrawal). You have to accept the withdrawal using the [`withdrawals/{WITHDRAWAL_ID}/accept` endpoint](/api-docs/openapi/rail-spec#operation/acceptWithdrawal) To dive deeper into what you can do on the Rail platform, head to our [API documentation](/api-docs/openapi/rail-spec).