Swap one token for another in Delphi
One of the most fundamental building blocks in DeFi are token swaps.
You can swap tokens in a decentralized manner on an automated market maker protocol or “AMM”.
An AMM is a decentralized platform for assets to be exchanged between individuals without regulation. The price of an asset is set by the holders and the traders strictly through supply and demand without any boundaries of entry for users.
In other words: AMMs are decentralized and censorship-resistant and permissionless. Nobody can stop you from trading on an AMM and as soon as your trades got confirmed by the network, nobody can revert them. Last but not least, there is nobody you would need to ask for permission for any of this.
In a nutshell, here are the inner workings of an AMM. Traders pay swap fees when they trade with a pool. The fees ultimately go to liquidity providers in exchange for them putting their tokens in the pool to facilitate trades. Fees are added to the pool, accrue in real time and can be claimed when you as a liquidity provider withdraw your liquidity. These rules are coded in smart contracts and because smart contracts are immutable, nobody can change the rules of this game.
Balancer is a $3 billion AMM with unique options. Investors can provide more than 2 tokens per pool if they want, where each token in the pool can have customizable weight. Traders can enjoy gasless trading with MEV protection.
This article describes how to use Delphi and swap one token for another, using Balancer as the underlying protocol.
The beauty of the Balancer smart contracts is that the protocol comes with a single vault architecture. In theory, you as a developer only ever need to interact with
TVault. But Delphereum makes things even simpler with a few easy access functions.
Let’s get started with a simple token swap.
swap is an easy access function that will make a trade between two tokens. It expects the following parameters:
owneris the owner of the tokens we are sending to the pool.
kindis the type of swap we want to perform — either (a)
assetInis the address of the token which we are sending to the pool.
assetOutis the address of the token which we will receive in return.
amountis the amount of tokens we (a) are sending to the pool, or (b) want to receive from the pool.
limitis the “other amount” aka (a) minimum amount of tokens to receive, or (b) maximum amount of tokens to send.
deadlineis a Unix timestamp. Your transaction will revert if it is still pending after this epoch.
Putting everything together, this is what a call to
swap could look like:
Let’s break it down. We are connecting to Ethereum via Infura.
owner is the owner of the tokens we are sending to the pool (probably your private key).
We are sending USDC to the pool and will want to receive WETH (aka wrapped Ether) from the pool in return.
GivenIn tells the function we are giving the pool 100 USDC for an estimated output. Because USDC has 6 digits, we must scale the amount as such. It is important to get your decimals set correctly, otherwise you may send far more or far fewer tokens than you intended.
limit is set to zero, indicating we want to receive at least zero WETH from the pool. Please note setting your receive limit to 0 is generally a very bad idea, but for the purpose of this example we are willing to accept 100% slippage on our trade.
deadline is set to infinite, indicating we are willing to wait forever for this transaction to complete. Should you want the trade to expire (for example, after 20 or 30 minutes), then the network will still take some gas to process the failed transaction, but it will be cheaper than a transaction failing for a different reason.
swap will sign the transaction with your private key, and broadcast the transaction to the network. If everything goes according to plan, then the function will call back into your closure, passing a transaction receipt to you. You might want to open the transaction in a tab in your web browser. Please see the accompanied demo app for an example on how to do that.
What is great about
swap is that you don’t need to worry about pool identifiers. The function includes an order router that will figure out what pool(s) to trade. If the function can execute a single swap, then it will save ~6,000 gas. If the trade will need to hop through multiple pools, then the function will execute a batch swap.
The other thing you don’t need to worry about are token approvals. If token spend allowance needs to be granted to the Balancer vault, then the function will take care of that.
Congratulations! You have swapped one token for another. But what if you would want to watch live trades as they happen? Does the Balancer vault emit any events, and if yes, how do we respond to that in Delphi?
Watching trade history
Delphereum includes an easy access function for Balancer events named
listen. You will need to pass a web3 client (indicating what chain you want to connect to) and a closure that Delphereum will call back into every time a trade happened on the Balancer protocol. Here is an example:
The arguments that get passed to your closure are pretty much self-explanatory but computing the size and the price of the trade might be challenging.
Let’s begin with the size of the trade. If you log the trade as a buy then you should unscale the
amountOut value by the
assetIn decimals, whereas if you want to log the trade as a sell then you should unscale the
amountIn value by the
Now that we have the size of the trade, it is time to compute the price where the trade got filled at. If you want to log the trade as a buy then you should unscale the
AmountIn value by the
AssetOut decimals, whereas if you want to log the trade as a sell then you should unscale the
AmountOut value by the
assetOut decimals. Last but not least, you should divide the unscaled amount by the size of the trade you computed earlier.
If any of this is confusing, then please take a look at the accompanied demo app.
Should you be interested in a complete demo project, then here is a repo for you, including binaries for Windows and macOS. Everyone is encouraged to read the code and fork the demo project if you want.
The Balancer implementation for the Delphi programming language would not have been made possible without an awesome grant from Balancer DAO.
Delphereum and the Balancer demo app are provided free of charge. There is no warranty and no independent audit has been or will be commissioned. You are encouraged to read the code and decide for yourself whether it is secure. The authors do not assume any responsibility for bugs, vulnerabilities, or any other technical defects. Use at your own risk.