diff --git a/src/blockchain/contract.js b/src/blockchain/contract.js index 083f514..f9c8239 100644 --- a/src/blockchain/contract.js +++ b/src/blockchain/contract.js @@ -4,9 +4,10 @@ import {useStore} from "@/store/store.js"; export function vaultAddress( owner, num=0) { + const s = useStore() + // console.log('vaultAddress', owner, s.factory, s.vaultInitCodeHash) if( !owner ) return null - const s = useStore() const salt = ethers.solidityPackedKeccak256(['address','uint8'],[owner,num]) return ethers.getCreate2Address(s.factory, salt, s.vaultInitCodeHash) } diff --git a/src/blockchain/prices.js b/src/blockchain/prices.js index 73b92d6..99bff64 100644 --- a/src/blockchain/prices.js +++ b/src/blockchain/prices.js @@ -16,25 +16,28 @@ export function subPrices( routes ) { let chainId = null for( const route of routes ) { // console.log('sub route', route, subscriptionCounts) - if( !(route in subscriptionCounts) || subscriptionCounts[route] === 0 ) { - subscriptionCounts[route] = 1 - // console.log('subscribing to pool', route.pool) + const routeKey = [route.chainId, route.pool] + if( !(routeKey in subscriptionCounts) || subscriptionCounts[routeKey] === 0 ) { + subscriptionCounts[routeKey] = 1 subRoutes.push(route) } else { - subscriptionCounts[route]++ + subscriptionCounts[routeKey]++ } 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.pool) ) + const pools = routes.map((r)=>r.pool); + // console.log('subscribing to pools', pools) + socket.emit('subPools', chainId, pools ) // perform a local query if necessary + const s = useStore() for( const route of subRoutes ) { - const s = useStore() - if( !(route.pool in s.poolPrices) ) { - getPriceForRoute(route).then((price)=>s.poolPrices[route.pool]=price) + const routeKey = [route.chainId, route.pool] + if( !(routeKey in s.poolPrices) ) { + getPriceForRoute(route).then((price)=>s.poolPrices[routeKey]=price) } } } @@ -45,26 +48,27 @@ export function unsubPrices( routes ) { const unsubAddrs = [] for( const route of routes ) { // console.log('unsub route', route, subscriptionCounts) - if( !(route in subscriptionCounts) ) { + const routeKey = [route.chainId, route.pool] + if( !(routeKey in subscriptionCounts) ) { console.error('unsubscribed to a nonexistent route', route) } else { - subscriptionCounts[route]-- - if( subscriptionCounts[route] === 0 ) { + subscriptionCounts[routeKey]-- + if( subscriptionCounts[routeKey] === 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( subscriptionCounts[route] < 0 ) { + else if( subscriptionCounts[routeKey] < 0 ) { console.error('unsubscribed to an already unsubbed route', route) - subscriptionCounts[route] = 0 // fix + subscriptionCounts[routeKey] = 0 // fix } } } if( unsubAddrs.length ) - socket.emit('unsubPool', chainId, unsubAddrs ) + socket.emit('unsubPools', chainId, unsubAddrs ) } @@ -86,7 +90,7 @@ async function getPriceForRoute(route) { 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) - store.poolPrices[addr] = price + store.poolPrices[[route.chainId,addr]] = price return price } else diff --git a/src/blockchain/route.js b/src/blockchain/route.js index 8dc6a9e..669aaa3 100644 --- a/src/blockchain/route.js +++ b/src/blockchain/route.js @@ -3,14 +3,13 @@ import {Exchange} from "@/blockchain/orderlib.js"; import {useStore} from "@/store/store.js"; -export async function findRoute(tokenA, tokenB) { +export async function findRoute(chainId, 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 - // 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/wallet.js b/src/blockchain/wallet.js index 60846b9..bffe88d 100644 --- a/src/blockchain/wallet.js +++ b/src/blockchain/wallet.js @@ -10,36 +10,37 @@ export function onChainChanged(chainId) { const store = useStore() if( chainId !== store.chainId ) { console.log('chain changed', chainId) - store.chainId = chainId // touch the chainId last. will cause any clients of the store's provider getter to refresh + store.chainId = chainId store.account = null const provider = new ethers.BrowserProvider(window.ethereum, chainId); store.provider = provider - provider.listAccounts().then((accounts)=>changeAccounts(accounts.map((a)=>a.address))) + provider.listAccounts().then((accounts)=>changeAccounts(chainId, accounts.map((a)=>a.address))) } } -function changeAccounts(accounts) { - console.log('change accounts', accounts) +function changeAccounts(chainId, accounts) { + // console.log('changeAccounts', chainId, accounts) const store = useStore() if( accounts.length === 0 ) { + console.log('account logged out') store.account = null store.vaults = [] store.vaultBalances = {} } else { const addr = accounts[0] - const store = useStore() + console.log('account logged in', addr) store.account = addr - discoverVaults() + discoverVaults(addr) flushTransactions() - socket.emit('address', store.chainId, addr) + socket.emit('address', chainId, addr) } } function onAccountsChanged(accounts) { const store = useStore() if (accounts.length === 0 || accounts[0] !== store.account) - changeAccounts(accounts); + changeAccounts(store.chainId.value, accounts); } export function detectChain() { @@ -93,15 +94,14 @@ export async function connectWallet() { let pendingOrders = [] -function discoverVaults() { +function discoverVaults(owner) { const s = useStore() - const owner = s.account.value if( owner === null ) - s.vaults.value = [] + s.vaults = [] else _discoverVaults(owner).then((result)=>{ - if( s.account.value === owner ) { // double-check the account since it could have changed during our await - s.vaults.value = result + if( s.account === owner ) { // double-check the account since it could have changed during our await + s.vaults = result if( pendingOrders.length ) if( result.length ) flushOrders(result[0]) @@ -114,18 +114,22 @@ function discoverVaults() { async function _discoverVaults(owner) { const result = [] // todo multi-vault scan - const addr = vaultAddress(owner, 0) + const num = 0 + const addr = vaultAddress(owner, num) const s = useStore() const vault = new ethers.Contract(addr, vaultAbi, s.provider) let version = -1 try { version = await vault.version(); - if( version === 1 ) + if( version === 1 ) { + console.log(`found vault ${num} at ${addr}`) result.push(addr) + } else console.error(`bad vault version ${version}`) } catch (e) { + console.log(`no vault ${num}`) } return result } @@ -133,11 +137,16 @@ async function _discoverVaults(owner) { export function ensureVault() { const s = useStore() - ensureVault2(s.chainId.value, s.account.value, 0) + const owner = s.account; + console.log('ensureVault', s.chainId.value, owner) + if( !owner ) + return + ensureVault2(s.chainId.value, owner, 0) } export function ensureVault2(chainId, owner, num) { + console.log('ensureVault2', chainId, owner, num) if( !chainId ) { console.log('cannot create vault: no chain selected') return @@ -153,12 +162,12 @@ export function ensureVault2(chainId, owner, num) { export async function pendOrder(order) { console.log('order', JSON.stringify(order)) const s = useStore() - if (!s.vaults.value.length) { + if (!s.vaults.length) { pendingOrders.push(order) ensureVault() } else { - const vault = s.vaults.value[0]; + const vault = s.vaults[0]; pendOrderAsTransaction(vault, order) } } @@ -224,11 +233,11 @@ export async function asyncFlushTransactions() { export async function asyncFlushTransactions2() { // todo rework into flushTransactions() const s = useStore() - if( s.provider.value === null ) { + if( s.provider === null ) { console.log('warning: asyncFlushOrders() cancelled due to null provider') return } - const senders = s.transactionSenders.value + const senders = s.transactionSenders if (!senders.length) return console.log(`flushing ${senders.length} transactions`) diff --git a/src/components/Amount.vue b/src/components/Amount.vue index 602e1ee..db98899 100644 --- a/src/components/Amount.vue +++ b/src/components/Amount.vue @@ -1,21 +1,23 @@ diff --git a/src/components/Faucet.vue b/src/components/Faucet.vue index 7cf7f28..0545578 100644 --- a/src/components/Faucet.vue +++ b/src/components/Faucet.vue @@ -49,7 +49,7 @@ function gib() { const s = useStore() if( s.account ) { disabled.value = true - socket.emit('faucet', s.chainId, s.account) + socket.emit('faucet', s.chainId.value, s.account) setTimeout(()=>disabled.value=false, 60*1000) } } diff --git a/src/components/LadderOrder.vue b/src/components/LadderOrder.vue new file mode 100644 index 0000000..17bfa47 --- /dev/null +++ b/src/components/LadderOrder.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/components/LimitPrice.vue b/src/components/LimitPrice.vue new file mode 100644 index 0000000..5a4ff33 --- /dev/null +++ b/src/components/LimitPrice.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/components/Order.vue b/src/components/Order.vue new file mode 100644 index 0000000..a005ce1 --- /dev/null +++ b/src/components/Order.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/components/Orders.vue b/src/components/Orders.vue index c4e7148..e7b5ddf 100644 --- a/src/components/Orders.vue +++ b/src/components/Orders.vue @@ -70,8 +70,8 @@ function tokenAmount(tokenAddr, amount) { const t = token(tokenAddr) if( !t ) return '' - console.log('tokenAmount amount', typeof amount, amount) - console.log('tokenAmount decimals', typeof t.decimals, t.decimals) + // console.log('tokenAmount amount', typeof amount, amount) + // console.log('tokenAmount decimals', typeof t.decimals, t.decimals) const amt = FixedNumber.fromValue(amount, t.decimals, {width:256, decimals:t.decimals, signed:false}) return `${amt} ${t.symbol}` } @@ -111,7 +111,6 @@ function pair(inTokenAddr, outTokenAddr, vaultAddr, index) { const orders = computed(()=>{ const result = [] - console.log('computing orders') // for( const [status] of pendingOrders.reverse() ) { // console.log('adding pended order') // const inTokenAddr = status[0] diff --git a/src/components/PairChoice.vue b/src/components/PairChoice.vue index c0a226d..fd56f4f 100644 --- a/src/components/PairChoice.vue +++ b/src/components/PairChoice.vue @@ -1,14 +1,14 @@ + + diff --git a/src/components/TimedOrderEntry.vue b/src/components/TimedOrderEntry.vue deleted file mode 100644 index 590da78..0000000 --- a/src/components/TimedOrderEntry.vue +++ /dev/null @@ -1,196 +0,0 @@ - - - - - diff --git a/src/components/Tranches.vue b/src/components/Tranches.vue new file mode 100644 index 0000000..3ba1f78 --- /dev/null +++ b/src/components/Tranches.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/Vault.vue b/src/components/Vault.vue index 0c82e3a..5227a6d 100644 --- a/src/components/Vault.vue +++ b/src/components/Vault.vue @@ -110,9 +110,19 @@ function onWithdraw(addr) { withdrawShow.value = true } + +function checkVault() { + console.log('checkVault', props.num, s.account, s.vault) + if(props.num===0 && s.account && !s.vault) + ensureVault() +} + // todo remove automatic vault creation for Alpha 2 -if(props.num===0 && !s.vault) - ensureVault() +s.$subscribe((mutation, state)=>{ + console.log('test') + checkVault() +}) +checkVault() diff --git a/src/misc.js b/src/misc.js index 0a1d766..e89128c 100644 --- a/src/misc.js +++ b/src/misc.js @@ -59,23 +59,3 @@ export function routeInverted(route) { 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/orderbuild.js b/src/orderbuild.js new file mode 100644 index 0000000..77fcbf7 --- /dev/null +++ b/src/orderbuild.js @@ -0,0 +1,46 @@ +import {routeInverted} from "@/misc.js"; +import {newLimitConstraint, newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js"; +import {useOrderStore, useStore} from "@/store/store.js"; + +export function limitConstraint() { + const s = useStore() + if (!s.limitPrice) + return null + const route = s.route + const inverted = routeInverted(route) + const isAbove = s.limitIsMinimum ^ inverted + const isRatio = false // todo ratios + const decimals = 10 ** (s.tokenA.decimals - s.tokenB.decimals) + const limit = inverted ? decimals / s.limitPrice : s.limitPrice / decimals + return newLimitConstraint(isAbove, isRatio, limit) +} + +export function timesliceTranches() { + const ts = [] + const os = useOrderStore() + const n = os.tranches // num tranches + const interval = os.interval; + const timeUnitIndex = os.timeUnitIndex; + let duration = + timeUnitIndex === 0 ? interval * 60 : // minutes + timeUnitIndex === 1 ? interval * 60 * 60 : // hours + interval * 24 * 60 * 60; // days + let window + if (!os.intervalIsTotal) { + window = duration + duration *= n // duration is the total time for all tranches + } else { + window = Math.round(duration / n) + } + const oneHundredPercent = 65535n // by contract definition of uint16 fraction + 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 + for (let i = 0; i < n; i++) { + const start = Math.floor(i * (duration / Math.max((n - 1), 1))) + const end = start + window + const cs = [newTimeConstraint(TimeMode.SinceOrderStart, start, TimeMode.SinceOrderStart, end)] + ts.push([amtPerTranche, cs]) + } + return ts +} diff --git a/src/socket.js b/src/socket.js index b294014..a154ca9 100644 --- a/src/socket.js +++ b/src/socket.js @@ -3,7 +3,6 @@ import {useStore} from "@/store/store.js"; import {flushOrders, onChainChanged} from "@/blockchain/wallet.js"; import {ethers} from "ethers"; import {applyFills} from "@/blockchain/common.js"; -import {ref} from "vue"; export const socket = io(import.meta.env.VITE_WS_URL || undefined, {transports: ["websocket"]}) @@ -28,20 +27,16 @@ socket.on('welcome', async (data) => { }) socket.on('p', async (chainId, pool, price) => { + console.log('pool price from message', chainId, pool, price) const s = useStore() - if( s.chainId !== chainId ) + if( s.chainId.value !== chainId ) return - const prices = {} - prices[pool] = price - console.log('pool price from message', pool, typeof price, price) - const poolPrices = s.poolPrices - poolPrices[pool] = price - s.poolPrices = poolPrices + s.poolPrices[[chainId,pool]] = price }) socket.on('vb', async (chainId, vault, balances) => { const s = useStore() - if( s.chainId !== chainId ) + if( s.chainId.value !== chainId ) return console.log('vb', vault, balances) const vb = {} @@ -52,9 +47,9 @@ socket.on('vb', async (chainId, vault, balances) => { socket.on('vaults', (chainId, owner, vaults)=>{ const s = useStore() - if( s.chainId !== chainId || s.account !== owner ) - return console.log('vaults', vaults) + if( s.chainId.value !== chainId || s.account !== owner ) + return s.vaults = vaults if( vaults.length ) { const vault = vaults[0] @@ -65,14 +60,12 @@ socket.on('vaults', (chainId, owner, vaults)=>{ function handleOrderStatus(chainId, vault, orderIndex, status) { const s = useStore() - if( s.chainId !== chainId ) + if( s.chainId.value !== chainId ) return console.log('o', chainId, vault, orderIndex, status) - const orders = s.orders - if( !(vault in orders) ) - orders[vault] = {} - orders[vault][orderIndex] = status - s.orders = orders + if( !(vault in s.orders) ) + s.orders[vault] = {} + s.orders[vault][orderIndex] = status } socket.on('os', (chainId, vault, orders) => { @@ -85,19 +78,18 @@ socket.on( 'o', handleOrderStatus) socket.on( 'of', (chainId, vault, orderIndex, fills)=>{ const s = useStore() - if( s.chainId !== chainId ) + if( s.chainId.value !== chainId ) return console.log('of', chainId, vault, orderIndex, fills) - const orders = s.orders - if( !(vault in orders) ) { + if( !(vault in s.orders) ) { console.log('warning: got fill on an order in an unknown vault') return } - if( !(orderIndex in orders[vault]) ) { + if( !(orderIndex in s.orders[vault]) ) { console.log(`warning: orderIndex ${orderIndex} missing from vault ${vault}`) return } - const order = orders[vault][orderIndex] + const order = s.orders[vault][orderIndex] applyFills(order, fills) - orders[vault][orderIndex] = order + s.orders[vault][orderIndex] = order }) diff --git a/src/store/store.js b/src/store/store.js index 54ac958..0a673ec 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -4,11 +4,33 @@ import {knownTokens} from "@/knownTokens.js"; import {computed, ref} from "vue"; +// USING THE STORE: +// +// +// When defining the store, use Vue script setup syntax, which requires assignment to .value: +// +// defineStore('foostore', ()=>{ +// const foo = ref(true) +// const obj = reactive({}) +// function reset() { +// obj.value = {} +// foo.value = false +// } +// }) +// +// +// Then use the store in components: +// +// const s = useStore() +// if( s.foo ) // store variables may be read simply +// s.reset() // store actions (functions) may be called directly +// const stuff = [ {}, [], true ] +// if( s.obj.value in stuff || s.obj.value === {} ) ... // use .value to access the raw object instead of its reference + + export const useStore = defineStore('app', ()=> { const _chainId = ref(null) const _chainInfo = ref({}) - const tokenA = ref(null) - const tokenB = ref(null) function getTokenList() { const chains = _chainId.value in _chainInfo.value && _chainInfo.value[_chainId.value].tokens !== undefined ? @@ -26,20 +48,16 @@ export const useStore = defineStore('app', ()=> { 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] + function refreshChain() { + useOrderStore().setDefaultTokens(getTokenList()) } const chainId = computed({ get() {return _chainId}, - set(v) {_chainId.value=v; setDefaultTokens()} + set(v) {_chainId.value=v; refreshChain()} }) const chainInfo = computed({ get() {return _chainInfo}, - set(v) {_chainInfo.value=v; setDefaultTokens()} + set(v) {_chainInfo.value=v; refreshChain()} }) const chain = computed(() => !_chainId.value ? null : (_chainInfo.value[_chainId.value] || null)) // making the provider directly reactive causes exceptions (calling private method...) when calling provider @@ -56,42 +74,16 @@ export const useStore = defineStore('app', ()=> { const transactionSenders = ref([]) // a list of function(signer) that send transactions const errors = ref([]) const extraTokens = ref({}) - const poolPrices = ref({}) + const poolPrices = ref({}) // keyed by [chainId,addr] 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 - // 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) @@ -117,9 +109,58 @@ export const useStore = defineStore('app', ()=> { 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, + poolPrices, vaultBalances, orders, vault, tokens, factory, helper, mockenv, mockCoins, removeTransactionSender, + error, closeError, addToken, } }) + + +export const useOrderStore = defineStore('order', ()=> { + const tokenA = ref(null) + const tokenB = ref(null) + + // 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 adjust default + const amountIsTokenA = ref(false) // todo adjust default + const amountIsTotal = ref(true) + const limitPrice = ref(null) + const limitPrice2 = ref(null) + const tranches = ref(3) + const interval = ref(1) + const intervalIsTotal = ref(true) + const timeUnitIndex = ref(0) + const routes = ref([]) + const routesPending = ref(false) + + const validOrder = computed(() => amount.value > 0 && routes.value.length > 0) + 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 setDefaultTokens(tokens) { + if( tokens.length > 0 ) + tokenA.value = tokens[0] + if( tokens.length > 1 ) + tokenB.value = tokens[1] + } + + return { + tokenA, tokenB, buy, inverted, amount, amountIsTokenA, amountIsTotal, limitPrice, limitPrice2, tranches, + interval, intervalIsTotal, timeUnitIndex, routes, routesPending, validOrder, route, base, quote, pairSymbol, + limitIsMinimum, amountToken, amountIsInput, setDefaultTokens, + } +}) \ No newline at end of file diff --git a/src/styles/style.scss b/src/styles/style.scss index 58bde60..cdf874b 100644 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -37,6 +37,11 @@ text-align: end; } + .v-input { + margin-top: 1em; + margin-bottom: 1em; + } + .maxw { max-width: v.$card-maxw; } diff --git a/src/validate.js b/src/validate.js new file mode 100644 index 0000000..3b3c983 --- /dev/null +++ b/src/validate.js @@ -0,0 +1,44 @@ +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 +} + +export function validateMax(v) { + if (!isEmpty(minPrice.value) && !isEmpty(v) && parseFloat(v) < parseFloat(minPrice.value)) + return 'Must be greater than the minimum price' + return true +} + +export function validateMin(v) { + if (!isEmpty(maxPrice.value) && !isEmpty(v) && parseFloat(v) > parseFloat(maxPrice.value)) + return 'Must be less than the maximum price' + return true +} + +export function validateTranches(v) { + const i = parseInt(v) + if (parseFloat(v) !== i) + return 'Whole numbers only' + if (i < 1) + return 'Must have at least one tranche' + if (i > 255) + return 'Maximum 255 tranches' + return true +} + diff --git a/src/views/Home.vue b/src/views/Home.vue index 8e65cfd..7399f2a 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -4,6 +4,6 @@ diff --git a/src/views/TwapView.vue b/src/views/TwapView.vue index 4124a91..48f66ba 100644 --- a/src/views/TwapView.vue +++ b/src/views/TwapView.vue @@ -3,7 +3,7 @@