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.

path

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
  2. Created the fiat and crypto accounts for the customer
  3. Deposited the crypto
  4. Exchanged crypto for fiat
  5. 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:

Copy
Copied
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:

Copy
Copied
{
	"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.

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 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 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 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:

Copy
Copied
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 :

Copy
Copied
{
	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:

Copy
Copied
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:

Copy
Copied
{
  "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:

Copy
Copied
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 :

Copy
Copied
{
	"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:

Copy
Copied
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:

Copy
Copied
{
  "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.

Copy
Copied
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.

Copy
Copied
{
    "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 :

Copy
Copied
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:

Copy
Copied
{
  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.

Copy
Copied
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

Copy
Copied
{
	"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:

Copy
Copied
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:

Copy
Copied
    "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.

Copy
Copied
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. .

Copy
Copied
{
    "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.

In this case, we will create a FIAT_US counterparty, called Zarina Inc. on the PK001 customer. Counterparties are always at a customer level.

Copy
Copied
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.

Copy
Copied
    "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.

Copy
Copied
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.

Copy
Copied
{
    "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.

Copy
Copied
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.

Copy
Copied
{
	'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 .
  3. Send funds into crypto account - Setup a deposit into the crypto deposit account and get deposit instructions using the deposits endpoint .
  4. Exchange to fiat - Setup the crypto to fiat exchange using the exchanges endpoint . You have to accept the exchange using the exchanges/{EXCHANGE_ID}/accept endpoint .
  5. Withdraw fiat - Setup the fiat withdrawal to an external account using the withdrawals endpoint . You have to accept the withdrawal using the withdrawals/{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.

© 2024 Rail. All Rights Reserved.