Aurum

A Simple Proof-of-Concept Non-Custodial Cryptocurrency Exchange in just 500 lines of code!

Mihir Kumar
Aug 8 · 26 min read

It would come as a surprise for most people to know that there are two billion people in the world who don’t have access to financial services through centralized banks. Even within the United States, 6.5% of households are unbanked and an additional 18.7% of households are underbanked. Not having access to basic financial services can make it much more difficult to lead a normal life condemning the unbanked to a downward financial spiral. Cryptocurrencies can help us remedy this issue, but the financial freedom that cryptocurrencies provide is not compatible with the existing financial system. As a result, many countries like China and Morocco have banned Bitcoin. Banning cryptocurrencies and hence access to financial systems to the unbanked in this manner is currently only effective since most cryptocurrency exchanges are centralized. This centralization is inherently opposed to the promise of decentralized finance of open, public, permissionless blockchains where anyone can be a participant and cannot be removed from the system. Hence it is critical for decentralized exchanges (DEXes in short) to be a viable option for exchanging cryptocurrencies.

A pie chart showing the percentages of unbanked and banked households in the US.
A pie chart showing the percentages of unbanked and banked households in the US.
Figure 1: Percentage of Unbanked and Banked Households in the US in 2017

Several DEXes have opened in the past few years in an attempt to fix these issues and there has been significant improvement in their ease of use with notable examples being 0x and Kyber Network. In this article, I will be describing a simple non-custodial exchange that is easy to understand and can help me demonstrate some new ideas that I feel can improve this space. Many of the ideas here are inspired by my understanding of Arwen, which is a startup trying to mitigate the problems of dealing with centralized exchanges and the previously mentioned Kyber Network which is a promising decentralized exchange for Ethereum-based tokens.

Using Aurum involves interacting with a simple system of smart contracts. For exchanging one cryptocurrency with another, escrow contracts are used by both parties. Two versions of this escrow contract are used, one for holding ERC20 tokens and the other for holding Ether. In this project, we will create a single ERC20 token to trade with Ether and vice-versa. More details about these smart contracts will follow in later sections.

In typical trading situations with an exchange, two common types of trades are relevant to this article, namely RFQs and limit orders.

Request For Quote or RFQs are a type of trade in which the user will submit a request to the exchange, asking for the exchange price of a certain asset. Then the exchange, if willing to participate in the trade, will respond with a quote or exchange price for that asset that is valid for a certain amount of time. If the user is satisfied by the offered exchange price, they can trade their assets. Potential problems with RFQs are that the user might refuse to participate in a trade with an asset that the exchange has made ready to be traded and that the exchange might provide the asset at a price different from the quote.

Limit orders are a situation in which the user informs the exchange of their intent to trade an asset they own and additionally the minimum price at which they are willing to trade it. Both these options are similar since even in RFQ trades the user can enforce a minimum trade price but without disclosing it to the exchange. The possible outcomes when placing a limit order are that the order does not get fulfilled since the exchange never offers a price low enough and the limit order is not filled or that the user is offered a price at or below their expectation and they get their order filled for potentially a better than expected price.

Another important term to understand to grasp the contents of this article is ‘Escrow’. This refers to a trusted third party to facilitate a transaction between two parties distrusting of one another. Escrows are used in all sorts of scenarios in the world in making trades. It is wise to be distrusting of any party when it comes to cryptocurrency transactions. When the user and exchange want to trade cryptocurrency directly with one another, they will each open an escrow smart contract. The escrow opened by the user is called the user escrow and similarly the escrow opened by the exchange is called the exchange escrow.

Going back to RFQs, they can either be unidirectional or bidirectional. Unidirectional RFQs mean that the user can only sell assets from their own user escrow and buy assets from the exchange’s escrow. Bidirectional RFQs mean that the user can sell and buy assets from both their and the exchange’s escrows. In this article, we will limit our discussion to unidirectional RFQs.

Since we are dealing with cryptocurrencies based on computer code, smart contracts allow us to lay out a set of rules and conditions in a manner that solves these inherent problems with trading and create a secure environment for cryptocurrency transactions to take place within.

The advent of Ethereum made it a lot easier to write and deploy these smart contracts. Ethereum smart contracts are programs running on the Ethereum Virtual Machine. The most popular languages in which Ethereum smart contracts are written are Solidity and Vyper. We will exclusively use Solidity for all purposes in this article.


Motivation for improving transactions on centralized and decentralized exchanges

Suppose you are a user on a popular centralized exchange, like Binance or Coinbase. If you want to use their services, you need to verify your identity, and send your cryptocurrency to the exchange. Once you do this, the exchange effectively owns your currency and you are subject to the whims and security vulnerabilities that come with the exchange. Once you make your trade on the exchange, you are free to withdraw your coins. Here we recognize the one main problem of doing trades on a centralized exchange: Needing to hand over your funds to the exchange and slow speed and costs associated with trading on the blockchain.

Instead of a centralized exchange, if we use a decentralized one like 0x or IDEX, both the actions of sending and withdrawing currency occur on the blockchain, which is inherently slow and charges a fee. Here we recognize one problem of trading on DEXes: High transaction fees.

Suppose Alice wants to trade BTC for ETH but without having to deal with the problems that come with using a centralized exchange and knows a person, Bob, who wants to trade ETH for BTC. This situation is referred to as an atomic swap and has a popular solution called Hashed Timelock Contracts or HTLCs. This idea was initially introduced by TierNolan.

In an attempt to mitigate these problems involved in trades using RFQs, we will employ ideas from HTLCs and the Arwen Protocol using Ethereum smart contracts.


Description of HTLCs

In this situation, Alice and Bob agree on a shared puzzle. This puzzle is a secure hash of a secret key chosen by one of the two participants. Let’s assume Alice knows this key. She will proceed to send her BTC to a HTLC deployed on the Bitcoin blockchain. The HTLC will contain a timelock which is a duration of time after which Alice can unilaterally withdraw her BTC from the HTLC back to her wallet. In the case that Alice is the one choosing the puzzle and also has a shorter timelock duration than Bob, posting the key right before Alice’s timelock duration could let Alice take Bob’s assets and then perform a timeout refund to also get her own assets back. Bob would be left cheated out of his part of the trade. This necessitates that the person who chooses the secret has a significantly longer timelock duration that their counterparty so that the counterparty has enough time to withdraw their assets after a solution to the puzzle has been posted.

This HTLC will also contain a function that will allow Bob to withdraw Alice’s BTC into his wallet, provided he can provide the secret key which has the same hash as the puzzle, implying that Alice did share the key with Bob and hence approves withdrawal of her BTC by Bob. Bob will also send his ETH to a HTLC deployed on the Ethereum blockchain with the same puzzle. Now if Alice wishes to withdraw Bob’s ETH, she will need to post the secret key to the ETH blockchain. When she performs a successful withdrawal, Bob can now view the secret key on the ETH blockchain and extract Alice’s BTC from the HTLC on the BTC blockchain. In this transaction, neither Alice nor Bob had to trust a third party to perform this trade. This HTLC setup protects the traders in all non-cooperative/malicious conditions:

  1. Alice places her BTC in an escrow and Bob does not. In this scenario, after the timelock duration of the HTLC has passed, Alice can unilaterally withdraw her BTC from the contract and close it. The same applies to Bob if Alice becomes uncooperative.
  2. Alice and Bob both lock their assets in their respective escrows but Alice turns malicious and wants to steal Bob’s assets. This would be impossible since to withdraw Bob’s assets, Alice would post the secret key on the blockchain giving Bob the ability to immediately withdraw Alice’s BTC.

This approach solves the first problem of using centralized exchanges that we discussed before: giving up custody of your assets to perform trades.


A simplified summary of Arwen Unidirectional RFQs

Now imagine that Alice is a user and Bob is an exchange. Users and exchanges commonly communicate their intents to trade using RFQs and limit orders as discussed before. We will limit our discussion to RFQs in this article. Every single time Alice and Bob perform an atomic swap, they incur blockchain transaction fees and their transaction is capped by the speed of the blockchain at that moment, which can take hours to confirm. This is not at all conducive in scenarios where participants might want to conduct multiple transactions in a short span of time. This is where Arwen brings a key insight and differentiates itself from a standard HTLC atomic swap. It makes the observation that two funded escrows on the blockchain don’t need to close after every trade. Arwen escrows can stay open and trade with each other extremely quickly since these trades are off-blockchain. Only when all desired trades are completed and the participants want to withdraw their final balances to their wallets do the escrows need to close and use the blockchain. This approach solves the problems of slow transaction speeds when making multiple atomic swap trades and the cost of high transaction fees when using DEXes.

In our implementation of Arwen trades, we don’t deal with the case of high frequency trades between escrows to add a degree of simplicity.

In our implementation, this puzzle is simply the SHA256 hash of a string key that is chosen by the exchange since the exchange is the first participant to lock up funds in an escrow. The Arwen escrows contain the same conditions as HTLCs.

Interacting with Arwen’s trading protocols requires the user to install its daemon on their machine. This daemon “performs the cryptographic operations involved in the Arwen Trading Protocol, posts and verifies transactions from relevant blockchains, and stores the secret trading keys used to securely trade against the Arwen escrows” [Source: Section 3.1 of Arwen’s whitepaper]. At the present moment, this daemon is not open source so we cannot look at it’s precise functionality. Hence, we will choose a timeout duration and puzzle assuming the parties have come to an agreement on those two parameters to abstract the daemon’s functionality for our purposes.

After the exchange verifies that the user escrow contains the funds that were agreed upon, the exchange withdraws funds from it in turn allowing the user to withdraw funds from the exchange escrow. Once the funds are withdrawn from the escrow, the smart contract is destroyed. In our implementation, we change the state of the smart contract to CLOSED to abstract this behavior in order to simplify things.

Visual description of the flow of states inside Arwen-inspired smart contracts
Visual description of the flow of states inside Arwen-inspired smart contracts
Figure 2. Different steps in my implementation of a cooperative Arwen trade and the flow of states of the contract during execution. Solid arrows point from caller to callee. Dotted arrows point from the previous state of the escrow to the updated state automatically as a result of function calls. Numbers indicate a possible sequence of function calls described below. Each number corresponds to a function call. Green boxes represent ERC20 escrows which are representative of a non-Ethereum based cryptocurrency escrows and yellow boxes represent ETH escrows. This example assumes user has already requested an RFQ.

The steps in my implementation are detailed as follows:

  1. The exchange responds to the user by opening an ERC20 exchange escrow. The escrow is currently in the OPEN state.
  2. The exchange calls the confirmDeposit() method to change the state of the escrow to PUZZLE state to indicate it is ready for trade.
  3. Similarly, the user opens a user ETH escrow. The escrow is currently in the OPEN state.
  4. The user calls the confirmDeposit() method to change the state of the escrow to PUZZLE to indicate it is ready for trade.
  5. The exchange calls the withdraw() function on the user escrow and discloses its key. This moves the user escrow to the CLOSED state.
  6. The user calls the withdraw() function on the exchange escrow using the disclosed key. This moves the exchange escrow to the CLOSED state.
  7. The exchange can choose to fund the exchange escrow again to move the exchange escrow back into the PUZZLE state to trade again.
  8. The user can choose to fund the user escrow again to move the user escrow back into the PUZZLE state to trade again.

Here is my implementation of a simplified Arwen escrow to store and trade ETH:

contract ETHEscrowInterface {

enum State {OPEN, PUZZLE, CLOSED}
State public currentState;

address payable public seller;

uint256 public timeLimit;

bytes32 public puzzle = 0xc505fe01b44140477f4a6ccad01f4b21f34243e809c0717e6cef4283b04359fa;

modifier onlySeller { require(msg.sender == seller); _; }
modifier onlyTimeout { require(now >= timeLimit); _; }

The escrow maintains a currentState to indicate which events it has already processed and which events it can expect to occur. An explanation of the listed states is provided below:

  • OPEN indicates that the contract constructor has run successfully and the escrow is ready to receive funds.
  • PUZZLE indicates that the contract has received funds and is ready to trade.
  • CLOSED indicates that the contract has relieved all held funds and is no longer available for making trades until further funds are deposited.

It also contains the address of the seller, which is the person who opened this escrow. This information will be crucial in the refund() method described later. Other attributes are timeLimit and puzzle which are used in the refund() and withdraw() methods later. This puzzle is different for each user of Aurum as each user has a different key.

Modifiers enforce the conditions listed inside their require() statements for functions that use them.

constructor(address payable _seller, uint256 _timeLimit) public {
seller = _seller;
timeLimit = _timeLimit;
currentState = State.OPEN;
}

The constructor is only called when the smart contract is deployed. It also sets the required parameters for deploying the contract. As shown in the code, it sets the seller, timeLimit to the passed parameters and updates the currentState of the escrow to OPEN. This function is called in steps 1 and 3 in Figure 2.

function () external payable {}

This fallback function allows payments to be made to the escrow by knowing the address of the deployed escrow contract on the blockchain. This function is called in steps 1 and 3 of Figure 2.

function confirmDeposit() public returns(bool){
currentState = State.PUZZLE;
return true;
}

This function can be used to change the currentState of the escrow to PUZZLE indicating that the escrow has funds locked in and is ready for trading. This function is called in steps 2 and 4 of Figure 2.

function withdraw(address payable _to, string memory _key) public {
require(puzzle == sha256(abi.encodePacked(_key)));
_to.transfer(address(this).balance);
currentState = State.CLOSED;
}

withdraw() allows the buyer to withdraw the funds locked inside the escrow by providing the address where they want the funds to arrive and the key to the puzzle with which the funds are locked. It sets the currentState to CLOSED indicating that the escrow is not available for trades. This function is called in steps 5 and 6 of Figure 2.

function refund() onlySeller onlyTimeout public {
seller.transfer(address(this).balance);
currentState = State.CLOSED;
}

refund() allows the seller to back out of the trade if the exchange does not occur or if the buyer becomes uncooperative. This function can only be called by the seller and only after the previously agreed duration of timeLimit has passed. It is a fail-safe that protects the buyer even if the exchange refuses to trade. Since these escrows are available both to the seller and the exchange, it protects both parties in uncooperative scenarios. This function is not called in a cooperative exchange so is not seen in Figure 2.

function setLargerTimeLimit(uint256 _newTimeLimit) onlySeller public {
require(_newTimeLimit > timeLimit);
timeLimit = _newTimeLimit;
}

This function increases the previously agreed timeLimit in the event that the seller wants to keep the escrow available for trades longer. This function is optional and may never be called.

So here is the complete smart contract for accepting Ethereum payments that captures the idea in less than 50 lines of code. Neat!

pragma solidity ^0.5.0;contract ETHEscrowInterface {

enum State {OPEN, PUZZLE, CLOSED}

State public currentState;

address payable public seller;

uint256 public timeLimit;

bytes32 public puzzle = 0xc505fe01b44140477f4a6ccad01f4b21f34243e809c0717e6cef4283b04359fa;

modifier onlySeller { require(msg.sender == seller); _; }
modifier onlyTimeout { require(now >= timeLimit); _; }
constructor(address payable _seller, uint256 _timeLimit) public {
seller = _seller;
timeLimit = _timeLimit;
currentState = State.OPEN;
}

function () external payable {}

function confirmDeposit() public returns(bool){
currentState = State.PUZZLE;
return true;
}
function withdraw(address payable _to, string memory _key) public {
require(puzzle == sha256(abi.encodePacked(_key)));
_to.transfer(address(this).balance);
currentState = State.CLOSED;
}

function refund() onlySeller onlyTimeout public {
seller.transfer(address(this).balance);
currentState = State.CLOSED;
}

function setLargerTimeLimit(uint256 _newTimeLimit) onlySeller public {
require(_newTimeLimit > timeLimit);
timeLimit = _newTimeLimit;
}
}

The above contract acts as an Ether escrow and provides an interface to store and withdraw Ether. A similar interface is used for serving as an ERC20 escrow for storing and withdrawing ERC20 tokens. The only key difference it has is the transfer function to comply with the ERC20 interface.

To conduct cross-chain atomic swaps, the official version of Arwen would contain different escrows compatible with different blockchains. Our ERC20 contract is meant to simulate such an interface on a different blockchain, but implemented on the Ethereum blockchain for simplicity.


A simplified summary of Kyber Network

Overview Of Actors In The Kyber Protocol Implementation
Overview Of Actors In The Kyber Protocol Implementation
Figure 3. Overview Of Actors In Protocol Implementation [Source: https://files.kyber.network/Kyber_Protocol_22_April_v0.1.pdf]

Motivation

The rise in popularity of blockchain technologies has given birth to many decentralized applications or DApps. Decentralized applications differ from centralized applications that run on centralized servers by running on a decentralized peer-to-peer network on computers. There is a subset of DApps called Decentralized Financial applications or DeFi applications that are attempting to better some aspect of the traditional financial system by using this property of running on a decentralized network. Two popular examples of DeFi applications are Dharma Protocol, that is decentralizing lending, and dYdX which facilitates exchange of derivatives based on cryptocurrencies. As you can imagine, DeFi applications like these require a decentralized liquidity pool to conduct their operations since a centralized source of liquidity would be incompatible with their basic manner of operation. This is the problem that Kyber Network is attempting to address by serving as a single interface for access to large decentralized pools of liquidity.

The participants of Kyber Network are termed as ‘Takers’ and ‘Reserves’. Takers are regular users who have a certain currency and would like to swap it for another one by requesting a swap from Kyber. The taker’s order is then matched to a single ‘Registered Reserve’ which is the smart contract of a liquidity provider and its process will be illustrated below.

In Figure 3, you can notice ‘Reserves’ are listed as ‘Registered Reserves’ and there is a group of ‘Maintainers’ who have the power to register or remove reserves. Hmmm this seems like a bit of centralization. 🤔 Kyber’s whitepaper states: “We can expect implementations to be permissioned initially until the maintainers are confident about these considerations.” There are extra security considerations to be made when making a completely permission-less model so this is understandable. We can hopefully see a permission-less version of Kyber soon.

A illustration of taker-reserve matching mechanism in Kyber
A illustration of taker-reserve matching mechanism in Kyber
Figure 4. Basic Token Trade Execution Flow [Source: https://files.kyber.network/Kyber_Protocol_22_April_v0.1.pdf]

Here is a figure to illustrate the process of matching a taker to a reserve. Numbers in circles correspond the step numbers below. Arrows point from sender of request to sendee. Text above and below arrows represent details of the request.

  1. The taker makes a request to the protocol contract of converting ETH to BAT and sends 1 ETH to the contract.
  2. The contract queries Reserve 1 for the ETH/BAT rate it can offer.
  3. Reserve 1 responds with a rate of 1 ETH = 800 BAT.
  4. The contract queries Reserve 2 for the ETH/BAT rate it can offer.
  5. Reserve 2 responds with a rate of 1 ETH = 800 BAT.
  6. The contract queries all other reserves for the ETH/BAT rates they can offer and determines that Reserve 2 provided the best rate.
  7. The contract sends 1 ETH to Reserve 2.
  8. Reserve 2 sends 820 BAT to the taker.

In summary, the taker’s request is sent to a Kyber smart contract. The contract in turn asks each valid reserve the best price they can offer for the taker’s tokens. On determining the reserve with the best price, the taker has a choice to call the trade() function in the Kyber smart contract to execute the trade. This will send taker’s tokens to the matched reserve and the reserve will send the requested token amount to the taker directly.

The main limitation of both the takers and liquidity pools acting as reserves is that Kyber Network is limited to Ethereum-based tokens only. This means that even if a reserve wishes to trade tokens not based on Ethereum, they will not be able to do so in using Kyber.


Recap

So far we have come across two cryptocurrency trading protocols:

  1. Arwen which is attempting to facilitate safe and secure atomic swaps between a couple of users or a user and a centralized exchange without having to trust the exchange.
  2. Kyber Network which can match users for making trades at the best prices available and is attempting to create a pool of decentralized liquidity but is currently only compatible with Ethereum-based coins.

What happens when we use the effective price-matching aspect of Kyber and combine it with the secure cross-chain swaps of Arwen providing access to more than just the Ethereum blockchain?


A hybrid model of Arwen and Kyber: Aurum

Aurum is meant to be a simple demonstration of combining the Arwen Trading Protocols with the taker-reserve matching functionality of Kyber Network. The value proposition of such a combination of the two protocols removes Kyber’s limitation of being restricted to Ethereum-based tokens. This hybrid would allow takers and reserves matched through the Kyber Network to participate in cross-chain atomic swaps for eventually all blockchains for which Arwen has been implemented. Cross-chain atomic swaps are currently not possible for Kyber Network to perform so this would expand it’s decentralized liquidity pool to more cryptocurrencies than just Ethereum.

Ability to make partial fills

If a user needs to make the best possible trade with other users that have signed on as Kyber reserves for any transaction, we need a mechanism to match the user and the reserve. Aurum uses a similar strategy to Kyber in this regard but also allows for partial fills which Kyber does not do.

A situation where the importance of allowing better priced partial trades to occur is described as such: Alice needs to trade 500 ETH for BTC, and expects around 10 BTC for 500 ETH based on general market rates. Charlie has 30 BTC but is selling them for a higher rate than Bob, who only has 1 BTC and cannot completely fulfil Alice’s order alone.

In this scenario, the Kyber Protocol would choose Charlie as the reserve and have him trade with Alice since Bob cannot complete Alice’s order. But this isn’t beneficial for Alice or Bob. Alice would be better off making a trade with Bob for all the BTC he has for the better price, and then completing the rest of the trade with Charlie. Aurum allows this to occur and does it automatically. This also allows users with small amounts of currency to participate as reserves for even big orders. This mechanism promotes greater market participation.

To summarize, if there are multiple small reserves each offering a less than whole portion of the trade, but for better prices, Aurum would prioritize exhausting smaller but better priced reserves. This may lead to more transactions but can be more profitable for both parties despite higher gas costs. Kyber Network does not provide such an option to the taker.

Goal of the implementation

We can create a simple simulation of this hybrid exchange protocol using two smart contract escrows, just like we did in the section on Arwen (refer to Figure 2), but each one running on different cryptocurrency blockchains. We will be using Ethereum to simulate trades on one blockchain, and an ERC20 token to simulate trade on the blockchain of a different non-Ethereum based cryptocurrency to keep the implementation simple. The following implementation is only meant to represent this hybrid concept. Please note that these are simplified versions of the Arwen and Kyber protocols and don’t implement every feature, as that would take a large team and thousands of lines of code. The following figure demonstrates this concept by matching a taker and a reserve using the Kyber Protocol and performing a cross-chain swap between the two using Arwen.

Figure 5. High-level model of Aurum as a hybrid of Arwen and Kyber Protocols represented as a set of two escrows, each operating on different blockchains. Singly pointed arrows between Taker/Reserve and escrows and vice-versa represent the flow of currency. Text around singly pointed arrows identifies a method in such a system to execute the represented trade. The doubly pointed arrow and text in red represents a match between a taker and a reserve using the Kyber Protocol. Please note that this a cross-chain atomic swap happening on blockchains of different cryptocurrencies, α and β in this illustration. In the current implementation, the ERC20 token “ABC” can be thought of as a token on blockchain α and ETH can be thought of a blockchain β.

Aurum is also meant to be a simple demonstration of providing the end user with the choice of using either centralized exchanges without their downsides that are mitigated by an Arwen-esque protocol and letting the decentralized market compete by having them on the same platform. Opening up both avenues of trading from one platform could also spur user adoption of said platform and help fix the problem of DEXes having a small number of participants and hence low volume by placing them within arm’s reach of users who are comfortable trading with the consistency and user support of centralized exchanges. It also adds the capability for users with small amounts of currency to participate as reserves and other users to benefit from their rates. The goal of this project is to demonstrate a simplified implementation of these differing worldviews about the future of cryptocurrency exchange in code.

A condensed summary of the goals of Aurum and it’s desired capabilities are listed below:

  1. Make Arwen-inspired cross-chain atomic swap between two identified parties.
  2. Match takers with users based on Kyber Network but prioritizing cheaper trades over filling the taker order in a single transaction.
  3. Bringing users together on a single platform that allows them to make both centralized and decentralized trades from one spot.

Use cases / Benefits to different parties

Suppose you are a liquidity provider currently on the Arwen network with cryptocurrency other that Ethereum and Ethereum-based chains. Even if a potential Taker has non-Ethereum based tokens you would like to trade with and could potentially profit from, those trades would never become available or visible to you through the current Kyber Network since it only supports Ethereum-based tokens. This could translate into a large amount of real profit you could be losing out on by not being able to make trades outside of the Ethereum ecosystem. The same is true for takers. Even if there is a party with liquidity outside of Ethereum-based tokens ready to trade and present on the Kyber Network, all those potential trades would stay hidden, which is not ideal. In such a scenario, the user would need to resort to centralized exchanges or deal with low liquidity provided by DEXes. These situations are not ideal that Aurum could help mitigate.

Implementation

We can lay down the steps of implementing Aurum as follows:

  1. Creating a test ERC20 token to serve as a simulation of a token running on a blockchain different from Ethereum.
  2. Deploying the token on a test blockchain for processing on-chain transactions.
  3. Adding the token to a wallet to have a visual way of easily verifying transactions.
  4. Deploying the previously explained Arwen escrow smart contracts to make trades with.
  5. Implement Arwen atomic swap trades between two parties.
  6. Implement Kyber’s protocol matching users and reserves based on best prices and ability to make partial fill trades.

Since Aurum is built from components resembling some parts of Arwen and some parts of Kyber, we name the functions and variables in Aurum to reflect this, i.e. the trade function in Aurum is called arwenTradeETHtoERC.

The tools we will be using to develop this exchange are:

1) Ganache: To run a test Ethereum blockchain for processing on-chain transactions

2) Remix: To compile and deploy smart contracts

3) Web3.py: To interact with the test blockchain and deployed smart contracts

4) Metamask: To act as a cryptocurrency wallet

Step 1: First we need to create an ERC20 token that we can use to trade for Ether. Even though ERC20 tokens operate on the same blockchain as Ethereum, the purpose of this token is to simulate a non-Ethereum based token. Creating this ERC20 token helps model the cross-atomic swaps of Aurum in a relatively simple and easy way. I have labeled my token with the symbol as “ABC” in the code. If you choose a different one, make sure to replace “ABC” with your token symbol. OpenZepplin has a set of contracts that are freely available to use to deploy an ERC20 token. Importing the necessary contracts, our solidity code is very short and creates a deployable working ERC20 contract running on the Ethereum blockchain.

pragma solidity ^0.5.0;import “./ERC20.sol”;
import “./SafeMath.sol”;
import “./ERC20Mintable.sol”;
contract MyToken is ERC20, ERC20Mintable {}

Step 2: Deploy this token on the Ganache blockchain for us to use. The next task would be to mint a certain amount of this coin into existence and have it sent to a user address on Ganache for us to use. You can do so using Remix’s interface after contract deployment.

Figure 6. Remix’s interface for interacting with the token smart contract. “Mint” has been underlined to indicate the action that needs to be taken.

Step 3: Once the tokens are minted and sent, they won’t be visible on the Ganache GUI since it only displays Ether. Open up Metamask and add a custom token by entering the address of the deployed token contract provided by Remix. You can now view the balance of the new ERC20 token in Metamask! Just make sure the token contract address is correct and the address provided to the mint function matches the address shown by Metamask.

Steps to view the new ERC20 token balance in Metamask
Steps to view the new ERC20 token balance in Metamask
Figure 7. Steps to view the new ERC20 token balance in Metamask. It’s yours, give it any symbol you want!

Step 4: Now we need to deploy escrow contracts for our accounts on Ganache so they can transact with one another. For our purposes, we can name one of the accounts as the exchange which will always contain a large value of both cryptocurrencies. Any of the users created using the User class can be named the exchange.

Step 5: Now we can move on to important functions in the Web3.py and Python3 code that make all the interactions and trades happen. Let’s start with the code that runs Arwen trades.

def arwenTradeETHtoERC(sender, receiver):
tradeInstance = ArwenAtomicSwap(sender, receiver)
tradeInstance.lockETHInEscrow(sender)
tradeInstance.lockERC20InEscrow(receiver)
tradeInstance.withdrawERC20InEscrow(sender, receiver)
tradeInstance.withdrawETHInEscrow(receiver, sender)

This function creates an instance of the ArwenAtomicSwap class and simulates a simplified Arwen swap between Ethereum and an ERC20 token. A trade instance is created to simulate the opening of smart contract escrows from both parties. They then proceed to lock their respective funds and subsequently withdraw each other’s tokens. Note that the sender is one who chooses the puzzle, so the sender is the first one to withdraw funds revealing the puzzle key on the blockchain allowing for fund retrieval by the second party, the receiver. This is the main mechanism used to trade between parties. A similar function exists to trade from ERC to ETH for simplicity, but in reality performs the same task the one above with the parties switched. All details about these simplified Arwen trade functions can be found in the ArwenAtomicSwap class code.

Step 6: Here are the key helper functions that allow Aurum to find the best possible rates from reserves in a Kyber-inspired fashion but allowing for partial fills.

def getConversionRates(self, baseCurrency, amountInBaseCurrency):
lowestPrice = -1;
desiredCurrency = “ETH” if baseCurrency is “ABC” else “ABC”
reserves = self.ETHReserves if desiredCurrency is “ETH” else self.ERCReserves
proposed_deals = [] for reserve in reserves:
conversionRate = reserve.getERCtoETHrate() if desiredCurrency is “ETH” else reserve.getETHtoERCrate()
reserveAvailableBalance = min(conversionRate*amountInBaseCurrency, ABC.getTokenContract().caller.balanceOf(reserve.getAddress()))
if ABC.getTokenContract().caller.balanceOf(reserve.getAddress()) < conversionRate*amountInBaseCurrency:
reserve.setDepositAmount(ABC.getTokenContract().caller.balanceOf(reserve.getAddress()))
proposed_txn_details = {‘rate’: conversionRate, ‘amountInDesiredCurrency’: reserveAvailableBalance, ‘reserve’: reserve}
proposed_deals.append(proposed_txn_details)
return proposed_deals

This function iterates through all listed reserves and returns a list of all available exchange trades. In the event that the reserve has a lesser balance than what it is promising for the trade, Aurum lowers the promised amount from the reserve to match it’s balance. This ensures that approved transactions don’t end up getting rejected if the reserves don’t have the balance promised.

def kyberBestAvailableTrade(self, baseCurrency, amountInBaseCurrency):
desiredCurrency = “ETH” if baseCurrency is “ABC” else “ABC”
available_deals = self.getConversionRates(baseCurrency, amountInBaseCurrency)
best_deal = {}
best_rate = sys.maxsize
for deal in available_deals:
if deal[‘rate’] < best_rate:
best_rate = deal[‘rate’]
best_deal = deal

return best_deal

This function calls getConversionRates() and selects the best deal available for trading. It iterates through all available rates as returned from getConversionRates() and subsequently chooses the best available deal for the taker.

def executeSingleKyberTrade(self, sender, baseCurrency, amountInBaseCurrency):
sender.setDepositCurrency(baseCurrency)
sender.setDepositAmount(amountInBaseCurrency)

reserve = self.kyberBestAvailableTrade(baseCurrency, amountInBaseCurrency)[‘reserve’]
user_token_destination = kyberERC if baseCurrency is “ABC” else kyberETH
reserve_token_destination = kyberETH if baseCurrency is “ABC” else kyberERC
userFundsLockup = ArwenAtomicSwap(sender, kyberERC) if baseCurrency is “ABC” else ArwenAtomicSwap(sender, kyberETH)
userFundsLockup.lockERC20InEscrow(sender, kyberERC) if baseCurrency is “ABC” else userFundsLockup.lockETHInEscrow(sender, kyberETH)
reserveFundsLockup = ArwenAtomicSwap(reserve, kyberETH) if baseCurrency is “ABC” else ArwenAtomicSwap(reserve, kyberERC)
reserveFundsLockup.lockETHInEscrow(reserve, kyberETH) if baseCurrency is “ABC” else reserveFundsLockup.lockERC20InEscrow(reserve)
userFundsWithdrawal = ArwenAtomicSwap(sender, kyberETH) if baseCurrency is “ABC” else ArwenAtomicSwap(sender, kyberERC)
userFundsWithdrawal.withdrawETHInEscrow(sender, kyberETH) if baseCurrency is “ABC” else userFundsLockup.withdrawERC20InEscrow(sender, kyberERC)
reserveFundsWithdrawal = ArwenAtomicSwap(reserve, kyberERC) if baseCurrency is “ABC” else ArwenAtomicSwap(reserve, kyberETH)
reserveFundsWithdrawal.withdrawERC20InEscrow(reserve, kyberERC) if baseCurrency is “ABC” else reserveFundsWithdrawal.withdrawETHInEscrow(reserve, kyberETH)
return True

This function calls kyberBestAvailableTrade() to get the best possible single trade for the taker and executes a single ArwenAtomicSwap() between the taker and the chosen reserve for the amount corresponding to the partial amount of the trade in the event the reserve doesn’t have enough liquidity for completing the trade fully.

def executeFullKyberTrade(self, sender, baseCurrency, amountInBaseCurrency):
amountLeftToTransact = amountInBaseCurrency
while (amountLeftToTransact > 0):
senderBalanceBefore = ABC.getTokenContract().caller.balanceOf(sender.getAddress()) if baseCurrency is “ABC” else web3.eth.getBalance(sender.getAddress())
self.executeSingleKyberTrade(sender, baseCurrency, amountInBaseCurrency)
senderBalanceAfter = ABC.getTokenContract().caller.balanceOf(sender.getAddress()) if baseCurrency is “ABC” else web3.eth.getBalance(sender.getAddress())
amountLeftToTransact = amountLeftToTransact — (senderBalanceBefore — senderBalanceAfter)

return (amountLeftToTransact == 0)

executeFullKyberTrade() is the only function that needs to be called to perform all the activities listed in the previous functions descriptions. This function will repeatedly execute best priced single trades on behalf of the taker using the executeSingleKyberTrade() method until all the currency put up by the taker for exchange has been transacted ensuring the taker gets the best prices at all times. This function makes an assumption that all decentralized reserves together have enough liquidity to fulfill the order. This might not always be the case, and the centralized exchange can potentially be set up to be a fallback to mitigate this situation.

Here is an illustration laying out the flow of the program logic:

Figure 8. Illustrating flow of program logic in making an Aurum (Kyber/Arwen hybrid) trade. Singly pointed arrows point from caller to callee. Doubly pointed arrows represent function calls and receiving return values. Blocks represent functions and are labeled in the diagram. The loop over executeFullKyberTrade() represents a while loop which keeps the function from exiting until all of the taker’s trade amount has been traded.

The steps taken in the trade in Figure 8 are listed as follows:

  1. The Taker makes a request an Aurum to trade their 1 BTC to ETH. Aurum manages this request by calling executeFullKyberTrade() and passing the necessary arguments.
  2. executeFullKyberTrade() checks if the amount requested to trade is larger than 0 and calls executeSingleKyberTrade() to perform the first transaction with the reserve offering the best price.
  3. executeSingleKyberTrade() calls kyberBestAvailableTrade() to find which reserve is offering the best prices and waits for its return value.
  4. kyberBestAvailable() calls getConversionRates() to find out the prices being offered by every available reserve and waits for its return value.
  5. getConversionRates() asks Reserve 1 for it’s rate, the total amount offered and stores it with a reference to the reserve.
  6. Reserve 1 responds with it’s rate and the total amount offered.
  7. getConversionRates() asks Reserve 2 for it’s rate, the total amount offered and stores it with a reference to the reserve.
  8. Reserve 2 responds with it’s rate and the total amount offered.
  9. getConversionRates() asks Reserve 3 for it’s rate, the total amount offered and stores it with a reference to the reserve.
  10. Reserve 3 responds with it’s rate and the total amount offered.
  11. getConversionRates() combines all the information received from the reserves and returns it to kyberBestAvailable().
  12. kyberBestAvailable() takes this return value and figures out which reserve is offering the lowest rate. It then selects that reserve’s information and returns it to executeSingleKyberTrade().
  13. executeSingleKyberTrade() receives this reserve’s information that has been deemed to offer the best possible rate for this particular trade and executes an ArwenAtomicSwap() between the reserve and the taker.
  14. executeFullKyberTrade() checks the taker’s balance in the currency offered for trade. If the balance is greater than 0, this indicates that the trade just executed did not partial fill and did not complete the order placed by the taker, and a while loop repeats this whole process again, with the next best priced reserve. This process continues until the taker’s trade is complete.

Conclusion

The main value proposition of Aurum is the ability to make cross-chain atomic swaps that are currently not possible through Kyber. Kyber in turn provides us with an effective way to find other participants to open up Arwen escrows and trade with.

Another thing to note is that the Arwen way of transacting cryptocurrency and DEXes have complementary strengths and weaknesses. DEXes are have low liquidity and high prices while Arwen provides a better mechanism to trade with centralized exchanges which have all the liquidity. Merging them onto one platform gives users a handy choice of being able to choose either one on one platform.

This concludes my discussion of a simple concept decentralized exchange that I have nicknamed Aurum. I hope you learned something new from my post and thanks for reading! All the code can be found here.

A giant thanks to Andrew Miller, Assistant Professor at the University of Illinois at Urbana-Champaign for providing vital support, feedback and encouragement throughout the duration of this project. 😀

Mihir Kumar

Written by

Student. Developer. Engineer. Not at all, funny.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade