This can be done a few ways the uniswap interface grabs the ticks using the graph, but depending on what you need the liquidity depths for you may want to have the most recent information from the RPC.
I will show you how to grab this from the RPC
Note: I have only tested this code using chunks of 5000 due to its original use case. So you may run into query limitations if requesting the full range from the RPC, if thats the case you just need to break the calls into smaller chunks
The usage looks like so:
import { abi as MulticallABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
// Hardcoded uniswaps multiicall address:
const multicallAddress = '0x1F98415757620B543A52E61c46B32eB19261F984'
const multicallContract = getMulticallContract(multicallAddress, MulticallABI, provider);
const lowerTick = TickMath.MIN_TICK;
const upperTick = TickMath.MAX_TICK;
const tickRangeResponse = await getTickRangeResponses(tickRange.lowerTick, tickRange.upperTick, pool, multicallContract);
I've written a few methods to help out with this, there is too much code to dump in a single answer but the majority of the code was translated from:
Multicall usages in the uniswap interface
Add the following packages to your project:
"@uniswap/sdk-core": "^3.1.0",
"@uniswap/v3-sdk": "^3.9.0",
Add typechain and to the project:
Add a script in package.json
to compile the solidity code to ethers interfaces:
"scripts": {
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir types/v3 "./node_modules/@uniswap//artifacts/contracts//*[!dbg].json""
},
Run the generation and you should now have new Interfaces in your project
This is the translated Multicall Method:
import type { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'
import { IUniswapV3PoolState, IUniswapV3PoolStateInterface } from "../types/v3/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState";
import { getAddress } from "@ethersproject/address";
// Note: Multicall will throw an error if the contract call exceeds the expected provisioned gas required
// since we are only using static methods we jack the number up insanely high to assure execution
const DEFAULT_STATIC_CALL_GAS_REQUIRED = 1_000_000_000_000;
const POOL_STATE_INTERFACE = new Interface(IUniswapV3PoolStateABI) as IUniswapV3PoolStateInterface
export function singleContractMultipleValue<T>(
multicallContract: UniswapInterfaceMulticall,
address: string,
contractInterface: Interface,
methodName: string,
callInputs: OptionalMethodInputs[] = [undefined]
) : Promise<MappedCallResponse<T>> {
//Use Ethers to get the fragmented function
const fragment = contractInterface.getFunction(methodName);
if(!fragment) {
throw new Error('Invalid Fragment: ' + fragment)
}
// This will validate we are passing valid method arguments
const callDatas = callInputs.map(i => {
if(!isValidMethodArgs(i)) {
throw new Error('Invalid Method Args:' + i)
}
return contractInterface.encodeFunctionData(fragment, i);
});
// We construct the call data for our example we are using the same call data for eac
const calls = callDatas.map((callData, i) => { return { target: address, callData, gasLimit: BigNumber.from(DEFAULT_STATIC_CALL_GAS_REQUIRED) }});
const result = multicallContract.callStatic.multicall(calls).then(response => {
if(!(response instanceof Object)) {
return response
}
//Adapt to the proper return type
const clone: any = Object.assign({}, response);
clone.returnData = response.returnData.map(v => {
const vClone: any = Object.assign({}, v);
vClone.returnData = contractInterface.decodeFunctionResult(methodName, v.returnData);
return vClone;
});
return clone;
})
return result as Promise<MappedCallResponse<T>>;
}
This Method will take care of retrieving the responses:
export async function getTickRangeResponses(poolAddress: string, lowerBound: number, upperBound: number, tickSpacing: number,
multicallContract: UniswapInterfaceMulticall) {
const parameters = [];
for(let i = lowerBound; i <= upperBound; i+= tickSpacing) {
parameters.push([i]);
}
return singleContractMultipleValue<TickResponse>(
multicallContract, poolAddress, POOL_STATE_INTERFACE, 'ticks', parameters)
.catch(err => console.log('Mapped Call Responose error:' + err)) as Promise<MappedCallResponse<TickResponse>>
}
A few other things required for the call:
export type MappedCallResponse<T> = [
BigNumber,
([boolean, BigNumber, string] & {
success: boolean;
gasUsed: BigNumber;
returnData: string;
})[]
] & {
blockNumber: BigNumber;
returnData: ([boolean, BigNumber, string] & {
success: boolean;
gasUsed: BigNumber;
returnData: T;
})[];
}
export type CallResponse = MappedCallResponse<string>
export type MethodArg = string | number | BigNumber;
export type MethodArgs = Array<MethodArg | MethodArg[]>;
type OptionalMethodInputs =
| Array<MethodArg | MethodArg[] | undefined>
| undefined;
function isMethodArg(x: unknown): x is MethodArg {
return (
BigNumber.isBigNumber(x) || ['string', 'number'].indexOf(typeof x) !== -1
);
}
function isValidMethodArgs(x: unknown): x is MethodArgs | undefined {
return (
x === undefined ||
(Array.isArray(x) &&
x.every(
(xi) => isMethodArg(xi) || (Array.isArray(xi) && xi.every(isMethodArg)),
))
);
}
// returns the checksummed address if the address is valid, otherwise returns false
export function isAddress(value: any): string | false {
try {
return getAddress(value)
} catch {
return false
}
}
export function getMulticallContract(address: string, abi: any, provider: JsonRpcProvider): UniswapInterfaceMulticall {
return getContract(address, abi, provider, undefined) as UniswapInterfaceMulticall;
}
export function getContract<T extends Contract = Contract>(address: string, ABI: any, provider: JsonRpcProvider, account?: string): Contract {
if (!isAddress(address) || address === AddressZero) {
throw Error(`Invalid 'address' parameter '${address}'.`)
}
return new Contract(address, ABI, getProviderOrSigner(provider, account) as any) as T
}
// account is not optional
function getSigner(provider: JsonRpcProvider, account: string): JsonRpcSigner {
return provider.getSigner(account).connectUnchecked()
}
// account is optional
function getProviderOrSigner(provider: JsonRpcProvider, account?: string): JsonRpcProvider | JsonRpcSigner {
return account ? getSigner(provider, account) : provider
}