diff --git a/src/blockchain/contract.js b/src/blockchain/contract.js index 1e2aeab..083f514 100644 --- a/src/blockchain/contract.js +++ b/src/blockchain/contract.js @@ -4,7 +4,6 @@ import {useStore} from "@/store/store.js"; export function vaultAddress( owner, num=0) { - console.log('va', owner, num) if( !owner ) return null const s = useStore() diff --git a/src/blockchain/prices.js b/src/blockchain/prices.js index 990aa8a..73b92d6 100644 --- a/src/blockchain/prices.js +++ b/src/blockchain/prices.js @@ -15,10 +15,10 @@ export function subPrices( routes ) { const subRoutes = [] let chainId = null for( const route of routes ) { - console.log('sub route', route, subscriptionCounts) + // console.log('sub route', route, subscriptionCounts) if( !(route in subscriptionCounts) || subscriptionCounts[route] === 0 ) { subscriptionCounts[route] = 1 - console.log('subscribing to pool', route.pool) + // console.log('subscribing to pool', route.pool) subRoutes.push(route) } else { @@ -33,9 +33,7 @@ export function subPrices( routes ) { // perform a local query if necessary for( const route of subRoutes ) { const s = useStore() - 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) } } @@ -46,7 +44,7 @@ export function unsubPrices( routes ) { let chainId = null const unsubAddrs = [] for( const route of routes ) { - console.log('unsub route', route, subscriptionCounts) + // console.log('unsub route', route, subscriptionCounts) if( !(route in subscriptionCounts) ) { console.error('unsubscribed to a nonexistent route', route) } @@ -56,7 +54,7 @@ export function unsubPrices( routes ) { 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) + // console.log('unsubscribing from pool', route.pool) chainId = route.chainId } else if( subscriptionCounts[route] < 0 ) { @@ -71,7 +69,6 @@ export function unsubPrices( routes ) { 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 store = useStore(); @@ -88,7 +85,7 @@ async function getPriceForRoute(route) { price = FixedNumber.fromValue(price,0,WIDE_PRICE_FORMAT) price = price.div(FixedNumber.fromValue(2n**(96n*2n),0,WIDE_PRICE_FORMAT)) price = price.round(18).toString() - console.log(`price for ${route.token0.symbol}/${route.token1.symbol}`,price) + // console.log(`price for ${route.token0.symbol}/${route.token1.symbol}`,price) store.poolPrices[addr] = price return price } diff --git a/src/blockchain/route.js b/src/blockchain/route.js index cd7cedb..8dc6a9e 100644 --- a/src/blockchain/route.js +++ b/src/blockchain/route.js @@ -10,7 +10,7 @@ export async function findRoute(tokenA, tokenB) { const chainId = useStore().chainId const rawRoutes = await helper.getRoutes(tokenA.address, tokenB.address) // todo expose all available pools - console.log('raw routes', rawRoutes) + // 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) diff --git a/src/blockchain/uniswap.js b/src/blockchain/uniswap.js index 17c0e50..9cb9d4c 100644 --- a/src/blockchain/uniswap.js +++ b/src/blockchain/uniswap.js @@ -18,7 +18,6 @@ export function uniswapV3PoolAddress(chainId, tokenAddrA, tokenAddrB, fee) { 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 diff --git a/src/blockchain/wallet.js b/src/blockchain/wallet.js index b1c3f1e..6c84e14 100644 --- a/src/blockchain/wallet.js +++ b/src/blockchain/wallet.js @@ -1,5 +1,5 @@ import {ethers} from "ethers"; -import {setProvider, useStore} from "@/store/store"; +import {useStore} from "@/store/store"; import {socket} from "@/socket.js"; import {contractOrNull, vaultAddress} from "@/blockchain/contract.js"; import {vaultAbi} from "@/blockchain/abi.js"; @@ -13,7 +13,7 @@ export function onChainChanged(chainId) { store.chainId = chainId // touch the chainId last. will cause any clients of the store's provider getter to refresh store.account = null const provider = new ethers.BrowserProvider(window.ethereum, chainId); - setProvider(provider, chainId) + store.provider = provider provider.listAccounts().then((accounts)=>changeAccounts(accounts.map((a)=>a.address))) } } @@ -30,12 +30,10 @@ function changeAccounts(accounts) { const addr = accounts[0] const store = useStore() store.account = addr - console.log('set store.account to', addr, store.account) discoverVaults() flushTransactions() socket.emit('address', store.chainId, addr) } - console.log('changeAccounts ended') } function onAccountsChanged(accounts) { @@ -102,7 +100,6 @@ function discoverVaults() { s.vaults = [] else _discoverVaults(owner).then((result)=>{ - console.log('read store.account', s.account) if( s.account === owner ) { // double-check the account since it could have changed during our await s.vaults = result if( pendingOrders.length ) diff --git a/src/components/Amount.vue b/src/components/Amount.vue new file mode 100644 index 0000000..602e1ee --- /dev/null +++ b/src/components/Amount.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/components/PairChoice.vue b/src/components/PairChoice.vue index eff8d24..c0a226d 100644 --- a/src/components/PairChoice.vue +++ b/src/components/PairChoice.vue @@ -44,7 +44,7 @@ const tokenA = computed({ }, set(value) { if( !s.tokenA || s.tokenA.address !== value.address ) { - s.tokenA.value = value + s.tokenA = value routeFinder.invoke() } } @@ -64,25 +64,25 @@ const tokenB = computed({ const routes = computed({ get() { - return s.routes.value + return s.routes }, set(value) { - console.log('setting new routes', s.routes.value, value) - s.routes.value = value + console.log('setting new routes', s.routes, value) + s.routes = value } }) async function componentFindRoute() { const tokenA = s.tokenA const tokenB = s.tokenB - console.log('finding route', tokenA, tokenB) + // console.log('finding route', tokenA, tokenB) s.routes = [] if (!tokenA || !tokenB) return s.routesPending = true try { const result = await findRoute(tokenA, tokenB) - console.log('found route', result) + // console.log('found route', result) s.routes = result } catch (e) { diff --git a/src/components/RoutePrice.vue b/src/components/RoutePrice.vue index 6fa395d..823174a 100644 --- a/src/components/RoutePrice.vue +++ b/src/components/RoutePrice.vue @@ -21,7 +21,7 @@ const price = computed(()=>{ if( !route || !(route.pool in s.poolPrices) ) return '' let p = s.poolPrices[route.pool] - console.log('pool price is',typeof p, p) + // console.log('pool price is',typeof p, p) if( !p ) return '' p = FixedNumber.fromString(p, WIDE_PRICE_FORMAT).toUnsafeFloat() diff --git a/src/components/TimedOrderEntry.vue b/src/components/TimedOrderEntry.vue index d14bbcb..590da78 100644 --- a/src/components/TimedOrderEntry.vue +++ b/src/components/TimedOrderEntry.vue @@ -6,26 +6,13 @@
- - - + - - - - - @@ -64,9 +51,10 @@ import {useStore} from "@/store/store"; import {computed, ref} from "vue"; import PhoneCard from "@/components/PhoneCard.vue"; +import Amount from "@/components/Amount.vue" // noinspection ES6UnusedImports -import {routeInverted, SingletonCoroutine, vAutoSelect} from "@/misc.js"; -import {newLimitConstraint, newOrder, newTimeConstraint, sqrtX96, TimeMode} from "@/blockchain/orderlib.js"; +import {isEmpty, routeInverted, SingletonCoroutine, vAutoSelect, validateRequired, validateAmount} from "@/misc.js"; +import {newLimitConstraint, newOrder, newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js"; import {FixedNumber} from "ethers"; import {pendOrder} from "@/blockchain/wallet.js"; import NeedsProvider from "@/components/NeedsProvider.vue"; @@ -76,21 +64,14 @@ import PairChoice from "@/components/PairChoice.vue"; const s = useStore() -const amount = ref(100) // todo 0 -const amountIsTokenA = ref(false) -const amountIsTotal = ref(true) const tranches = ref(3) -const inverted = ref(false) const minPrice = ref(null) const maxPrice = ref(null) -const limitPrice = ref(null) const interval = ref(1) const intervalIsTotal = ref(true) const timeUnits = ['minutes', 'hours', 'days'] const timeUnitIndex = ref(0) -const limitIsMinimum = computed(() => !(s.buy ^ s.inverted)) -const validOrder = computed(()=>amount.value > 0 && s.routes.length > 0 ) - +const validOrder = computed(()=>s.validOrder) function toggleTimeUnits() { timeUnitIndex.value++ @@ -99,18 +80,6 @@ function toggleTimeUnits() { } -function isEmpty(v) { - return v === null || typeof v === 'string' && v.trim() === '' -} - - -function validateRequired(v) { - if (isEmpty(v)) - return 'Required' - return true -} - - function validateTranches(v) { const i = parseInt(v) if (parseFloat(v) !== i) @@ -123,17 +92,6 @@ function validateTranches(v) { } -function validateAmount(v) { - if (isEmpty(v)) - return true - const floatRegex = /^-?\d*(?:[.,]\d*?)?$/ - if (!floatRegex.test(v)) - return 'Amount must be a number' - if (parseFloat(v) <= 0) - return 'Amount must be positive' - return true -} - function validateMax(v) { if (!isEmpty(minPrice.value) && !isEmpty(v) && parseFloat(v) < parseFloat(minPrice.value)) return 'Must be greater than the minimum price' @@ -152,9 +110,7 @@ function placeOrder() { const tokenIn = s.buy ? tb.address : ta.address const tokenOut = s.buy ? ta.address : tb.address const route = s.route - const amountToken = amountIsTokenA.value ? ta : tb - const amt = FixedNumber.fromString(amount.value.toString(), {decimals: amountToken.decimals}).value - const amountIsInput = amountIsTokenA.value !== s.buy + const amt = FixedNumber.fromString(s.amount.toString(), {decimals: s.amountToken.decimals}).value // build tranches const n = tranches.value // num tranches @@ -173,20 +129,18 @@ function placeOrder() { const ceil = oneHundredPercent % BigInt(n) ? 1n : 0n 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 ) { + if( s.limitPrice ) { const inverted = routeInverted(route) - const isAbove = limitIsMinimum.value ^ inverted + const isAbove = s.limitIsMinimum ^ inverted const isRatio = false // todo ratios const decimals = 10 ** (s.tokenA.decimals - s.tokenB.decimals) - const limit = inverted ? decimals/limitPrice.value : limitPrice.value/decimals - priceConstraint = !limitPrice.value ? null : newLimitConstraint(isAbove, isRatio, limit) + const limit = inverted ? decimals/s.limitPrice : s.limitPrice/decimals + priceConstraint = !s.limitPrice ? null : newLimitConstraint(isAbove, isRatio, limit) } for (let i = 0; i < n; i++) { const start = Math.floor(i * (duration / Math.max((n - 1), 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) diff --git a/src/knownTokens.js b/src/knownTokens.js index a66cbeb..9965533 100644 --- a/src/knownTokens.js +++ b/src/knownTokens.js @@ -4,7 +4,7 @@ const known_chains = [ { name: 'Arbitrum One', id: 42161, - image: null, + image: '/arbitrum-logo.svg', } ] diff --git a/src/misc.js b/src/misc.js index 3fa8ead..0a1d766 100644 --- a/src/misc.js +++ b/src/misc.js @@ -58,3 +58,24 @@ export function routeInverted(route) { const s = useStore() return route && (route.token0 === s.tokenA) === s.inverted } + +export function isEmpty(v) { + return v === null || typeof v === 'string' && v.trim() === '' +} + +export function validateRequired(v) { + if (isEmpty(v)) + return 'Required' + return true +} + +export function validateAmount(v) { + if (isEmpty(v)) + return true + const floatRegex = /^-?\d*(?:[.,]\d*?)?$/ + if (!floatRegex.test(v)) + return 'Amount must be a number' + if (parseFloat(v) <= 0) + return 'Amount must be positive' + return true +} \ No newline at end of file diff --git a/src/socket.js b/src/socket.js index 858f825..b294014 100644 --- a/src/socket.js +++ b/src/socket.js @@ -17,18 +17,10 @@ socket.on('disconnect', () => { socket.on('welcome', async (data) => { console.log('welcome', data) - const mockCoins = data['chainInfo'][31337].mockCoins - console.log('coin order:', - mockCoins[1] > mockCoins[0] ? "coin1 > coin0" : "coin0 > coin1 (inverted)", - mockCoins) const s = useStore() + // todo put the vaultInitCodeHash into the chainInfo s.chainInfo = data.chainInfo s.vaultInitCodeHash = data.vaultInitCodeHash - // set default tokens in pair choice dropdown - if( s.tokenA === null && Object.values(s.tokens).length >= 1 ) - s.tokenA = Object.values(s.tokens)[0] - if( s.tokenB === null && Object.values(s.tokens).length >= 2 ) - s.tokenB = Object.values(s.tokens)[1] const p = new ethers.BrowserProvider(window.ethereum) const network = await p.getNetwork() if (network !== null) diff --git a/src/store/store.js b/src/store/store.js index 1fe0fa0..54ac958 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -3,90 +3,123 @@ import { defineStore } from 'pinia' import {knownTokens} from "@/knownTokens.js"; import {computed, ref} from "vue"; -let rawProvider = null -let rawProviderChainId = null -export function setProvider( provider, chainId ) { - rawProvider = provider - rawProviderChainId = chainId -} +export const useStore = defineStore('app', ()=> { + const _chainId = ref(null) + const _chainInfo = ref({}) + const tokenA = ref(null) + const tokenB = ref(null) -export const useStore = defineStore('app', { - state: () => ({ - chainId: null, - chainInfo: {}, - vaultInitCodeHash: null, - account: null, - vaults: [], - transactionSenders: [], // a list of function(signer) that send transactions - errors: [ - // todo re-enable danger warning - // {title: 'DANGER!', text: 'This is early development (alpha) software. There could be severe bugs that lose all your money. Thank you for testing a SMALL amount!', closeable: true} - ], - extraTokens: {}, - poolPrices: {}, - vaultBalances: {}, // indexed by vault addr then by token addr. value is an int - orders: {}, // indexed by vault, value is another dictionary with orderIndex as key and order status values + function getTokenList() { + const chains = _chainId.value in _chainInfo.value && _chainInfo.value[_chainId.value].tokens !== undefined ? + _chainInfo.value[_chainId.value].tokens : [] + let known = knownTokens[_chainId.value] + known = known ? Object.values(known) : [] + let extras = extraTokens[_chainId.value] + extras = extras ? Object.values(extras) : [] + return [...chains, ...known, ...extras]; // put chains first so the Mockcoin pool is automatically selected + } + function getTokens() { + const result = {} + const all = getTokenList(); + for (const token of all) + result[token.address] = token + return result + } + function setDefaultTokens() { + const tokens = getTokenList() + if( tokens.length > 0 ) + tokenA.value = tokens[0] + if( tokens.length > 1 ) + tokenB.value = tokens[1] + } + const chainId = computed({ + get() {return _chainId}, + set(v) {_chainId.value=v; setDefaultTokens()} + }) + const chainInfo = computed({ + get() {return _chainInfo}, + set(v) {_chainInfo.value=v; setDefaultTokens()} + }) + const chain = computed(() => !_chainId.value ? null : (_chainInfo.value[_chainId.value] || null)) + // making the provider directly reactive causes exceptions (calling private method...) when calling provider + // functions, so we use a separate ref to signal changes + let _provider = null + const _providerTouch = ref(false) + const provider = computed({ + get() {_providerTouch.value; return _provider}, + set(v) {_provider=v; _providerTouch.value = !_providerTouch.value} + }) + const vaultInitCodeHash = ref(null) + const account = ref(null) + const vaults = ref([]) + const transactionSenders = ref([]) // a list of function(signer) that send transactions + const errors = ref([]) + const extraTokens = ref({}) + const poolPrices = ref({}) + const vaultBalances = ref({}) // indexed by vault addr then by token addr. value is an int + const orders = ref({}) // indexed by vault value is another dictionary with orderIndex as key and order status values - // Order Input Forms - tokenA: null, - tokenB: null, - routes: [], - routesPending: false, - inverted: false, - }), - getters: { - vault: (s)=>s.vaults.length===0 ? null : s.vaults[0], - provider: (s)=>s.chainId===rawProviderChainId ? rawProvider : null, - chain: (s)=> !s.chainInfo ? null : (s.chainInfo[s.chainId] || null), - tokens: (s)=>{ - const chains = s.chainId in s.chainInfo && s.chainInfo[s.chainId].tokens !== undefined ? s.chainInfo[s.chainId].tokens : [] - let known = knownTokens[s.chainId] - known = known ? Object.values(known) : [] - let extras = s.extraTokens[s.chainId] - extras = extras ? Object.values(extras) : [] - const result = {} - const all = [...chains, ...known, ...extras]; // put chains first so the Mockcoin pool is automatically selected - for( const token of all) - result[token.address] = token - return result - }, - 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, - route: (s)=>s.routes.length===0 ? null : s.routes[0], - pairSymbol: (s)=>s.base?.symbol+'\\'+s.quote?.symbol, - base: (s)=>{ - const token = s.inverted ? s.tokenB : s.tokenA - return !token?{}:token - }, - quote: (s)=> { - const token = s.inverted ? s.tokenA : s.tokenB - return !token ? {} : token - }, - }, - actions: { - removeTransactionSender(sender) { - this.transactionSenders = this.transactionSenders.filter((v) => v !== sender) - }, - error(title, text, closeable=true) { - this.errors.push({title, text, closeable}) - }, - closeError(title, text) { - const result = [] - this.errors.forEach((i)=>{if(i.title!==title && i.text!==text) result.push(i)}) - this.errors = result - }, - addToken(chainId, info) { - this.$patch((s) => { - let extras = s.extraTokens[chainId] - if (extras === undefined) { - extras = {} - s.extraTokens[chainId] = extras - } - extras[info.address] = info - }) - }, - }, + // Order Input Forms + // const tokenA = ref(null) // defined at top + // const tokenB = ref(null) + const buy = ref(false) + const inverted = ref(false) + const amount = ref(100) // todo + const amountIsTokenA = ref(false) // todo + const amountIsTotal = ref(true) + const limitPrice = ref(null) + const routes = ref([]) + const routesPending = ref(false) + + const validOrder = computed(() => amount.value > 0 && routes.value.length > 0) + const vault = computed(() => vaults.value.length === 0 ? null : vaults.value[0]) + const tokens = computed(getTokens) + const factory = computed(() => !chain.value ? null : chain.value.factory) + const helper = computed(() => !chain.value ? null : chain.value.helper) + const mockenv = computed(() => !chain.value ? null : chain.value.mockenv) + const mockCoins = computed(() => !chain.value ? [] : !chain.value.mockCoins ? [] : chain.value.mockCoins) + const route = computed(() => routes.value.length === 0 ? null : routes.value[0]) + const base = computed(() => { + const token = inverted.value ? tokenB.value : tokenA.value + return !token ? {} : token + }) + const quote = computed(() => { + const token = inverted.value ? tokenA.value : tokenB.value + return !token ? {} : token + }) + const pairSymbol = computed(() => base.value?.symbol + '\\' + quote.value?.symbol) + const limitIsMinimum = computed(() => !(buy.value ^ inverted.value)) + const amountToken = computed(() => amountIsTokenA.value ? tokenA.value : tokenB.value) + const amountIsInput = computed(() => amountIsTokenA.value !== buy.value) + + function removeTransactionSender(sender) { + this.transactionSenders = this.transactionSenders.filter((v) => v !== sender) + } + function error(title, text, closeable=true) { + this.errors.push({title, text, closeable}) + } + function closeError(title, text) { + const result = [] + this.errors.forEach((i)=>{if(i.title!==title && i.text!==text) result.push(i)}) + this.errors = result + } + function addToken(chainId, info) { + this.$patch(() => { + let extras = extraTokens[chainId] + if (extras === undefined) { + extras = {} + extraTokens[chainId] = extras + } + extras[info.address] = info + }) + } + + return { + chainId, chainInfo, chain, provider, vaultInitCodeHash, account, vaults, transactionSenders, errors, extraTokens, + poolPrices, vaultBalances, orders, tokenA, tokenB, routes, routesPending, inverted, amount, amountIsTokenA, + amountIsTotal, limitPrice, validOrder, vault, tokens, factory, helper, mockenv, mockCoins, route, + pairSymbol, base, quote, limitIsMinimum, amountToken, amountIsInput, removeTransactionSender, error, closeError, + addToken, + } })