Tutorial: Executing swap with Shell Protocol
This tutorial will walk you through the example of executing a swap (DAI <> USDC) on Arbitrum Goerli testnet using the two most important Shell Protocol components: the Ocean, a unified accounting system, and Proteus, Shell's novel AMM primitive.
You don't need to worry about the inner workings of the Ocean and Proteus for now.
Getting started
If you have already completed this steps by following Quickstart guide you can skip this section. If not, follow along.
Clone the Shell Protocol public repo, cd
into it and run npm install
In the root folder, go to hardhat.config.js
and add your private key on line 27.
WARNING: Do not just simply paste the private key. Use something like dot-env
and environment variables instead. Be sure that you ad the file containing secrets to the .gitignore
Be sure that your account has enough test tokens on Arbitrum Goerli network. You can use this faucet to fund your account https://faucet.quicknode.com/arbitrum/goerli. You can also obtain up to 1000 testnet ERC-20 tokens by calling the claimTokens
function on the following contract: https://testnet.arbiscan.io/address/0xEaE5B59499a461887fBf2BF47887e4e4cB91D703#writeContract
Now go to scripts
folder and create new file swap.js
. We will write the whole swap logic inside this file and let the hardhat execute the script.
Writing the swap script
The first thing we will do is to import hardhat and create an empty main()
function which will be called upon running swap.js
by the Hardhat. It's a standard way of writing Hardhat scripts.
First thing we will need is an instance of the Ocean smart contract. As Shell Protocol's accounting system, the Ocean is responsible for updating balances and "moving" tokens between the user and the AMM. It's not necessary that you understand how Ocean works for the sake of this tutorial, but if you want to learn more you can start read the The Ocean page in the deep dive section. Note that reading Important concepts section before that is highly recommended. So let's create and instance of The Ocean in line 4:
You should pass the correct Ocean contract address that can be found here.
Congratulations! We just created and instance of the Ocean smart contract. Now we have to pass to that instance a set of instructions (as Interactions) we want it to execute. In order to swap 100 DAI for USDC we will need to do the following:
Wrap 100 DAI to the Ocean
Ask Proteus AMM Engine to calculate how many USDC should we get for 100 DAIs
Ask The Ocean to transfer computed amount of USDC to our wallet
Now that we know what to do, we need to determine how to do it. As we already mentioned, The Ocean executes instructions by passing Ocean Interactions to it in order we want them to be executed. So, in this case we need to pass the following interactions:
WrapErc20
with the DAI token address as an input token and amount set to the 100ComputeOutputAmount
passing DAI as an input token, 100 as amount and USDC as an output tokenUnwrapErc20
to transfer USDC from the Ocean to the specified address
So, the next step is to encode this three interactions and prepare them for execution.
Interactions within the Ocean are uniformly encoded via the Interaction
struct.
That means that we have to create and array of three interaction objects and we will use helper methods for that. Helper methods for creating correct format interaction objects can be found inside interactions.js
file in the utils-js
folder. We will need wrapERC20
, computeInputAmount
and unwrapERC20
helper method specifically so let's import them and see how to use them.
Now we just have to pass the right values instead of the template strings. DAI contract address on Arbitrum Goerli is 0xEaE5B59499a461887fBf2BF47887e4e4cB91D703
while USDC is at the following address 0x1f84761D120F2b47E74d201aa7b90B73cCC3312c
.
Finally, DAI USDC Pool address we need is 0x785402A418B56fd9a05F60B194B3CeecaB42E78f
. All Proteus pool address can be found in Contract addresses page.
Ok, but what are these "WRAPPED_DAI_OCEAN_ID"
and "WRAPPED_USDC_OCEAN_ID"
that we are passing as input and output tokens to the computeInputAmount
helper function?
Well, as an accounting system Ocean is responsible for handling both tokens from the external ledgers (ERC20, ERC721, ERC1155), i.e. wrapped tokens, and Ocean native tokens. In order to keep the track of everything, every token inside the Ocean has its own ID which we call Ocean ID.
We won't be going into explaining this concept here as it's not necessary to understand how it works to complete this tutorial, it's throughly explained in the Ocean page under the Deriving token's Ocean ID section.
All we need to know right now is that we have another helper function which we can use to get the correct token id. The helper method name is calculateWrappedTokenId and it's located inside utils.js
file in the utils-js
folder.
Let's swap the template strings with the real values:
Let's see what we have so far. We have created an instance of the Ocean contract and we have encrypted three interactions we want to execute. All we have to do now is to tell the Ocean to execute them.
We will use another helper function executeInteractions
defined in index.js
under the utils-js
folder. This helper method receives three params: an instance of the Ocean contract, signer and an array of interactons.
All this helper method does is it calculates interaction IDs and calls an Ocean contract method doMultipleInteractions
on the behalf of signer.
We already have an instance of the Ocean created at line 7 and an array of interactions prepared. We just need a signer. Let's fetch it with the etherjs
library and place it above the Ocean instance creation on line 7.
Only thing we have to do now in order to complete the swap and this tutorial is to call the executeInteractions
Since you have successfully completed this tutorial you deserve appreciation.Don't forget to tweet about your accomplishment!
Last updated