As cryptocurrencies grow in popularity and widespread use, it is important that certain fundamental aspects of privacy remain intact in the process of exchanging these currencies. Over-the-counter (OTC) trading offers a way for market participants to transact in orders of arbitrary size without disrupting the market and thus tipping off other participants. What does this mean? In order to retain privacy and asset value, users need to get off of exchanges.

In April 2018, an estimated $250 million to $30 billion dollars were exchanged through OTC trading per day compared to $15 billion traded on exchanges daily in the same timeframe.

Source: Getty Images
Source: Getty Images

What is OTC? Who uses it?

Over-the-counter trading is a deal between two parties that does not take place on an exchange. Traditionally, OTC desks have offered trading securities that are unlisted on exchanges to avoid the complexity of listing requirements or paying exorbitant fees. However, in the cryptocurrency market, OTC desks have grown in popularity with two types of users: miners and investors. Miners are looking to liquidate their rewards without dropping the price, while investors want to buy large amounts of assets without reducing market supply. One key feature of OTC, especially within the crypto sphere, is the anonymity provided — there are no order books.

Why trade OTC?

Cryptocurrency exchanges, outside of the top ten assets, often have very low liquidity which results in shallow order books and volatile prices. As such, a large order is unlikely to be filled immediately. It then becomes necessary to break up the order into smaller pieces to immediately fill orders at the cost of reduced prices (known as slippage). OTC eliminates slippage and bolsters the amount of liquidity available on exchanges, which in turn reduces volatility and results in deeper order books.

Let’s say that the market is very volatile and I’m trying to quickly sell off 50 BTC on an exchange. By looking at the order books, I can see the spot price is at ~$10544. The quantities in the buy orders, however, are not large enough to fill a sell order at this price. This means I’ll have to lower the price to meet deeper orders in the book — and the deeper I go, the less profit I make. Not only this, but my large sell order can push the spot price of BTC even lower.

OTC trades avoid disrupting the market. Let’s imagine that I’m an early investor in a new token which we’ll call CharityToken. For each dollar a participating company donates to charity, a CharityToken is minted and given to the company. CharityTokens can then be spent in some open marketplace for various services. We can assume I need to exchange my tokens for another asset, but as an early investor:

  • I support the goal of the project and do not want to erode public trust
  • I do not want to negatively affect the price, thus reducing overall profit

Many projects, in a situation like this, would prefer to buy back their tokens rather than suffer a negative impact on their market. If CharityToken works with an OTC provider, it becomes simple for an investor to safely offload their assets without affecting the future of the project.

Getting Started with OTC Trading

I’ll be using the Carbon OTC API platform (currently in beta), which allows us to make OTC quotes as well as deposit, exchange and withdraw funds. Carbon is a crypto fintech startup that currently offers fiat-to-crypto on-ramps, and will soon also provide crypto-to-crypto exchanges and crypto-to-fiat off-ramps. Liquidity is handled by our off-shore partners and services are not currently available domestically. You’ll need some familiarity with Python 3 and the requests library.

This walkthrough will detail step-by-step instructions and explanations, but as it is done in the sandbox (testnet) environment, it may not perfectly coincide with the production (mainnet) environment.

Production Endpoint: https://api.carbon.money
Sandbox Endpoint: https://sandbox.carbon.money

We’ll need to create a super-user first, which we can consider to be a master account used solely for administration and authorization.

import requestsroot_path = 'https://sandbox.carbon.money'
create_super_path = '/v1/create/super'
data = {
'emailAddress': 'julian1@example-email.xyz',
'password': 'Password1234',
'companyName': '',
'firstName': 'Julian',
'lastName': 'Kocher'
}
req = requests.post(root_path + create_super_path, data)
print(req)
print(req.text)

Which gives us the console response (cleaned for readability):

<Response[200]>{“message”: ”Successfully created new user! Please record and save your UUID so Carbon Fiber’s team can approve you”,”uuid”: ”c7c3ef5c-d936–49dc-bbe6-dd4069472400",”jwtToken”: ”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjN2MzZWY1Yy1kOTM2LTQ5ZGMtYmJlNi1kZDQwNjk0NzI0MDAiLCJzdXBlclVzZXIiOnRydWUsImNvbnRhY3QiOmZhbHNlLCJlbWFpbCI6Imp1bGlhbjFAZXhhbXBsZS1lbWFpbC54eXoiLCJpYXQiOjE1NjU3MjI3OTJ9.3gAOcA7kTo_fQaNnCXITdT0bUA-XH0zaeoW-R_wVL5I”,”webhookSecret”: ”u894dxPvJa”}

If we were on the production , we would need to get KYC approved in order to access any Carbon services at this point. However, we’re in the sandbox so KYC won’t be necessary now.

Moving on, we need to to use the jwtToken(JSON Web Token) with all of our future requests so the Carbon can correctly identify and authorize us. We can now create a contact , which is how we interact with Carbon services.

import requestsheaders = {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjN2MzZWY1Yy1kOTM2LTQ5ZGMtYmJlNi1kZDQwNjk0NzI0MDAiLCJzdXBlclVzZXIiOnRydWUsImNvbnRhY3QiOmZhbHNlLCJlbWFpbCI6Imp1bGlhbjFAZXhhbXBsZS1lbWFpbC54eXoiLCJpYXQiOjE1NjU3MjI3OTJ9.3gAOcA7kTo_fQaNnCXITdT0bUA-XH0zaeoW-R_wVL5I'
}
root_path = 'https://sandbox.carbon.money'
create_contact_path = '/v1/contact/create'
data = {
'emailAddress': 'otc_test@example-email.xyz',
}
req = requests.post(root_path + create_contact_path, data, headers = headers)print(req)
print(req.text)

Our response:

<Response[200]>{
"message": "successfully created new contact!",
"code": 200,
"details":
{
"contactId": "72e75284-8010-4bbd-bfae-9843fcaf5b3c",
"memo": "59356f62e57767c827a3",
"addresses": {
"tron": "TA6dYVjnZtE9mHCSYm5BCtmMa94JDEk42w",
"eth": "0xd50d569810C445c283A64731f995d9Eb2B660d5F"
}
}
}

The data we’ve received from creating a contact is important — our contactId is necessary to make quotes and submit trades, and the remaining information pertains to deposits.

The next two steps can be performed in any order:

  1. We can deposit funds (EOS, TRX, ETH) in preparation for an OTC trade.
  2. We can get a quote for a desired crypto-asset (EOS, TRX, ETH, CUSD).

Since quotes have an active lifetime of 60 seconds, we’ll deposit our funds first and then request our desired quote pair afterwards.

Making a Deposit

The response from making a contact contains the information we need to make a deposit. For the Ethereum and Tron blockchains, we can simply send our funds to our respective, unique, custody address provided in the addresses field of our contact creation response. For EOS, we have to send our funds to the Carbon custody account with our unique memo.

Deposit Routes (for the contact we just created):
Ethereum: send funds to 0xd50d569810C445c283A64731f995d9Eb2B660d5F
Tron: send funds to TA6dYVjnZtE9mHCSYm5BCtmMa94JDEk42w
EOS: send funds to account carbonteswal with memo 59356f62e57767c827a3

Note: carbonteswal is the Carbon custody account on sandbox . The production custody account is qindynasty11.

In preparation for making quote, I send 300 EOS through Scatter.

Image for post
Image for post
Don’t forget the memo!

Getting a Quote

Now that I’ve deposited my EOS, I’m ready to get an OTC quote. Following the sample provided above:

get_quote_path = '/v1/otc/getPrice'data = {
'contactId': '72e75284-8010-4bbd-bfae-9843fcaf5b3c',
'input': 'eos',
'output': 'eth',
'quantity': 300
}
req = requests.post(root_path + get_quote_path, data, headers = headers)

This results in the response:

<Response [200]>{
"token":"0e48f7ebac54e65e",
"exchangeRate":"0.019562286961201156126",
"input":"300 EOS",
"output":"5.8686860883603468378 ETH",
"timestamp":1565726930105,
"quantity":"300",
"usdValue":"1225.81"
}

We have our quote — I’m exchanging 300 EOS for ~5.8687 ETH (valued at $1225.81). By contrast, CoinMarketCap’s Currency Converter calculates that 300 EOS is equivalent to $1218.63 at the time of quoting. Of course, this also doesn’t account for slippage!

Performing an OTC Trade

We have everything we need at this point — our funds are deposited and our quote has been generated. By submitting the token, (within its 60 seconds lifespan, the swap will be performed.

submit_token_path = '/v1/otc/submit'data = {
'contactId': contactId,
'token': '0e48f7ebac54e65e',
}
req = requests.post(root_path + submit_token_path, data, headers = headers)

Which (in this case) returns:

<Response [200]>{
"message":"Performing swap now.",
"inputSwap":"eos input transfer pending",
"outputSwap": "0xfb07d24002eb17bcb09c4cd7597d15b4a206b61295995f8d967d4578f5a318e9"
}

If the swap if performed successfully, we should get a message like above. Our EOS is exchanged with Carbon and in return, we should have ETH in our Ethereum custody account (the outputSwap transaction).

Note: EOS does not have a transaction ID because EOS transfers are handled internally. Refer to the Carbon documentation for more information.

Image for post
Image for post
The outputSwap receipt.

Withdrawing Funds

Now that we’ve executed our trade and received our funds, we may want to place them into cold storage for security. Let’s look at how we can do this:

withdraw_path = '/v1/otc/withdraw'data = {
'contactId': contactId,
'chain': 'eth',
'address': '0x33E2a10456669E4CeeBFa693ED567a8Fd5E2e9d6',
'asset': 'eth',
'quantity': '5.868665088360346838'
}
req = requests.post(root_path + withdraw_path, data, headers=headers)

A simple way to withdraw all your funds is to request to withdraw your full amount, which will return an error that the amount requested is greater than the balance after gas fees have been applied. Simply replace the quantity with the modified balance provided by the response.

<Response[200]>{
"details": {
"txId": "0x9e93296ba2a7ec0ee9482b4c3ddb571e9bd729293a3427b662c641e44568a131",
"to":"0x33E2a10456669E4CeeBFa693ED567a8Fd5E2e9d6",
"quantity":"5.868665088360346838 ETH"
},
"message":"withdrawal in progress"
}

The withdrawal has been performed — we can check the actual status of the withdrawal by looking up the txId on an Ethereum Testnet Explorer.

Related Services Provided

If the deposit/quote/submit/withdraw cycle is more involved than you prefer, or if you’d like to try a more simple form of OTC trade, Carbon also offers a two-part OTC trade that only requires getting a quote and then depositing funds to seamlessly receive the exchanged asset in the user-provided address.

Also be on the lookout for our on-ramp and crypto-to-fiat off-ramp tutorials which will be available soon.

Disclaimer: Carbon designed and developed this software at the request of an offshore partner.

M.S. in Computer Science @ NYU

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store