Interfacing With UniswapV2Router02: 2 ― Fetching Token Pairs From the Subgraph

The first thing I was wondering when I started working on the app was where Uniswap was sourcing information about the tokens that end up in its swap interface. Specifically, Uniswap needs to know about token contract addresses, names, symbols, and images. One important concept to understand is that, while Uniswap does use token lists, this in no way limits what tokens can be traded, as is obvious from Uniswap's token selector:

Uniswap token selector

As the placeholder text says, any address of a contract that implements the ERC20 token standard can be entered here. If there is enough liquidity, i.e. if it is possible for Uniswap to find a route of one or multiple pairs where enough reserves exist in order for Uniswap to transfer a sufficient amount of the output token to the user, the swap can be executed through Uniswap's router.

Creating a pair is equally painless. Imagine you have just deployed your own ERC20 token contract to mainnet, and now you would like people to be able to trade it on Uniswap. Since it wasn't traded before, no liquidity or pair contract exists. But, just like the swap interface, the Pool dialog will let you enter your token's address. Since you are the one creating the liquidity pool, you get to determine your token's initial value relative to the other token in the pool, as you choose the ratio in which you provide Uniswap with reserves of both tokens in exchange for your pool's liquidity token. What happens now in terms of contracts is that UniswapV2Factory deploys a contract that implements UniswapV2Pair, and this new pair's reserves are initialized with the amounts provided by you. Once the pair exists, the Uniswap router can execute swaps that feature your token at any point in the route, so long as the pool has sufficient liquidity.

But back to initializing the token selector with a list of tokens. As mentioned before, Uniswap is using a number of token lists, and the de facto standard source of lists ― the list of lists so to speak ―, can be found at https://tokenlists.org. There are a bunch of lists there from various exchanges and other DeFi sites, some of them more frequently updated than others. The largest one, which also happens to be updated on a daily basis, is CoinGecko's. It's one of the default lists that can be loaded from Uniswap's token list manager, and it features almost 4700 tokens at the time of writing.

In the first part, I mentioned that one of my app's restrictions would be that it would allow only single-hop swaps, meaning a direct pair for the input and output token must exist in order for a swap to be possible. This limitation was chosen for reasons of simplicity. It also makes just plugging a list of tokens without knowing where direct pairs exist less than ideal. So I was looking for a source of pair information.

It would be possible, if a little unwieldy, to get a list of pairs from the UniswapV2Factory contract by using its allPairsLength and allPairs functions. However, there's a wonderful off-chain data source of all things Uniswap over at The Graph, which was my preferred option, because it also allows me to query for pairs that have a lot of reserves. The Graph is another of the go-to technologies used by DApp developers: it allows for the creation of subgraphs, which essentially are data stores fed from data extracted from the blockchain, and queryable using GraphQL. There are many pre‑existing subgraphs provided by The Graph, and unsurprisingly, there's one for UniswapV2.

For querying GraphQL data sources from a React app, there's Apollo Client. It does provide hooks for fetching GraphQL data from within a React component, but since I would only fetch a bunch of token pairs as a once-off, I opted for just dispatching an action that calls query directly on the client. The query for fetching the top 100 pairs by USD reserves is formulated as a tagged template:

const TOP_PAIRS = gql`
{
pairs(
where: {
token0_not: "
${TokenId.UETH}",
token1_not: "
${TokenId.UETH}"
},
orderBy: reserveUSD,
orderDirection: desc,
first: 100
) {
id
token0 {
id
symbol
decimals
}
token1 {
id
symbol
decimals
}
reserveUSD
}
}
`

id here is identical with the token's contract address. Just as an aside, the reason for excluding UETH is that it features in a number of pairs with extremely large reserves that aren't meant to be used in swaps directly. UETH (ulock.eth Wrapped Ether) seems to be some liquidity providing solution I haven't wrapped my head around.

It's the tokens from the pairs returned for above query that end up in the app's token requester:

Token requester

Once an input token is selected, the options for the output token are restricted to those for which there's a direct pairing with the input token in the top 100 pairs that were fetched.

Another detail worth mentioning is that, wherever there's a liquidity pool for swapping between ether and some token, ether will be represented in the pair as WETH (Wrapped Ether): since a pool always pairs two ERC20 tokens, an ERC20 that wraps ether needed to be created. The UI pays heed to this by displaying ETH instead of WETH as the symbol for Wrapped Ether.