diagonal line support

This commit is contained in:
Tim Olson
2023-12-19 17:07:08 -04:00
parent 9199d31e77
commit 8007f63469
10 changed files with 170 additions and 52 deletions

View File

@@ -4,31 +4,43 @@
<div class="title">Line Point A</div> <div class="title">Line Point A</div>
<v-divider/> <v-divider/>
<time-entry v-model="time1" class="mb-0" hide-details="true"/> <time-entry v-model="time1" class="mb-0" hide-details="true"/>
<limit-price v-model="price1" label="Price A" :required="true"/> <limit-price v-model="price1" label="Price A" :required="true" :show-price="false"/>
<div class="title">Line Point B</div> <div class="title">Line Point B</div>
<v-divider/> <v-divider/>
<time-entry v-model="time2" hide-details="true"/> <time-entry v-model="time2" hide-details="true"/>
<limit-price v-model="price2" label="Price B" :required="true"/> <limit-price v-model="price2" label="Price B" :required="true" :show-price="false"/>
<div><i>Backend support for diagonal lines is coming soon...</i></div> <div v-if="curLimit">
<v-row>
<v-col cols="6">
<span>Current line value</span>&nbsp;<span class="text-green">{{curLimit ? curLimit.toPrecision(5) : curLimit}}</span>
</v-col>
<v-col cols="6">
<span>Current price</span>&nbsp;<route-price class="text-green"/>
</v-col>
</v-row>
</div>
{{os.limitIsMinimum}}
</order> </order>
</template> </template>
<script setup> <script setup>
import {useOrderStore} from "@/store/store"; import {useOrderStore, useStore} from "@/store/store";
import LimitPrice from "@/components/LimitPrice.vue"; import LimitPrice from "@/components/LimitPrice.vue";
import Order from "@/components/Order.vue"; import Order from "@/components/Order.vue";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import {applyLimit, applyLinePoints} from "@/orderbuild.js"; import {applyLinePoints, linePointsValue} from "@/orderbuild.js";
import {validateRequired, validateTranches} from "@/validate.js"; import {newTranche} from "@/blockchain/orderlib.js";
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
import TimeEntry from "@/components/TimeEntry.vue"; import TimeEntry from "@/components/TimeEntry.vue";
import RoutePrice from "@/components/RoutePrice.vue";
const s = useStore()
const os = useOrderStore() const os = useOrderStore()
const time1 = ref(new Date()) const time1 = ref(new Date())
const price1 = ref(null) const price1 = ref(null)
const time2 = ref(new Date()) const time2 = ref(new Date())
const price2 = ref(null) const price2 = ref(null)
const curLimit = computed(()=>linePointsValue(time1.value, price1.value, time2.value, price2.value, s.time))
function buildTranches() { function buildTranches() {
const t = newTranche(); const t = newTranche();
@@ -37,7 +49,7 @@ function buildTranches() {
} }
function validOrder() { function validOrder() {
return false return time1.value && (price1.value || price1.value===0) && time2.value && (price2.value || price2.value===0)
} }
</script> </script>

View File

@@ -24,8 +24,8 @@
</suspense> </suspense>
</template> </template>
<template v-slot:item.avg="{ item }"> <template v-slot:item.avg="{ item }">
{{ pairPrice(item.order.tokenIn, item.order.tokenOut, vaultAddr, item.avg) }} {{ pairPrice(item.order.tokenIn, item.order.tokenOut, item.avg) }}
<btn v-if="pairPrice(item.order.tokenIn, item.order.tokenOut, vaultAddr, item.avg)!==''" size="small" <btn v-if="pairPrice(item.order.tokenIn, item.order.tokenOut, item.avg)!==''" size="small"
variant="plain" variant="plain"
@click="inverted[[vaultAddr,item.index]] = !inverted[[vaultAddr,item.index]]"> @click="inverted[[vaultAddr,item.index]] = !inverted[[vaultAddr,item.index]]">
{{ pair(item.order.tokenIn, item.order.tokenOut, vaultAddr, item.index) }} {{ pair(item.order.tokenIn, item.order.tokenOut, vaultAddr, item.index) }}
@@ -117,7 +117,7 @@ const inverted = reactive({})
// todo create a Price component that keeps inversion flags in the store and defaults to stablecoins as the quote // todo create a Price component that keeps inversion flags in the store and defaults to stablecoins as the quote
function pairPrice(inTokenAddr, outTokenAddr, vaultAddr, price) { function pairPrice(inTokenAddr, outTokenAddr, price) {
if( price === null ) if( price === null )
return '' return ''
const inToken = token(inTokenAddr) const inToken = token(inTokenAddr)
@@ -125,7 +125,7 @@ function pairPrice(inTokenAddr, outTokenAddr, vaultAddr, price) {
if( !inToken || !outToken ) if( !inToken || !outToken )
return '' return ''
const decimals = outToken.decimals-inToken.decimals const decimals = inToken.decimals-outToken.decimals
if( decimals > 0 ) if( decimals > 0 )
price /= 10 ** decimals price /= 10 ** decimals
else else
@@ -268,11 +268,14 @@ function describeTrancheTime(st, isStart, t) {
} }
function describeTrancheLine(st, isMin, b, m) { function describeTrancheLine(st, isMin, b, m) {
if( b===0 && m===0 ) return ''
// todo slopes
console.log('tranche line', isMin, b, m)
// todo make this a PairPrice // todo make this a PairPrice
return (isMin === st.order.amountIsInput ? 'dont-chase ' : 'limit ') + pairPrice(st.order.tokenIn, st.order.tokenOut, s.vault, b) if( b===0 && m===0 ) return ''
// console.log('tranche line', isMin, b, m)
if( m !== 0 ) {
const limit = b + m * s.time
return 'diagonal ' + pairPrice(st.order.tokenIn, st.order.tokenOut, limit)
}
return (isMin === st.order.amountIsInput ? 'dont-chase ' : 'limit ') + pairPrice(st.order.tokenIn, st.order.tokenOut, b)
} }
</script> </script>

View File

@@ -0,0 +1,45 @@
<template>
<span>{{adjValue}}</span>
<!-- todo optional pair label and inversion button -->
</template>
<script setup>
import {usePrefStore, useStore} from "@/store/store";
import {computed} from "vue";
import {token} from "@/blockchain/token.js";
const props = defineProps(['value','tokenA','tokenB','addrA','addrB',])
const s = useStore()
const prefs = usePrefStore()
const adjValue = computed(()=>{
const a = props.tokenA ? props.tokenA : token(s.chainId,props.addrA)
const b = props.tokenB ? props.tokenB : token(s.chainId,props.addrB)
if( !a || !b )
return ''
let price = props.value
const decimals = b.decimals-a.decimals
if( decimals > 0 )
price /= 10 ** decimals
else
price *= 10 ** -decimals
const token0 = a.address < b.address ? a.address : b.address
const token1 = a.address > b.address ? a.address : b.address
const invertedKey = [token0, token1];
if( !(invertedKey in prefs.inverted) ) {
// todo prefer stablecoins as the quote asset
prefs.inverted[invertedKey] = false
}
if( prefs.inverted[invertedKey] )
price = 1/price
return price.toPrecision(5)
})
</script>
<style scoped lang="scss">
@use "src/styles/vars" as *;
</style>

View File

@@ -3,24 +3,27 @@
</template> </template>
<script setup> <script setup>
import {useStore} from "@/store/store"; import {useOrderStore, useStore} from "@/store/store";
import {subPrices, unsubPrices, WIDE_PRICE_FORMAT} from "@/blockchain/prices.js"; import {subPrices, unsubPrices, WIDE_PRICE_FORMAT} from "@/blockchain/prices.js";
import {computed, onBeforeUnmount} from "vue"; import {computed, onBeforeUnmount} from "vue";
import {FixedNumber} from "ethers"; import {FixedNumber} from "ethers";
import {routeInverted} from "@/misc.js";
const s = useStore() const s = useStore()
const os = useOrderStore()
const props = defineProps({ const props = defineProps({
route: {type: Object, required:true}, route: {type: Object, required:false},
inverted: {type: Boolean, required:true}, inverted: {type: Boolean, required:false},
precision: {type: Number, default:5, required:false}, precision: {type: Number, default:5, required:false},
}) })
const route = computed(()=>props.route ? props.route : os.route)
const price = computed(()=>{ const price = computed(()=>{
const route = props.route; if( !route.value )
if( !route )
return '' return ''
const routeKey = [route.chainId, route.pool] const routeKey = [route.value.chainId, route.value.pool]
if( !(routeKey in s.poolPrices) ) if( !(routeKey in s.poolPrices) )
return '' return ''
let p = s.poolPrices[routeKey] let p = s.poolPrices[routeKey]
@@ -28,19 +31,20 @@ const price = computed(()=>{
if( !p ) if( !p )
return '' return ''
p = FixedNumber.fromString(p, WIDE_PRICE_FORMAT).toUnsafeFloat() p = FixedNumber.fromString(p, WIDE_PRICE_FORMAT).toUnsafeFloat()
if( props.inverted ) const inverted = props.inverted === undefined ? routeInverted(route.value) : props.inverted;
if( inverted )
p = 1/p p = 1/p
return p.toPrecision(props.precision) return p.toPrecision(props.precision)
}) })
if( props.route ) if( route.value )
subPrices([props.route]) subPrices([route.value])
else else
console.log('route is empty: no price') console.log('route is empty: no price')
onBeforeUnmount(() => { onBeforeUnmount(() => {
if( props.route ) if( route.value )
unsubPrices([props.route]) unsubPrices([route.value])
}) })

View File

@@ -30,7 +30,18 @@ const year = computed({
set(v) { update(v, month.value, day.value, time.value)}, set(v) { update(v, month.value, day.value, time.value)},
}) })
const time = computed({ const time = computed({
get() { return '' + (s.utc ? `${props.modelValue.getUTCHours()}:${props.modelValue.getUTCMinutes()}` : `${props.modelValue.getHours()}:${props.modelValue.getMinutes()}` ) }, get() {
let hour, min
if( s.utc ) {
hour = props.modelValue.getUTCHours()
min = props.modelValue.getUTCMinutes()
}
else {
hour = props.modelValue.getHours()
min = props.modelValue.getMinutes()
}
return hour.toString().padStart(2,'0') + ':' + min.toString().padStart(2,'0')
},
set(v) { update(year.value, month.value, day.value, v) } set(v) { update(year.value, month.value, day.value, v) }
}) })
@@ -45,7 +56,6 @@ const monthItems = monthsForLocale(undefined, 'short').map((v)=>{return {title:v
function buildDate(y, m, d, t) { function buildDate(y, m, d, t) {
const [hours,minutes] = t.split(':') const [hours,minutes] = t.split(':')
console.log('buildDate', y, m, d, hours, minutes)
return s.utc ? new Date(Date.UTC(y, m, d, hours, minutes)) return s.utc ? new Date(Date.UTC(y, m, d, hours, minutes))
: new Date(y, m, d, hours, minutes); : new Date(y, m, d, hours, minutes);
} }

View File

@@ -79,6 +79,8 @@ export function dateString(seconds) {
} }
export function timestamp(date) { export function timestamp(date=null) {
if(date===null)
date = new Date()
return Math.round(date.getTime() / 1000) return Math.round(date.getTime() / 1000)
} }

View File

@@ -4,7 +4,7 @@ import {useOrderStore} from "@/store/store.js";
import {encodeIEE754} from "@/blockchain/common.js"; import {encodeIEE754} from "@/blockchain/common.js";
export function applyLimit(tranche, price=null, isAbove=null) { export function applyLimit(tranche, price=null, isMinimum=null) {
if( price === null ) { if( price === null ) {
const os = useOrderStore() const os = useOrderStore()
price = os.limitPrice price = os.limitPrice
@@ -12,38 +12,62 @@ export function applyLimit(tranche, price=null, isAbove=null) {
return return
} }
applyLine(tranche, price, 0, isAbove) applyLine(tranche, price, 0, isMinimum)
} }
export function applyLinePoints(tranche, date0, price0, date1, price1, isAbove=null) { function computeInterceptSlope(date0, price0, date1, price1) {
const os = useOrderStore() if (!date0 || !price0 && price0 !== 0 || !date1 || !price1 && price1 !== 0)
if( !date0 || !price0 && price0!==0 || !date1 || !price1 && price1!==0 ) throw Error(`invalid line points data ${date0} ${price0} ${date1} ${price1}`)
return
const t0 = timestamp(date0); const t0 = timestamp(date0);
const t1 = timestamp(date1); const t1 = timestamp(date1);
const slope = (price1-price0)/(t1-t0) if (t0 === t1)
throw Error("line points' times must be different")
const slope = (price1 - price0) / (t1 - t0)
const intercept = price1 - slope * t1 const intercept = price1 - slope * t1
applyLine(tranche, intercept, slope, isAbove) return [intercept, slope]
} }
export function applyLine(tranche, intercept, slope, isAbove=null) { export function linePointsValue(date0, price0, date1, price1, unixTime=null) {
if(unixTime===null)
unixTime = timestamp()
try {
const [intercept, slope] = computeInterceptSlope(date0, price0, date1, price1)
return intercept + unixTime * slope
}
catch (e) {
return null
}
}
export function applyLinePoints(tranche, date0, price0, date1, price1, isMinimum=null) {
const [intercept, slope] = computeInterceptSlope(date0, price0, date1, price1);
applyLine(tranche, intercept, slope, isMinimum)
}
export function applyLine(tranche, intercept, slope, isMinimum=null) {
console.log('intercept, slope', intercept, slope)
// intercept and slope are still in "human" units of decimal-adjusted prices // intercept and slope are still in "human" units of decimal-adjusted prices
const os = useOrderStore() const os = useOrderStore()
const route = os.route const route = os.route
const inverted = routeInverted(route) const inverted = routeInverted(route)
const scale = 10 ** (os.tokenA.decimals - os.tokenB.decimals) const scale = 10 ** (os.tokenA.decimals - os.tokenB.decimals)
const m = encodeIEE754(inverted ? scale / slope : slope / scale) let m = inverted ? -scale / slope : slope / scale
const b = encodeIEE754(inverted ? scale / intercept : intercept / scale) let b = inverted ? scale / intercept : intercept / scale
if( isAbove === null ) const cur = b + timestamp() * m
isAbove = os.limitIsMinimum ^ inverted console.log('inverted b, m, cur', inverted, b, m, cur)
if( isAbove ) { m = encodeIEE754(m)
b = encodeIEE754(b)
if( isMinimum === null )
isMinimum = os.limitIsMinimum
console.log('limit is minimum', isMinimum)
if (isMinimum) {
tranche.minIntercept = b; tranche.minIntercept = b;
tranche.minSlope = m; tranche.minSlope = m;
} } else {
else {
tranche.maxIntercept = b; tranche.maxIntercept = b;
tranche.maxSlope = m; tranche.maxSlope = m;
} }

View File

@@ -39,10 +39,8 @@ socket.on('vb', async (chainId, vault, balances) => {
if( s.chainId.value !== chainId ) if( s.chainId.value !== chainId )
return return
console.log('vb', vault, balances) console.log('vb', vault, balances)
const vb = {} s.vaultBalances[vault] = JSON.parse(balances)
vb[vault] = JSON.parse(balances) console.log('vault balances', vault, s.vaultBalances[vault])
s.$patch({vaultBalances:vb})
console.log('vault balances', vault, vb)
}) })
socket.on('vaults', (chainId, owner, vaults)=>{ socket.on('vaults', (chainId, owner, vaults)=>{

View File

@@ -2,6 +2,7 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import {knownTokens} from "@/knownTokens.js"; import {knownTokens} from "@/knownTokens.js";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import {timestamp} from "@/misc.js";
// USING THE STORE: // USING THE STORE:
@@ -29,6 +30,14 @@ import {computed, ref} from "vue";
export const useStore = defineStore('app', ()=> { export const useStore = defineStore('app', ()=> {
const time = ref(timestamp())
console.log('starting clock')
setInterval(()=>{
const now = timestamp();
// console.log('clock', now)
time.value= now
}, 10*1000)
const nav = ref(false) // controls opening navigation drawer const nav = ref(false) // controls opening navigation drawer
const _chainId = ref(null) const _chainId = ref(null)
@@ -111,7 +120,7 @@ export const useStore = defineStore('app', ()=> {
return { return {
nav, chainId, chainInfo, chain, provider, vaultInitCodeHash, account, vaults, transactionSenders, errors, nav, chainId, chainInfo, chain, provider, vaultInitCodeHash, account, vaults, transactionSenders, errors,
extraTokens, poolPrices, vaultBalances, orders, vault, tokens, factory, helper, mockenv, mockCoins, extraTokens, poolPrices, vaultBalances, orders, vault, tokens, factory, helper, mockenv, mockCoins,
removeTransactionSender, error, closeError, addToken, removeTransactionSender, error, closeError, addToken, time,
} }
}) })
@@ -168,3 +177,12 @@ export const useOrderStore = defineStore('order', ()=> {
limitIsMinimum, amountToken, amountIsInput, setDefaultTokens, totalAmount, trancheAmount, utc, limitIsMinimum, amountToken, amountIsInput, setDefaultTokens, totalAmount, trancheAmount, utc,
} }
}) })
export const usePrefStore = defineStore('order', ()=> {
// user preferences
const inverted = ref({})
return {inverted,}
})

View File

@@ -2,6 +2,7 @@
<!-- todo needs account --> <!-- todo needs account -->
<needs-signer> <needs-signer>
<vault :owner="s.account" :num="0"/> <vault :owner="s.account" :num="0"/>
<new-order class="my-6"/>
<faucet class="mt-3"/> <faucet class="mt-3"/>
</needs-signer> </needs-signer>
</template> </template>
@@ -12,6 +13,7 @@ import Vault from "@/components/Vault.vue";
import NeedsSigner from "@/components/NeedsSigner.vue"; import NeedsSigner from "@/components/NeedsSigner.vue";
import Faucet from "@/components/Faucet.vue"; import Faucet from "@/components/Faucet.vue";
import NeedsProvider from "@/components/NeedsProvider.vue"; import NeedsProvider from "@/components/NeedsProvider.vue";
import NewOrder from "@/components/NewOrder.vue";
const s = useStore() const s = useStore()