-
Searching for {{s.pairSymbol}} pools...
+
+
+ Searching for {{ os.pairSymbol }} pools...
-
+
+
+
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 @@
-
-
-
- DCA / TWAP
- Multiple tranches over a time range
-
-
-
-
-
- tranches
-
-
-
-
-
-
-
-
-
-
-
-
- {{s.pairSymbol}}
-
-
-
-
- Current price
-
-
-
-
-
-
-
-
- Cancel
- Place Dexorder
-
-
-
-
-
-
-
-
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 @@
+
+
+ tranches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@