Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import type { AnyConfigSchema } from '@xyo-network/module-model'
import type { Payload, Schema } from '@xyo-network/payload-model'
import type { UniswapCryptoMarketPayload } from '@xyo-network/uniswap-crypto-market-payload-plugin'
import {
UniswapCryptoMarketSchema,
UniswapCryptoMarketWitnessConfigSchema,
UniswapCryptoMarketSchema,UniswapCryptoMarketWitnessConfigSchema,
} from '@xyo-network/uniswap-crypto-market-payload-plugin'
import type { WitnessParams } from '@xyo-network/witness-model'
import type { Provider } from 'ethers'

import type { UniswapCryptoMarketWitnessConfig } from './Config.ts'
import type { EthersUniSwap3Pair } from './lib/index.ts'
import {
createUniswapPoolContracts,
pricesFromUniswap3, UniswapPoolContracts,
createUniswapPoolContracts,pricesFromUniswap3, UniswapPoolContracts,
} from './lib/index.ts'

export type UniswapCryptoMarketWitnessParams = WitnessParams<
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './Ethers/index.ts'
export * from './pricesFromUniswap3.ts'
export * from './UniswapPoolContracts.ts'
export * from './v3/index.ts'
export * from './v4/index.ts'
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export type EthersUniswapV3Slot0Fields = [bigint, bigint, bigint, bigint, bigint, bigint, boolean]
export type UniswapV3Slot0Fields = [bigint, number, number, number, number, number, boolean]
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './createUniswapPoolContracts.ts'
export * from './pricesFromUniswap3.ts'
export * from './UniSwap3Pair.ts'
export * from './Uniswap3PoolSlot0Wrapper.ts'
export * from './UniswapPoolContracts.ts'
export * from './UniswapV3Slot0Fields.ts'
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { fulfilled } from '@xylabs/promise'
import type { UniswapCryptoPair } from '@xyo-network/uniswap-crypto-market-payload-plugin'

import type { EthersUniSwap3Pair } from './Ethers/index.ts'
import { logErrorsAsync } from './logErrors.ts'
import type { EthersUniSwap3Pair } from './index.ts'
import { logErrorsAsync } from '../logErrors.ts'

export const pricesFromUniswap3 = async (pools: EthersUniSwap3Pair[]): Promise<UniswapCryptoPair[]> => {
return await logErrorsAsync(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import '@xylabs/vitest-extended'

import { getProviderFromEnv } from '@xyo-network/witness-blockchain-abstract'
import {
describe, expect,
test,
describe, expect, test,
} from 'vitest'

import { createUniswapPoolContracts } from '../Ethers/index.ts'
import { pricesFromUniswap3 } from '../pricesFromUniswap3.ts'
import { createUniswapPoolContracts } from '../createUniswapPoolContracts.ts'
import { UniswapPoolContracts } from '../UniswapPoolContracts.ts'

describe.skipIf(!(process.env.INFURA_PROJECT_ID && process.env.INFURA_PROJECT_SECRET))('pricesFromUniswap3', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Token } from "@uniswap/sdk-core";

export interface UniswapV4TokenContractIdentifier {
tokenA: Token,
tokenB: Token,
fee: number,
hookAddress?: string,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface UniswapV4TokenIdentifier {
chainId: number,
address: string,
decimals: number,
symbol?: string,
name?: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Token } from "@uniswap/sdk-core"
import { ZeroAddress } from "ethers/constants"
import { Contract } from "ethers/contract"
import { Provider } from "ethers/providers"
import { getPoolId } from "./getPoolId.ts"
import { getPriceFromSqrtX96 } from "./getPriceFromSqrtX96.ts"

const STATE_VIEW_ADDRESS = "0x7ffe42c4a5deea5b0fec41c94c136cf115597227" // State view contract address

const STATE_VIEW_ABI = [{"inputs":[{"internalType":"contract IPoolManager","name":"_poolManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"}],"name":"getFeeGrowthGlobals","outputs":[{"internalType":"uint256","name":"feeGrowthGlobal0","type":"uint256"},{"internalType":"uint256","name":"feeGrowthGlobal1","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"}],"name":"getFeeGrowthInside","outputs":[{"internalType":"uint256","name":"feeGrowthInside0X128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1X128","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"}],"name":"getLiquidity","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"bytes32","name":"positionId","type":"bytes32"}],"name":"getPositionInfo","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"getPositionInfo","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"bytes32","name":"positionId","type":"bytes32"}],"name":"getPositionLiquidity","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"}],"name":"getSlot0","outputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"int24","name":"tick","type":"int24"},{"internalType":"uint24","name":"protocolFee","type":"uint24"},{"internalType":"uint24","name":"lpFee","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"int16","name":"tick","type":"int16"}],"name":"getTickBitmap","outputs":[{"internalType":"uint256","name":"tickBitmap","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"int24","name":"tick","type":"int24"}],"name":"getTickFeeGrowthOutside","outputs":[{"internalType":"uint256","name":"feeGrowthOutside0X128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthOutside1X128","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"int24","name":"tick","type":"int24"}],"name":"getTickInfo","outputs":[{"internalType":"uint128","name":"liquidityGross","type":"uint128"},{"internalType":"int128","name":"liquidityNet","type":"int128"},{"internalType":"uint256","name":"feeGrowthOutside0X128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthOutside1X128","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"PoolId","name":"poolId","type":"bytes32"},{"internalType":"int24","name":"tick","type":"int24"}],"name":"getTickLiquidity","outputs":[{"internalType":"uint128","name":"liquidityGross","type":"uint128"},{"internalType":"int128","name":"liquidityNet","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"poolManager","outputs":[{"internalType":"contract IPoolManager","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

/**
* Returns the price of the token pair in the Uniswap V4 pool.
* @param tokenA The first token in the pair.
* @param tokenB The second token in the pair.
* @param fee The fee tier for the pool.
* @param hookAddress The address of the hook contract. Default is ZeroAddress.
* @param provider The EVM provider to use for the transaction.
* @returns The price of the token pair.
*/
export const getExchangeRate = async (
tokenA: Token,
tokenB: Token,
fee: number,
hookAddress: string | undefined,
provider: Provider
): Promise<number> => {
const hooks = hookAddress || ZeroAddress
const stateView = new Contract(STATE_VIEW_ADDRESS, STATE_VIEW_ABI, provider)
const [token0, token1] = tokenA.sortsBefore(tokenB)
? [tokenA, tokenB]
: [tokenB, tokenA]

const poolId: string = getPoolId(token0, token1, fee, 60, hooks)
if (poolId === ZeroAddress) throw new Error("Invalid poolId")
const response = await stateView.getSlot0(poolId)
const sqrtPriceX96 = response[0]
const price = getPriceFromSqrtX96(sqrtPriceX96, token1.decimals, token0.decimals)
return price
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Token } from "@uniswap/sdk-core"
import { AbiCoder } from "ethers/abi"
import { ZeroAddress } from "ethers/constants"
import { keccak256 } from "ethers/crypto"

/**
* Computes the pool address for a given pair of tokens, fee, tick spacing, and hooks which
* is used to identify the pool on the Uniswap V4 protocol.
* @param currencyA - The first token in the pair.
* @param currencyB - The second token in the pair.
* @param fee - The fee tier for the pool.
* @param tickSpacing - The tick spacing for the pool.
* @param hooks - The hooks associated with the pool.
* @returns The computed pool address as a string.
*/
export const getPoolId = (currencyA: Token, currencyB: Token, fee: number, tickSpacing: number, hooks: string): string => {
const [currency0, currency1] = currencyA.sortsBefore(currencyB) ? [currencyA, currencyB] : [currencyB, currencyA]
const currency0Addr = currency0.isNative ? ZeroAddress : currency0.wrapped.address
const currency1Addr = currency1.isNative ? ZeroAddress : currency1.wrapped.address
return keccak256(
AbiCoder.defaultAbiCoder().encode(
['address', 'address', 'uint24', 'int24', 'address'],
[currency0Addr, currency1Addr, fee, tickSpacing, hooks]
),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Calculates the price from the square root price in Q96 format.
* @param sqrtPriceX96 The square root price in Q96 format.
* @param decimalsA The number of decimals for the first token.
* @param decimalsB The number of decimals for the second token.
* @returns The price as a number.
*/
export const getPriceFromSqrtX96 = (sqrtPriceX96: bigint, decimalsA: number, decimalsB: number): number => {
const Q96 = 2n ** 96n

// Scale to avoid floating point math by using a 1e18 factor
const numerator = sqrtPriceX96 * sqrtPriceX96 * 10n ** 18n
const denominator = Q96 * Q96

let price = numerator / denominator

// Adjust for decimal differences
const decimalAdjustment = decimalsB - decimalsA
if (decimalAdjustment > 0) {
price *= 10n ** BigInt(decimalAdjustment)
} else if (decimalAdjustment < 0) {
price /= 10n ** BigInt(-decimalAdjustment)
}

return Number(price) / 1e18
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './pricesFromUniswap4.ts'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Provider, ZeroAddress } from 'ethers'
import { getExchangeRate } from './getExchangeRate.ts'
import { UniswapV4TokenContractIdentifier } from './UniswapV4TokenContractIdentifier.ts'

export const pricesFromUniswap4 = async (contract: UniswapV4TokenContractIdentifier, provider: Provider) => {
const { tokenA, tokenB, fee, hookAddress = ZeroAddress } = contract
const rate = await getExchangeRate(tokenA, tokenB, fee, hookAddress, provider)
return rate
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import '@xylabs/vitest-extended'

import { getProviderFromEnv } from '@xyo-network/witness-blockchain-abstract'
import {
describe, expect, test,
} from 'vitest'
import { pricesFromUniswap4 } from '../pricesFromUniswap4.ts'
import { Token } from '@uniswap/sdk-core'

describe.skipIf(!(process.env.INFURA_PROJECT_ID && process.env.INFURA_PROJECT_SECRET))('pricesFromUniswap4', () => {
test('pricesFromUniswap4', async () => {
const provider = getProviderFromEnv()
const tokenA = new Token(1, "0x55296f69f40ea6d20e478533c15a6b08b654e758", 18, 'XYO')
const tokenB = new Token(1, "0xdac17f958d2ee523a2206206994597c13d831ec7", 6, 'USDT')
const tokenContractIdentifier = { tokenA, tokenB, fee: 3000 }
const value = await pricesFromUniswap4(tokenContractIdentifier, provider)
expect(value).toBeDefined()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import '@xylabs/vitest-extended'

import { PayloadSetPluginResolver } from '@xyo-network/payloadset-plugin'
import {
describe, expect,
test,
describe, expect, test,
} from 'vitest'

import { UniswapCryptoMarketPlugin } from '../Plugin.ts'
Expand Down
Loading