427 lines
15 KiB
Vue
427 lines
15 KiB
Vue
<template>
|
|
<div>
|
|
<v-data-table :headers="datatableHeaders" :items="orders" item-value="id"
|
|
:show-select="true" :show-expand="true" v-model="selected" select-strategy="single">
|
|
<template v-slot:item.startTime="{ value }">{{ timestampString(value) }}</template>
|
|
<template v-slot:item.input="{ item }">
|
|
<span v-if="item.order.amountIsInput">
|
|
<span v-if="item.state > OrderState.Signing">
|
|
<suspense>
|
|
<pulse :touch="item.filledIn">
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenIn" :amount="item.filledIn" :raw="true"/>
|
|
</pulse>
|
|
</suspense>
|
|
/
|
|
</span>
|
|
<suspense>
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenIn" :amount="item.order.amount" :raw="true"/>
|
|
</suspense>
|
|
</span>
|
|
<span v-else>
|
|
<suspense>
|
|
<pulse :touch="item.filledIn">
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenIn" :amount="item.filledIn" :raw="true"/>
|
|
</pulse>
|
|
</suspense>
|
|
</span>
|
|
<suspense>
|
|
<token-symbol :addr="item.order.tokenIn"/>
|
|
</suspense>
|
|
</template>
|
|
<template v-slot:item.output="{ item }">
|
|
<span v-if="!item.order.amountIsInput">
|
|
<span v-if="item.state > OrderState.Signing">
|
|
<suspense>
|
|
<pulse :touch="item.filledOut">
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenOut" :amount="item.filledOut" :raw="true"/>
|
|
</pulse>
|
|
</suspense>
|
|
/
|
|
</span>
|
|
<suspense>
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenOut" :amount="item.order.amount" :raw="true"/>
|
|
</suspense>
|
|
</span>
|
|
<span v-else>
|
|
<suspense>
|
|
<pulse :touch="item.filledOut">
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenOut" :amount="item.filledOut" :raw="true"/>
|
|
</pulse>
|
|
</suspense>
|
|
</span>
|
|
<suspense>
|
|
<token-symbol :addr="item.order.tokenOut"/>
|
|
</suspense>
|
|
</template>
|
|
<!--
|
|
<template v-slot:item.remaining="{ item }">
|
|
<suspense>
|
|
<token-amount :addr="item.amountToken" :amount="item.remaining"/>
|
|
</suspense>
|
|
</template>
|
|
<template v-slot:item.filled="{ item }">
|
|
<suspense>
|
|
<token-amount :addr="item.amountToken" :amount="item.filled"/>
|
|
</suspense>
|
|
</template>
|
|
-->
|
|
<template v-slot:item.avg="{ item }">
|
|
<suspense>
|
|
<pair-price :base="item.order.tokenIn" :quote="item.order.tokenOut" :value="item.avg" :show-btn="true"/>
|
|
</suspense>
|
|
</template>
|
|
<template v-slot:item.state="{ item }">
|
|
<v-chip v-if="item.state===OrderState.Open" class="d-none d-lg-inline-flex" prepend-icon="mdi-dots-horizontal"
|
|
color="green">Open
|
|
</v-chip>
|
|
<btn v-if="isOpen(item.state)" class="d-none d-sm-inline-flex" icon="mdi-cancel" color="red"
|
|
@click="cancelOrder(vaultAddr,item.index)">Cancel
|
|
</btn>
|
|
<btn v-if="isOpen(item.state)" class="d-sm-none" variant="plain" icon="mdi-cancel" color="red"
|
|
@click="cancelOrder(vaultAddr,item.index)"/>
|
|
<v-chip v-if="item.state===OrderState.Canceled" prepend-icon="mdi-cancel" color="red">Canceled</v-chip>
|
|
<v-chip v-if="item.state===OrderState.Filled" prepend-icon="mdi-check-circle-outline" color="green">Completed
|
|
</v-chip>
|
|
<v-chip v-if="item.state===OrderState.Expired" prepend-icon="mdi-progress-check" color="grey-darken-1">Partial
|
|
</v-chip>
|
|
<v-chip v-if="item.state===OrderState.Underfunded" prepend-icon="mdi-alert" color="warning">Underfunded
|
|
<v-tooltip :text="`This order is underfunded. Add more ${item.inSymbol} to your vault.`" location="top" activator="parent"></v-tooltip>
|
|
</v-chip>
|
|
<v-chip v-if="item.state===OrderState.Error" prepend-icon="mdi-alert" color="error">Error</v-chip>
|
|
</template>
|
|
<template v-slot:expanded-row="{item}">
|
|
<template v-for="(t, i) in item.order.tranches">
|
|
<tr>
|
|
<td class="text-right" colspan="2">
|
|
<div class="text-right">
|
|
<div v-if="s.clock < item.trancheStatus[i].startTime">
|
|
Activates {{ timestampString(item.trancheStatus[i].startTime) }}
|
|
</div>
|
|
<div v-if="s.clock < item.trancheStatus[i].endTime && item.trancheStatus[i].endTime < DISTANT_FUTURE">
|
|
Expires {{ timestampString(item.trancheStatus[i].endTime) }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="mx-3" v-if="t.marketOrder">market order</div>
|
|
<line-price class="mx-3" v-if="!t.marketOrder"
|
|
:base="item.base" :quote="item.quote"
|
|
:b="t.minLine.intercept" :m="t.minLine.slope" :is-breakout="!item.minIsLimit"
|
|
:show-btn="true"/>
|
|
<line-price class="mx-3" v-if="!t.marketOrder"
|
|
:base="item.base" :quote="item.quote"
|
|
:b="t.maxLine.intercept" :m="t.maxLine.slope" :is-breakout="item.minIsLimit"
|
|
:show-btn="true"/>
|
|
</div>
|
|
|
|
</td>
|
|
<td class="text-right">
|
|
<suspense>
|
|
<span v-if="item.state > OrderState.Signing">
|
|
<pulse :touch="item.trancheStatus[i].filledIn">
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenIn"
|
|
:amount="item.trancheStatus[i].filledIn" :raw="item.order.amountIsInput"/>
|
|
</pulse>
|
|
</span>
|
|
</suspense>
|
|
<suspense>
|
|
<span v-if="item.order.amountIsInput">
|
|
/
|
|
<token-amount :chain-id="item.chainId" :addr="item.amountToken"
|
|
:amount="item.order.amount*BigInt(t.fraction)/65535n"/>
|
|
</span>
|
|
</suspense>
|
|
</td>
|
|
<td class="text-right w-33">
|
|
<suspense>
|
|
<span v-if="item.state > OrderState.Signing">
|
|
<pulse :touch="item.trancheStatus[i].filledOut">
|
|
<token-amount :chain-id="item.chainId" :addr="item.order.tokenOut"
|
|
:amount="item.trancheStatus[i].filledOut" :raw="!item.order.amountIsInput"/>
|
|
</pulse>
|
|
</span>
|
|
</suspense>
|
|
<suspense>
|
|
<span v-if="!item.order.amountIsInput">
|
|
/
|
|
<token-amount :chain-id="item.chainId" :addr="item.amountToken"
|
|
:amount="item.order.amount*BigInt(t.fraction)/65535n"/>
|
|
</span>
|
|
</suspense>
|
|
</td>
|
|
<td class="text-right">
|
|
<suspense>
|
|
<pair-price :base="item.order.tokenIn" :quote="item.order.tokenOut" :value="item.trancheStatus[i].avg"
|
|
:show-btn="false"/>
|
|
</suspense>
|
|
</td>
|
|
<td>{{ item.trancheStatus[i].status }}<!--todo:tranche status--></td>
|
|
</tr>
|
|
<tr>
|
|
</tr>
|
|
</template>
|
|
</template>
|
|
</v-data-table>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import LinePrice from "@/components/LinePrice.vue";
|
|
import {useStore} from "@/store/store";
|
|
import {computed, defineAsyncComponent, onUnmounted, ref, watch} from "vue";
|
|
import Btn from "@/components/Btn.vue"
|
|
import {cancelOrder, useWalletStore} from "@/blockchain/wallet.js";
|
|
import {DISTANT_FUTURE, isOpen, OrderState} from "@/blockchain/orderlib.js";
|
|
import Pulse from "@/components/Pulse.vue";
|
|
import {OrderShapes} from "@/charts/ordershapes.js";
|
|
import {useChartOrderStore} from "@/orderbuild.js";
|
|
import {lookupSymbol, tickerForOrder} from "@/charts/datafeed.js";
|
|
import {setSymbol} from "@/charts/chart.js";
|
|
import {uniswapV3AveragePrice} from "@/blockchain/uniswap.js";
|
|
import {timestampString} from "@/misc.js";
|
|
import {metadataMap} from "@/version.js";
|
|
|
|
const PairPrice = defineAsyncComponent(()=>import("@/components/PairPrice.vue"))
|
|
const TokenAmount = defineAsyncComponent(()=>import('./TokenAmount.vue'))
|
|
const TokenSymbol = defineAsyncComponent(()=>import('./TokenSymbol.vue'))
|
|
|
|
const s = useStore()
|
|
const co = useChartOrderStore()
|
|
const ws = useWalletStore()
|
|
const props = defineProps(['vault'])
|
|
const vaultAddr = computed(()=>props.vault?props.vault:s.vault)
|
|
const selected = ref([])
|
|
|
|
// Draw Shapes
|
|
watch(selected, async ()=>{
|
|
const statusIndex = {}
|
|
for (const order of orders.value)
|
|
statusIndex[order.id] = order
|
|
const selectedIndex = {}
|
|
const chainId = s.chainId
|
|
for (const id of selected.value) {
|
|
selectedIndex[id] = true
|
|
const status = statusIndex[id];
|
|
if (!(id in orderShapes)) {
|
|
const ticker = tickerForOrder(chainId, status.order)
|
|
const symbol = lookupSymbol(ticker)
|
|
if (co.selectedSymbol.ticker !== ticker) {
|
|
// switch symbol
|
|
deleteOrderShapes()
|
|
await setSymbol(symbol)
|
|
}
|
|
orderShapes[id] = new OrderShapes(symbol, status)
|
|
}
|
|
}
|
|
deleteOrderShapes(selectedIndex)
|
|
})
|
|
|
|
function deleteOrderShapes(keepSet={}) {
|
|
// now delete old shapes
|
|
const keys = [...Object.keys(orderShapes)]
|
|
for (const id of keys) {
|
|
if (!(id in keepSet)) {
|
|
orderShapes[id].delete()
|
|
delete orderShapes[id]
|
|
}
|
|
}
|
|
}
|
|
|
|
const orderShapes = {}
|
|
|
|
onUnmounted(()=>{
|
|
for (const s of Object.values(orderShapes))
|
|
s.delete()
|
|
})
|
|
|
|
const datatableHeaders = [
|
|
{title: 'Date', align: 'start', key: 'startTime'},
|
|
{title: 'Input', align: 'end', key: 'input'},
|
|
{title: 'Output', align: 'end', key: 'output'},
|
|
{title: 'Avg Price', align: 'end', key: 'avg'},
|
|
{title: 'Status', align: 'end', key: 'state'},
|
|
// {title: '', align: 'end', key: 'action'},
|
|
];
|
|
|
|
const orders = computed(()=>{
|
|
// example twap
|
|
// status = [
|
|
// [ order
|
|
// "0x52412507302F6bAB17f56370b6a8F304CbB30Ce1", in token
|
|
// "0x63e187162a4c33A4D14465eA3859fFe423647710", out token
|
|
// [1, 500], route
|
|
// "100000000", amount
|
|
// "1000000", min amount
|
|
// false, amount is input
|
|
// false, output to owner
|
|
// 18446744073709552000, chain order
|
|
// [ tranches
|
|
// [21845, fraction
|
|
// true, start time relative
|
|
// true, end time relative
|
|
// false, min is barrier
|
|
// false, max is barrier
|
|
// false, market order
|
|
// 0, minIsRatio
|
|
// 0, maxIsRatio
|
|
// false, _reserved7
|
|
// 0, rateLimitFraction
|
|
// 0, rateLimitPeriod
|
|
// 0, start time
|
|
// 20, end time
|
|
// [730643660, 0], [min intercept, slope]
|
|
// [0, 0], [max intercept, slope]
|
|
// ],
|
|
// ]
|
|
// ],
|
|
// 4, state
|
|
// 1701809053, started at
|
|
// null, oco group
|
|
// "0", filled in
|
|
// "0", filled out
|
|
// [...], trancheStatus
|
|
// ]
|
|
const result = []
|
|
|
|
// in-flight orders
|
|
for (const pend of ws.pendingOrders) {
|
|
console.log('pended order', pend)
|
|
result.push({
|
|
chainId: pend.chainId,
|
|
id: pend.id,
|
|
startTime: pend.placementTime,
|
|
order: pend.order,
|
|
filled: 0,
|
|
state: pend.state,
|
|
amountToken: pend.order.amountIsInput ? pend.order.tokenIn : pend.order.tokenOut
|
|
})
|
|
}
|
|
|
|
// historical orders
|
|
const vault = vaultAddr.value;
|
|
if( vault in s.orders ) {
|
|
for (const [index, status] of Object.entries(s.orders[vault]).reverse()) {
|
|
// const st = {...status}
|
|
// const o = {...st.order}
|
|
const st = status
|
|
const o = st.order
|
|
st.order = o
|
|
// console.log('order status', st)
|
|
result.push(st)
|
|
st.id = `${vault}|${index}`
|
|
st.index = parseInt(index)
|
|
// st.startTime = timestampString(st.startTime)
|
|
/*
|
|
o.tranches = o.tranches.map((tranche)=>{
|
|
const t = {...tranche}
|
|
// t.startTime = t.startTimeIsRelative ? intervalString(t.startTime) : timestampString(t.startTime)
|
|
// t.endTime = t.endTimeIsRelative ? intervalString(t.endTime) : timestampString(t.endTime)
|
|
return t
|
|
})
|
|
*/
|
|
st.filled = o.amountIsInput ? st.filledIn : st.filledOut
|
|
const fee = st.order.route.fee;
|
|
if(st.filled === 0n)
|
|
st.avg = null
|
|
else
|
|
st.avg = uniswapV3AveragePrice(status.filledIn, status.filledOut, fee)
|
|
st.amountToken = o.amountIsInput ? o.tokenIn : o.tokenOut
|
|
st.input = o.amountIsInput ? o.amount : 0
|
|
st.output = !o.amountIsInput ? o.amount : 0
|
|
st.remaining = o.amount - st.filled
|
|
st.selectable = st.state===OrderState.Open
|
|
for( const ts of st.trancheStatus ) {
|
|
let filledIn = 0n
|
|
let filledOut = 0n
|
|
for( const f of ts.fills ) {
|
|
filledIn += f.filledIn
|
|
filledOut += f.filledOut
|
|
}
|
|
ts.filledIn = filledIn
|
|
ts.filledOut = filledOut
|
|
ts.filled = o.amountIsInput ? filledIn : filledOut
|
|
ts.avg = uniswapV3AveragePrice(filledIn, filledOut, fee)
|
|
}
|
|
}
|
|
}
|
|
// elaborate
|
|
for (const st of result) {
|
|
let low, high;
|
|
// console.log('elab', st.order)
|
|
const o = st.order;
|
|
const buy = o.tokenIn > o.tokenOut;
|
|
if (buy) {
|
|
low = o.tokenOut
|
|
high = o.tokenIn
|
|
}
|
|
else {
|
|
low = o.tokenIn
|
|
high = o.tokenOut
|
|
}
|
|
st.base = o.inverted ? high : low;
|
|
st.quote = o.inverted ? low : high;
|
|
st.minIsLimit = buy === o.inverted // whether limit/breakout is flipped
|
|
const found = metadataMap[st.chainId][o.tokenIn]
|
|
st.inSymbol = found ? found.s : o.tokenIn
|
|
// console.log('elaborated', st)
|
|
}
|
|
result.sort((a,b)=>b.startTime-a.startTime)
|
|
// console.log('orders', result)
|
|
for( const st of result ) {
|
|
if( st.id in orderShapes )
|
|
orderShapes[st.id].update(st)
|
|
}
|
|
return result
|
|
})
|
|
|
|
function describeTrancheTime(st, trancheIndex, isStart) {
|
|
const t = st.order.tranches[trancheIndex]
|
|
const ts = st.trancheStatus[trancheIndex]
|
|
console.log('describeTT', t, ts)
|
|
let result = ''
|
|
if( t.minIsBarrier || t.maxIsBarrier )
|
|
return 'barrier'
|
|
const now = s.clock
|
|
if( isStart && ts.activationTime > 0 ) {
|
|
const start = t.startTimeIsRelative ? st.startTime + t.startTime : t.startTime
|
|
result += now < start ? ts.activationTime < now ? 'Rate Limited ' : 'Activates ' : 'Activated '
|
|
result += timestampString(ts.activationTime) + ' '
|
|
}
|
|
if( !isStart && t.endTime < 4294967295 ) {
|
|
const ended = t.endTimeIsRelative ? st.startTime + t.endTime : t.endTime
|
|
result += ended < now ? 'Ended ' : 'Ending '
|
|
result += timestampString(ended)
|
|
}
|
|
return result
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
@use "src/styles/vars" as *;
|
|
|
|
// columns
|
|
.num {
|
|
min-width: 1em;
|
|
}
|
|
.token {
|
|
min-width: 3em;
|
|
max-width: 5em;
|
|
}
|
|
.pair {
|
|
min-width: 5em;
|
|
}
|
|
.amount {
|
|
min-width: 3em;
|
|
max-width: 6em;
|
|
overflow-x: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.state {
|
|
max-width: 8em;
|
|
}
|
|
.cancel {
|
|
}
|
|
|
|
</style>
|