To use the rubi sdk, first install it using pip
or poetry
:
(.venv) $ pip install rubi
(.venv) $ poetry add rubi
This quickstart guide aims highlight the most common use case of rubi, connecting to and trading on the Rubicon Protocol.
This sdk depends on a connection to an Ethereum node by relying on the web3.py library. Please refer to their documentation if you want to understand what is going on behind the scenes.
The main entrypoint of this library is the rubi client <client>
. In order to instantiate a client we will need to provide it with some environment variables, namely a HTTP_NODE_URL
, WALLET
and KEY
. While not strictly necessary it is best practice to provide this from a .env
file.
Note
If you are working in a git repository make sure you add a .gitignore
and ignore .env
files to ensure you never commit sensitive information.
Create a .env
file with the following format:
HTTP_NODE_URL = <an optimism http node url>
WALLET = <the wallet that is being used to sign transactions and pay gas for said transactions>
KEY = <the private key of the wallet being used to sign transactions>
Following this you will need to read in these environment variables. The easiest way to do this is using the python dotenv
library.
(.venv) $ pip install python-dotenv
(.venv) $ poetry add python-dotenv
This will allow you to read in your environment variables as follows:
import logging as log
import os
from dotenv import load_dotenv
# load from env file
load_dotenv(".env")
# set the env variables
http_node_url = os.getenv("HTTP_NODE_URL")
wallet = os.getenv("WALLET")
key = os.getenv("KEY")
Finally we are ready to instantiate a client
# rubi imports
from rubi import Client, NetworkName, Transaction, NewLimitOrder, OrderSide
# instantiate the client
client = Client.from_http_node_url(
http_node_url=http_node_url,
wallet=wallet,
key=key
)
Note
In the above example we are creating a client using the from_http_node_url
function. This fetches the chain id from the node and then maps this to default network config that is managed by the Rubicon team. This config can be seen here. If you prefer you can instantiate your own Network
instance and use that to instantiate the client.
Note
In the above example we are connecting to the optimism goerli testnet. Make sure the node you are using is an optimism goerli node.
Here it is worth noting that you can also instantiate an OrderTrackingClient
which will track your open limit orders automatically. However, in order to do this you must provide a list of pair_names
so the client knows which pairs to track.
# rubi imports
import logging
from rubi import OrderTrackingClient, NetworkName, Transaction, NewLimitOrder, OrderSide
# instantiate the order tracking client
client = OrderTrackingClient.from_http_node_url(
http_node_url=http_node_url,
pair_names=["WETH/USDC", "WBTC/USDC"]
wallet=wallet,
key=key
)
# log open limit orders
logging.info(client.open_limit_orders)
Having instantiated a client you are now ready to start interacting with the Rubicon protocol. In order to use the client to read or trade against a specific pair you will first need to approve the RubiconMarket
contract
# Approve WETH
weth_approval = RubiconMarketApproval(token="WETH", amount=Decimal("1"))
client.approve(approval=weth_approval)
# Approve USDC
usdc_approval = RubiconMarketApproval(token="USDC", amount=Decimal("2000"))
client.approve(approval=usdc_approval)
Note
The allowances in the code above approve the RubiconMarket
contract to transact up to that amount on your wallets behalf. This is necessary in order to create offers on the protocol.
Now having approved the RubiconMarket` you can place your first limit order on Rubicon the decentralized world orderbook!
limit_order = NewLimitOrder(
pair_name="WETH/USDC",
order_side=OrderSide.BUY,
size=Decimal("1"),
price=Decimal("1914.13")
)
client.place_limit_order(limit_order=limit_order)
To create a read only rubi client <client>
follow the steps above except when creating your .env
file DO NOT add a KEY
. Instead your .env
file should only contain the following
HTTP_NODE_URL = <an optimism http node url>
WALLET = <the wallet that is being used to sign transactions and pay gas for said transactions>
The rubi client <client>
will then be instantiated without signing rights. You will still have read access to all the Rubicon contracts. And additionally will be able to simulate transactions against the chain you have connected to.
By default when you instantiate a rubi client <client>
you will only be able to create pairs from the tokens found in the token_addresses
section of the network.yaml
config for the chain you are connected to. This set of token addresses is vetted by the Rubicon team and intended to ensure that in interacting with the protocol users do not fall victim to scam coins. However, when instantiating the client you can provide an additional custom_token_addresses_file
parameter
client = Client.from_http_node_url(
http_node_url=http_node_url,
custom_token_addresses_file="custom_token_addresses.yaml",
wallet=wallet,
key=key
)
This points to a yaml file (which is relative to the current working directory) containing token addresses in the following format
# Forrest coin and USDT on Optimism Goerli
F: 0x45fa7d7b6c954d17141586e1bd63d2e35d3e26de
USDT: 0xd70734ba8101ec28b38ab15e30dc9b60e3c6f433
These additional tokens will then be appended to the valid tokens found in the network.yaml
.
Additionally, it should be noted that you can override the addresses found in token_addresses
section of the network.yaml
by adding the same key to the custom_token_addresses_file
.
Warning
THIS IS SUPER RISKY. I HOPE YOU KNOW WHAT YOU'RE DOING IF YOU CHOOSE TO DO THIS.
USDC: 0xFAKEfakeFAKEfakeFAKEfakeFAKEfakeFAKEfake
This will result in the client being instantiated with the address of USDC
as 0xFAKEfakeFAKEfakeFAKEfakeFAKEfakeFAKEfake
.
In this section, we will go through some methods in the Client
and MarketData
classes of the Rubicon package, specifically the get_offers
and get_trades
methods. We'll also illustrate how to use these methods with a basic example at the end.
This method is used to retrieve offers placed on the market contract. Users can filter the offers based on various parameters including the maker's address, transaction origin address, tokens involved in the transaction, etc.
Here's the signature of the method:
def get_offers(
self,
maker: Optional[Union[ChecksumAddress, str]] = None,
from_address: Optional[Union[ChecksumAddress, str]] = None,
pair_names: Optional[List[str]] = None,
book_side: Optional[OrderSide] = None,
open: Optional[bool] = None,
start_time: Optional[int] = None,
end_time: Optional[int] = None,
first: int = 10000000,
order_by: str = "timestamp",
order_direction: str = "desc",
as_dataframe: bool = True,
) -> Optional[pd.DataFrame] | List[LimitOrder]:
The method accepts the following parameters:
Parameter | Description |
---|---|
maker | The address of the maker of the offer |
from_address | The address that originated the transaction that created the offer |
pair_names | List of token pair names in the format ["WETH/USDC"] following the pattern <ASSET/QUOTE> |
book_side | Specifies which side of the order book to consider |
open | Whether or not the offer is still active |
start_time | The unix timestamp of the earliest offer to return |
end_time | The unix timestamp of the latest offer to return |
first | Number of offers to return |
order_by | Field to order the offers by. Default is "timestamp" |
order_direction | Direction to order the offers by. Default is "desc" |
- as_dataframe
- If the response should be as a dataframe or as a list of LimitOrders
This method is used to retrieve trades that have occurred on the market contract. Similar to get_offers, users can filter the trades based on various parameters including the taker's address, transaction origin address, tokens involved in the transaction, etc.
Here's the signature of the method:
def get_trades(
self,
taker: Optional[Union[ChecksumAddress, str]] = None,
from_address: Optional[Union[ChecksumAddress, str]] = None,
pair_names: Optional[List[str]] = None,
book_side: Optional[OrderSide] = None,
start_time: Optional[int] = None,
end_time: Optional[int] = None,
first: int = 10000000,
order_by: str = "timestamp",
order_direction: str = "desc",
) -> pd.DataFrame:
The method accepts the following parameters:
Parameter | Description |
---|---|
taker | The address of the taker of the trade |
from_address | The address that originated the transaction that created the trade (helpful when transactions go through the router) |
pair_names | List of token pair names in the format ["WETH/USDC"] following the pattern <ASSET/QUOTE> |
book_side | Specifies which side of the order book to consider |
start_time | The unix timestamp of the earliest trade to return |
end_time | The unix timestamp of the latest trade to return |
first | Number of trades to return |
order_by | Field to order the trades by. Default is "timestamp" |
order_direction | Direction to order the trades by. Default is "desc" |
In the example below, we will retrieve WETH/USDC offer data for a given time range on the network of the node connection.
weth_usdc_offers = client.get_offers(
pair_name="WETH/USDC",
book_side=OrderSide.NEUTRAL, # options are NEUTRAL, BUY, SELL
start_time=1688187600,
end_time=1690606800,
)
In the example below, we will access WETH/USDC trade data for a given time range on the network of the node connection.
weth_usdc_trades = client.get_trades(
pair_name="WETH/USDC",
book_side=OrderSide.NEUTRAL, # options are NEUTRAL, BUY, SELL
start_time=1688187600,
end_time=1690606800,
)
That brings us to the end of the quickstart. Next see the overview
of the client's current functionality.