diff --git a/index.html b/index.html index cd32b0b..3b524b8 100644 --- a/index.html +++ b/index.html @@ -9,8 +9,7 @@ -
- +
diff --git a/src/blockchain/abi.js b/src/blockchain/abi.js index f090ae9..6c7c7b4 100644 --- a/src/blockchain/abi.js +++ b/src/blockchain/abi.js @@ -43,6 +43,10 @@ export const erc20Abi = [ 'event Approval(address indexed,address indexed,uint256)', ] +export const mockErc20Abi = [...erc20Abi, + 'function mint(address,uint256)', +] + const Route = '(uint8,uint24)' const Constraint = '(uint8,bytes)' const Tranche = `(uint64,${Constraint}[])` diff --git a/src/blockchain/orderlib.js b/src/blockchain/orderlib.js index 85b4207..c0d409f 100644 --- a/src/blockchain/orderlib.js +++ b/src/blockchain/orderlib.js @@ -1,4 +1,3 @@ - import {uint32max, uint64max} from "@/misc.js"; import {ethers} from "ethers"; @@ -49,18 +48,14 @@ export const Exchange = { } // enum ConstraintMode { -// Time, // 0 -// Limit, // 1 -// Trailing, // 2 -// Barrier, // 3 -// Line // 4 +// Time, // 0 +// Line, // 1 +// Barrier // 2 // } export const ConstraintMode = { Time: 0, - Limit: 1, - Trailing: 2, - Barrier: 3, - Line: 4, + Line: 1, + Barrier: 2, } // struct Constraint { @@ -98,12 +93,6 @@ export function newTimeConstraint(startMode, start, endMode, end) { ) } -// struct PriceConstraint { -// bool isAbove; -// bool isRatio; -// uint160 valueSqrtX96; -// } - // struct LineConstraint { // bool isAbove; // bool isRatio; @@ -112,3 +101,19 @@ export function newTimeConstraint(startMode, start, endMode, end) { // int160 slopeSqrtX96; // price change per second // } +export function newLimitConstraint( isAbove, isRatio, price ) { + return newLineConstraint(isAbove, isRatio, 0, price, 0) +} + +export function newLineConstraint( isAbove, isRatio, time, price, slope ) { + return encodeConstraint( + ConstraintMode.Line, + ['bool', 'bool', 'uint32', 'uint160', 'int160'], + [isAbove, isRatio, time, sqrtX96(price), sqrtX96(slope)] + ) +} + +export function sqrtX96(value) { + return BigInt(Math.round(Math.sqrt(value * 2 ** (96*2)))) +} + diff --git a/src/blockchain/prices.js b/src/blockchain/prices.js index df509f9..316368f 100644 --- a/src/blockchain/prices.js +++ b/src/blockchain/prices.js @@ -2,35 +2,40 @@ 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"; +import {ethers} from "ethers"; +import {uniswapV3PoolAbi} from "@/blockchain/abi.js"; -const subs = {} // key is route and value is a subscription counter +const subscriptionCounts = {} // key is route and value is a subscription counter export function subPrices( routes ) { + if( !routes.length ) + return const subRoutes = [] let chainId = null for( const route of routes ) { - if( !(route in subRoutes) || subRoutes[route] === 0 ) { - subRoutes[route] = 1 + console.log('sub route', route, subscriptionCounts) + if( !(route in subscriptionCounts) || subscriptionCounts[route] === 0 ) { + subscriptionCounts[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 + subscriptionCounts[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) ) + socket.emit('subPools', chainId, routes.map((r)=>r.pool) ) // 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) + console.log('route in prices?', route.pool in s.poolPrices, route.pool, s.poolPrices) + if( !(route.pool in s.poolPrices) ) { + console.log('querying initial route price', route.pool) + getPriceForRoute(route).then((price)=>s.poolPrices[route.pool]=price) } } } @@ -40,21 +45,22 @@ export function unsubPrices( routes ) { let chainId = null const unsubAddrs = [] for( const route of routes ) { - if( !(route in subs) ) { + console.log('unsub route', route, subscriptionCounts) + if( !(route in subscriptionCounts) ) { console.error('unsubscribed to a nonexistent route', route) } else { - subs[route]-- - if( subs[route] === 0 ) { + subscriptionCounts[route]-- + if( subscriptionCounts[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 ) { + else if( subscriptionCounts[route] < 0 ) { console.error('unsubscribed to an already unsubbed route', route) - subs[route] = 0 // fix + subscriptionCounts[route] = 0 // fix } } } @@ -67,7 +73,8 @@ 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; + const store = useStore(); + const provider = store.provider; if( provider === null ) { console.error('provider was null during getPriceForRoute') return null @@ -78,6 +85,8 @@ async function getPriceForRoute(route) { 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) + store.poolPrices[addr] = price + return price } else throw Error(`Unsupported exchange ${route.exchange}`) diff --git a/src/blockchain/route.js b/src/blockchain/route.js index 0eb6cd3..cd7cedb 100644 --- a/src/blockchain/route.js +++ b/src/blockchain/route.js @@ -10,14 +10,15 @@ export async function findRoute(tokenA, tokenB) { 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 + console.log('raw routes', rawRoutes) + let result = null // 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) { + if (!result || result.fee > fee) { switch (exchange) { case 0: // UniswapV2 - break + throw Error('Uniswap V2 not yet supported') case 1: // UniswapV3 const [token0, token1] = tokenA.address < tokenB.address ? [tokenA, tokenB] : [tokenB, tokenA] result = {chainId, exchange: Exchange.UniswapV3, pool, fee, token0, token1} @@ -25,5 +26,5 @@ export async function findRoute(tokenA, tokenB) { } } } - return [result] -} \ No newline at end of file + return result ? [result] : [] +} diff --git a/src/components/Faucet.vue b/src/components/Faucet.vue new file mode 100644 index 0000000..30e208b --- /dev/null +++ b/src/components/Faucet.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/components/PhoneCard.vue b/src/components/PhoneCard.vue index 9e22a0d..1691239 100644 --- a/src/components/PhoneCard.vue +++ b/src/components/PhoneCard.vue @@ -1,12 +1,15 @@ + + diff --git a/src/components/TimedOrderEntry.vue b/src/components/TimedOrderEntry.vue index 4d61e3e..05dbe4b 100644 --- a/src/components/TimedOrderEntry.vue +++ b/src/components/TimedOrderEntry.vue @@ -21,16 +21,17 @@ {{ s.chain.name }} v3 - {{pairSymbol}} {{r.fee/10000}}% + {{pairSymbol}} {{r.fee/10000}}%  +
Searching for {{pairSymbol}} pools...
- + -
+
-
@@ -89,12 +93,12 @@ import TokenChoice from "@/components/TokenChoice.vue" import PhoneCard from "@/components/PhoneCard.vue"; // noinspection ES6UnusedImports import {SingletonCoroutine, vAutoSelect} from "@/misc.js"; -import {newOrder, newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js"; +import {newLimitConstraint, newOrder, newTimeConstraint, sqrtX96, TimeMode} from "@/blockchain/orderlib.js"; import {FixedNumber} from "ethers"; import {pendOrder} from "@/blockchain/wallet.js"; import NeedsProvider from "@/components/NeedsProvider.vue"; -import {subPrices, unsubPrices} from "@/blockchain/prices.js"; import {findRoute} from "@/blockchain/route.js"; +import RoutePrice from "@/components/RoutePrice.vue"; const s = useStore() const buy = ref(false) @@ -138,18 +142,20 @@ const routes = computed({ return _routes.value }, set(value) { - console.log('setting new routes', value) - subPrices(value) - unsubPrices(_routes.value) + console.log('setting new routes', _routes.value, value) _routes.value = value } }) +const route = computed(()=>_routes.value.length===0 ? null : _routes.value[0]) const routesPending = ref(false) const amount = ref(100) // todo 0 const amountIsTokenA = ref(false) const amountIsTotal = ref(true) const tranches = ref(3) const inverted = ref(false) +function routeInverted(route) { + return route && (route.token0 === tokenA.value) === inverted.value +} const minPrice = ref(null) const maxPrice = ref(null) const limitPrice = ref(null) @@ -161,11 +167,6 @@ const limitIsMinimum = computed(() => !(buy.value ^ inverted.value)) const validOrder = computed(()=>amount.value > 0 && routes.value.length > 0 ) -onBeforeUnmount(() => { - unsubPrices(_routes.value) -}) - - async function componentFindRoute() { console.log('finding route', _tokenA.value, _tokenB.value) routes.value = [] @@ -271,11 +272,22 @@ function placeOrder() { const amtPerTranche = oneHundredPercent / BigInt(n) + ceil duration -= 15 // subtract 15 seconds so the last tranche completes before the deadline console.log('duration', duration) + let priceConstraint = null + if( limitPrice.value ) { + const inverted = routeInverted(route) + const isAbove = limitIsMinimum.value ^ inverted + const isRatio = false // todo ratios + const decimals = 10 ** (tokenA.value.decimals - tokenB.value.decimals) + const limit = inverted ? decimals/limitPrice.value : limitPrice.value/decimals + priceConstraint = !limitPrice.value ? null : newLimitConstraint(isAbove, isRatio, limit) + } for (let i = 0; i < n; i++) { const start = Math.floor(i * (duration / (n-1))) const end = start + window console.log('tranche window', start, end, (end-start)/60) const cs = [newTimeConstraint(TimeMode.SinceOrderStart, start, TimeMode.SinceOrderStart, end)] + if( priceConstraint !== null ) + cs.push(priceConstraint) ts.push([amtPerTranche, cs]) } const order = newOrder(tokenIn, tokenOut, route.exchange, route.fee, amt, amountIsInput, ts) diff --git a/src/socket.js b/src/socket.js index d2f2365..30a65f5 100644 --- a/src/socket.js +++ b/src/socket.js @@ -28,7 +28,10 @@ socket.on('p', async (pool, price) => { const s = useStore() const prices = {} prices[pool] = price - s.$patch({poolPrices: prices}) + console.log('pool price', pool, price) + const poolPrices = s.poolPrices + poolPrices[pool] = price + s.poolPrices = poolPrices }) socket.on('vb', async (vault, balances) => { diff --git a/src/store/store.js b/src/store/store.js index 431b0da..1972d57 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -44,6 +44,8 @@ export const useStore = defineStore('app', { }, factory: (s)=>!s.chain?null:s.chain.factory, helper: (s)=>!s.chain?null:s.chain.helper, + mockenv: (s)=>!s.chain?null:s.chain.mockenv, + mockCoins: (s)=>!s.chain?[]:!s.chain.mockCoins?[]:s.chain.mockCoins, }, actions: { removeTransactionSender(sender) { diff --git a/src/styles/style.scss b/src/styles/style.scss index 044f490..4c6e991 100644 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -1,6 +1,6 @@ @use "/src/styles/vars" as v; -.app { +#app { .clickable { :hover { cursor: pointer; diff --git a/src/views/VaultView.vue b/src/views/VaultView.vue index 400b20b..163c8aa 100644 --- a/src/views/VaultView.vue +++ b/src/views/VaultView.vue @@ -1,7 +1,8 @@ @@ -9,6 +10,7 @@ import {useStore} from "@/store/store"; import Vault from "@/components/Vault.vue"; import NeedsSigner from "@/components/NeedsSigner.vue"; +import Faucet from "@/components/Faucet.vue"; const s = useStore()