Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

An endpoint to calculate additional metadata for a Uniswap v3 liquidity position. In order to keep on-chain fees low, the Uniswap cryptocurrency exchange balances how much data it writes to the blockchain when users take out a liquidity position. Properties are saved in a few different contracts and so knowing exactly what a liquidity position entails takes a bit of additional calculation.

This endpoint takes in a token identifier that represents a liquidity position, and follows the math to make the values more human-friendly. This script uses the bignumber.js library to do higher-precision floating-point math than JavaScript can do on its own with number variables.

Usage

Find the token ID of the Uniswap position you wish to get details about. If you own the liquidity position, you can head to https://app.uniswap.org/pool and click on the v3 position. The ID of that position will be in the URL of the details page.

Append the token ID to the end of this val's URL to fetch data about it (e.g. to get information about token ID 12345, fetch the url https://midnightlightning-uniswapv3position.web.val.run/12345)

Reference

This val fetches blockchain data from the following smart contracts:

  • NonfungiblePositionManager(0xc36442b4a4522e871399cd717abdd847ab11fe88) Manages the liquidity position tokens as an ERC721 contract. The positions function is used to determine what tokens and tick ranges the individual liquidity position has.
  • UniswapV3Factory (0x1F98431c8aD98523631AE4a59f267346ea31F984) Contract that manages liquidity pools, and acts as a registry to record where each pairing is deployed to. The getPool function is used to find where the smart contract for a specific pairing of ERC20 tokens is deployed to.
  • ERC20 tokens Each position has two tokens it's balancing between, and each token has its own instance of an ERC20 contract deployed to the blockchain. The name and symbol functions are used to determine how to describe each token, and the decimals function to determine how to scale the price value for the ratio between them.
  • UniswapV3Pool Contract that handles swaps between specific pairs of tokens. The slot0 function is used to fetch the current price the two tokens are swapping at.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import BigNumber from "npm:bignumber.js";
import { createPublicClient, http, parseAbi } from "npm:viem";
import { mainnet } from "npm:viem/chains";
BigNumber.config({ DECIMAL_PLACES: 50, POW_PRECISION: 50, EXPONENTIAL_AT: 20 });
const client = createPublicClient({
chain: mainnet,
transport: http(),
});
export default async function(req: Request): Promise<Response> {
// Get the target token ID from URL path
const firstPath = req.url.split("/")[3];
if (firstPath == "")
return Response.json({
ok: false,
message: "No token ID provided",
});
let tokenId;
try {
tokenId = BigInt(firstPath);
} catch (err) {
return Response.json({
ok: false,
message: err.message,
});
}
// Fetch info about this liquidity position
const [
nonce,
operator,
token0,
token1,
fee,
tickLower,
tickUpper,
liquidity,
feeGrowthInside0LastX128,
feeGrowthInside1LastX128,
tokensOwed0,
tokensOwed1,
] = await client.readContract({
address: "0xc36442b4a4522e871399cd717abdd847ab11fe88",
abi: parseAbi([
"function positions(uint256 tokenId) external view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1Las
]),
functionName: "positions",
args: [tokenId],
});
// Fetch info about the two tokens in the position
const erc20ABI = parseAbi([
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
]);
const [token0Name, token0Symbol, token0Decimals, token1Name, token1Symbol, token1Decimals] = await client.multicall({
contracts: [
{
address: token0,
abi: erc20ABI,
functionName: "name",
},
{
address: token0,
abi: erc20ABI,
functionName: "symbol",
},
{
address: token0,
abi: erc20ABI,
functionName: "decimals",
},
{
address: token1,
abi: erc20ABI,
functionName: "name",
},
{
address: token1,
abi: erc20ABI,
functionName: "symbol",
},
{
address: token1,
abi: erc20ABI,
functionName: "decimals",
},
],
allowFailure: false,
});
// Calculate price ranges
const poolAddress = await client.readContract({
address: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
abi: parseAbi([
// mapping(address => mapping(address => mapping(uint24 => address))) public override getPool;
"function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address)",
midnightlightning-uniswapv3position.web.val.run
May 29, 2024