diff --git a/src/blockchain/abi.js b/src/blockchain/abi.js index 8bfd844..f090ae9 100644 --- a/src/blockchain/abi.js +++ b/src/blockchain/abi.js @@ -6,7 +6,7 @@ export const queryHelperAbi = [ 'function getRoutes(address tokenA,address tokenB) view returns((uint8,uint24,address)[])', ] -export const poolAbi = [ +export const uniswapV3PoolAbi = [ // { // // the current price // uint160 sqrtPriceX96; diff --git a/src/blockchain/contract.js b/src/blockchain/contract.js index 6a6268f..a16a02a 100644 --- a/src/blockchain/contract.js +++ b/src/blockchain/contract.js @@ -4,6 +4,8 @@ import {useStore} from "@/store/store.js"; export function vaultAddress( owner, num=0) { + if( !owner ) + return null const s = useStore() const salt = ethers.solidityPackedKeccak256(['address','uint8'],[owner,num]) return ethers.getCreate2Address(s.factory, salt, s.vaultInitCodeHash) @@ -31,7 +33,7 @@ export async function queryHelperContract() { export async function poolContract(addr) { const s = useStore() - return contractOrNull(addr, poolAbi, s.provider) + return contractOrNull(addr, uniswapV3PoolAbi, s.provider) } export async function vaultContract(num, signer) { diff --git a/src/blockchain/prices.js b/src/blockchain/prices.js new file mode 100644 index 0000000..df509f9 --- /dev/null +++ b/src/blockchain/prices.js @@ -0,0 +1,84 @@ +import {socket} from "@/socket.js"; +import {useStore} from "@/store/store.js"; +import {Exchange} from "@/blockchain/orderlib.js"; +import {uniswapV3PoolAddress} from "@/blockchain/uniswap.js"; +import {ethers, FixedNumber} from "ethers"; +import {erc20Abi, uniswapV3PoolAbi} from "@/blockchain/abi.js"; + +const subs = {} // key is route and value is a subscription counter + + +export function subPrices( routes ) { + const subRoutes = [] + let chainId = null + for( const route of routes ) { + if( !(route in subRoutes) || subRoutes[route] === 0 ) { + subRoutes[route] = 1 + console.log('subscribing to pool', route.pool) + subRoutes.push(route) + } + else { + subRoutes[route]++ + if( chainId !== null && route.chainId !== chainId ) + throw Error('cannot mix chainIds in a subscription list') + chainId = route.chainId + } + } + if( subRoutes.length ) { + socket.emit('subPools', chainId, routes.map((r)=>r.address) ) + // perform a local query if necessary + for( const route of subRoutes ) { + const s = useStore() + if( !(route.address in s.poolPrices) ) { + getPriceForRoute(route).then((price)=>s.poolPrices[route.address]=price) + } + } + } +} + +export function unsubPrices( routes ) { + let chainId = null + const unsubAddrs = [] + for( const route of routes ) { + if( !(route in subs) ) { + console.error('unsubscribed to a nonexistent route', route) + } + else { + subs[route]-- + if( subs[route] === 0 ) { + unsubAddrs.push(route.pool) + if( chainId !== null && route.chainId !== chainId ) + throw Error('cannot mix chainIds in a subscription list') + console.log('unsubscribing from pool', route.pool) + chainId = route.chainId + } + else if( subs[route] < 0 ) { + console.error('unsubscribed to an already unsubbed route', route) + subs[route] = 0 // fix + } + } + } + if( unsubAddrs.length ) + socket.emit('unsubPool', chainId, unsubAddrs ) +} + + +async function getPriceForRoute(route) { + console.log('route is',route) + if( route.exchange === Exchange.UniswapV3 ) { + const addr = uniswapV3PoolAddress(route.chainId, route.token0.address, route.token1.address, route.fee) + const provider = useStore().provider; + if( provider === null ) { + console.error('provider was null during getPriceForRoute') + return null + } + const pool = new ethers.Contract(addr, uniswapV3PoolAbi, provider) + const got = await pool.slot0() + const [sqrtPrice,,,,,,] = got + const spn = Number(sqrtPrice) + const price = spn*spn/2**(96*2) * 10**(route.token0.decimals-route.token1.decimals) + console.log(`price for ${route.token0.symbol}/${route.token1.symbol}`,price) + } + else + throw Error(`Unsupported exchange ${route.exchange}`) +} diff --git a/src/blockchain/route.js b/src/blockchain/route.js new file mode 100644 index 0000000..0eb6cd3 --- /dev/null +++ b/src/blockchain/route.js @@ -0,0 +1,29 @@ +import {queryHelperContract} from "@/blockchain/contract.js"; +import {Exchange} from "@/blockchain/orderlib.js"; +import {useStore} from "@/store/store.js"; + + +export async function findRoute(tokenA, tokenB) { + const helper = await queryHelperContract() + if (!helper) + throw Error('no helper') + const chainId = useStore().chainId + const rawRoutes = await helper.getRoutes(tokenA.address, tokenB.address) + // todo expose all available pools + let result = {} // we actually only find a single pool for now + for (let [exchange, fee, pool] of rawRoutes) { + exchange = Number(exchange) + fee = Number(fee) + if (result.fee === undefined || result.fee > fee) { + switch (exchange) { + case 0: // UniswapV2 + break + case 1: // UniswapV3 + const [token0, token1] = tokenA.address < tokenB.address ? [tokenA, tokenB] : [tokenB, tokenA] + result = {chainId, exchange: Exchange.UniswapV3, pool, fee, token0, token1} + break + } + } + } + return [result] +} \ No newline at end of file diff --git a/src/blockchain/uniswap.js b/src/blockchain/uniswap.js new file mode 100644 index 0000000..63bfd71 --- /dev/null +++ b/src/blockchain/uniswap.js @@ -0,0 +1,24 @@ +import {ethers} from "ethers"; + +const UNISWAPV3_POOL_INIT_CODE_HASH = '0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54' +const uniswapV3Addresses = { + 42161: { + factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', + }, + 31337: { + factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', + }, +} + +export function uniswapV3PoolAddress(chainId, tokenAddrA, tokenAddrB, fee) { + const [addr0, addr1] = tokenAddrA < tokenAddrB ? [tokenAddrA, tokenAddrB] : [tokenAddrB, tokenAddrA] + const encoded = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'address', 'uint24'], [addr0, addr1, fee]); + const salt = ethers.keccak256(encoded) + const factory = uniswapV3Addresses[chainId]?.factory + console.log('uni3addr', addr0, addr1, fee, salt, factory) + if (!factory) { + console.log('no uniswap factory for chain', chainId) + return null + } + return ethers.getCreate2Address(factory, salt, UNISWAPV3_POOL_INIT_CODE_HASH) +} \ No newline at end of file diff --git a/src/blockchain/wallet.js b/src/blockchain/wallet.js index a96bb07..4c0eb9e 100644 --- a/src/blockchain/wallet.js +++ b/src/blockchain/wallet.js @@ -81,8 +81,9 @@ const errorHandlingProxy = { export async function connectWallet() { - console.log('TODO connect wallet') // eth_getaccounts + const s = useStore() + await s.provider.getSigner() } const pendingOrders = [] diff --git a/src/components/NeedsSigner.vue b/src/components/NeedsSigner.vue new file mode 100644 index 0000000..406f14b --- /dev/null +++ b/src/components/NeedsSigner.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/components/TimedOrderEntry.vue b/src/components/TimedOrderEntry.vue index e4e3180..4d61e3e 100644 --- a/src/components/TimedOrderEntry.vue +++ b/src/components/TimedOrderEntry.vue @@ -84,16 +84,17 @@