Off-ramp Use Case Build Guide
If you haven’t already, Sign Up for Sandbox Access 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 for the list of supported assets.
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:
- Authenticated your request
- Created the fiat and crypto accounts for the customer
- Deposited the crypto
- Exchanged crypto for fiat
- Withdrawn fiat to an external account
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:
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:
{
"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
andcustomers:write
so we can create our customer and get information about them. -
accounts:read
andaccounts:write
so we can create our accounts and get information about them. -
exchanges:read
andexchanges:write
so we can create an exchange operation and get information about it -
deposits:read
anddeposits:write
so we can create a deposit operation and get information about it -
withrawals:read
andwithdrawals:write
so we can create a withdrawal operation and get information about it
The full list of scopes are available here.
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.
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 isPK001
. -
An
account_id
for each account that will be the unique identifier for that specific account. Here we’ll usePK001_USD.001
as our fiat account id andPK001_USDC.001
for our crypto account id. -
The
product_id
: This is the type of account to be opened. For the fiat account, it isDEPOSIT_BASIC
. For crypto, it isDEPOSIT_BASIC
. Different products have different capabilities. Click here 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 theasset_type_id
isFIAT_TESTNET_USD
. For the USDC account, theasset_type_id
isETHEREUM_GOERLI_USDC
. Click here 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:
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 :
{
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:
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:
{
"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:
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 :
{
"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:
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:
{
"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.
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 theaddress
specified on theblockchain
in the instructions. Given the immutable nature of the blockchain, we cannot guarantee recovery of the funds otherwise.
{
"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 (requires signup) or All That Node (no signup needed). You can also send a note at support@layer2financial.com.
Once you’ve deposited funds, you can check they are in the account by querying the accounts/deposits/{ACCOUNT_ID}
endpoint :
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:
{
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
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.
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
{
"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:
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:
"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. .
{
"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:
- create a counterparty
- setup the withdrawal and
- accept the withdrawal.
Create a Counterparty
To create the counterparty, call the counterparties
endpoint.
In this case, we will create a FIAT_US
counterparty, called Zarina Inc. on the PK001
customer. Counterparties are always at a customer level.
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.
"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
.
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.
{
"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.
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.
{
'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:
-
Authenticate
-Authenticate using the OAuth Endpoint to get the
AUTH_TOKEN
. -
Create accounts for the customer
- Create a fiat deposit account and crypto deposit account using the
accounts/deposits
endpoint . -
Send funds into crypto account
- Setup a deposit into the crypto deposit account and get deposit instructions using the
deposits
endpoint . -
Exchange to fiat
- Setup the crypto to fiat exchange using the
exchanges
endpoint . You have to accept the exchange using theexchanges/{EXCHANGE_ID}/accept
endpoint . -
Withdraw fiat
- Setup the fiat withdrawal to an external account using the
withdrawals
endpoint . You have to accept the withdrawal using thewithdrawals/{WITHDRAWAL_ID}/accept
endpoint
To dive deeper into what you can do on the Rail platform, head to our API documentation.
Contact Us - We’d love to hear your thoughts, and you can contact the team via slack, website or email support@layer2financial.com.