Files
web/src/components/Orders.vue
2024-03-28 15:46:34 -04:00

319 lines
12 KiB
Vue

<template>
<div>
<v-data-table :headers="datatableHeaders" :items="orders" item-value="index"
item-selectable="selectable" :show-select="false" :show-expand="true">
<template v-slot:item.start="{ value }">{{ dateString(value) }}</template>
<template v-slot:item.input="{ item }">
<suspense v-if="item.order.amountIsInput">
<span>
<token-amount :addr="item.order.tokenIn" :amount="item.filled" :raw="true"/>
/
<token-amount :addr="item.order.tokenIn" :amount="item.order.amount"/>
</span>
</suspense>
<suspense v-if="!item.order.amountIsInput">
<token-symbol :addr="item.order.tokenIn"/>
</suspense>
</template>
<template v-slot:item.output="{ item }">
<suspense v-if="!item.order.amountIsInput">
<span>
<token-amount :addr="item.order.tokenOut" :amount="item.filled" :raw="true"/>
/
<token-amount :addr="item.order.tokenOut" :amount="item.order.amount"/>
</span>
</suspense>
<suspense v-if="item.order.amountIsInput">
<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 }">
<pair-price :base-addr="item.order.tokenIn" :quote-addr="item.order.tokenOut" :value="item.avg" :show-btn="true"/>
</template>
<template v-slot:item.status="{ item }">
<v-chip v-if="item.state===PendingOrderState.Submitted || item.state===PendingOrderState.Signing"
prepend-icon="mdi-signature">Wallet Signing</v-chip>
<v-chip v-if="item.state===PendingOrderState.Rejected" prepend-icon="mdi-cancel">Rejected</v-chip>
<v-chip v-if="item.state===PendingOrderState.Sent" prepend-icon="mdi-send">Sent</v-chip>
<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">Expired
</v-chip>
<v-chip v-if="item.state===OrderState.Underfunded" prepend-icon="mdi-alert" color="warning">Underfunded</v-chip>
</template>
<!-- <template v-slot:item.action="{item}">-->
<!-- <btn v-if="item.state===OrderState.Open" icon="mdi-cancel" color="red"-->
<!-- @click="cancelOrder(vaultAddr,item.index)">Cancel-->
<!-- </btn>-->
<!-- </template>-->
<template v-slot:expanded-row="{item}">
<tr>
<td>&nbsp;</td>
<td colspan="100">
<v-table>
<!--
<thead>
<tr>
<th></th>
<th>Filled</th>
<th></th>
</tr>
</thead>
-->
<tbody>
<tr v-for="(t, i) in item.order.tranches">
<td class="text-right">Tranche {{ i + 1 }}</td>
<td class="text-center w-33">
<suspense v-if="item.state > OrderState.Signing">
<span>
<token-amount :addr="item.amountToken" :amount="item.trancheFilled[i]" :raw="true"/>
/
</span>
</suspense>
<suspense>
<span>
<token-amount :addr="item.amountToken" :amount="item.order.amount*BigInt(t.fraction)/65535n"/>
</span>
</suspense>
</td>
<td class="text-left">
<span class="mx-3">{{ describeTrancheTime(item, true, t) }}</span>
<span class="mx-3">{{ describeTrancheTime(item, false, t) }}</span>
<span class="mx-3">{{ describeTrancheLine(item, true, t.minIntercept, t.minSlope) }}</span>
<span class="mx-3">{{ describeTrancheLine(item, false, t.maxIntercept, t.maxSlope) }}</span>
</td>
</tr>
</tbody>
</v-table>
</td>
</tr>
</template>
</v-data-table>
<btn icon="mdi-delete-alert" color="red" class="mx-3 mb-3" @click="cancelAll(vaultAddr)">Cancel All Orders</btn>
</div>
</template>
<script setup>
import {FixedNumber} from "ethers";
import {useStore} from "@/store/store";
import {computed, defineAsyncComponent, ref} from "vue";
import Btn from "@/components/Btn.vue"
import {cancelAll, cancelOrder, PendingOrderState, useWalletStore} from "@/blockchain/wallet.js";
import {dateString, pairPriceAddr} from "@/misc.js";
import {isOpen, OrderState} from "@/blockchain/orderlib.js";
import NewOrder from "@/components/NewOrder.vue";
import PairPrice from "@/components/PairPrice.vue";
const TokenAmount = defineAsyncComponent(()=>import('./TokenAmount.vue'))
const TokenSymbol = defineAsyncComponent(()=>import('./TokenSymbol.vue'))
const s = useStore()
const ws = useWalletStore()
const props = defineProps(['vault'])
const vaultAddr = computed(()=>props.vault?props.vault:s.vault)
const inverted = ref({})
// <thead>
// <tr>
// <th className="num d-none d-md-table-cell">Date</th>
// <th className="token d-none d-sm-table-cell">Input</th>
// <th className="token d-none d-sm-table-cell">Output</th>
// <th className="amount">Remaining</th>
// <th className="amount">Filled</th>
// <th className="amount d-none d-md-table-cell">Avg&nbsp;Price</th>
// <th className="status">Status</th>
// <th className="cancel d-none d-lg-table-cell">&nbsp;</th>
// </tr>
// </thead>
const datatableHeaders = [
{title: 'Date', align: 'start', key: 'start'},
{title: 'Input', align: 'end', key: 'input'},
{title: 'Output', align: 'end', key: 'output'},
// {title: 'Remaining', align: 'end', key: 'remaining'},
// {title: 'Filled', align: 'end', key: 'filled'},
{title: 'Avg Price', align: 'end', key: 'avg'},
{title: 'Status', align: 'end', key: 'status'},
// {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
// false, reserved
// false, reserved
// false, reserved
// 0, reserved
// 0, reserved
// 0, start time
// 20, end time
// 730643660, min intercept
// 0, min slope
// 0, max intercept
// 0 max slope
// ],
// [...],
// [...],
// ]
// ],
// 4, state
// 1701809053, started at
// null, oco group
// "0", filled in
// "0", filled out
// ["0", "0", "0"], tranche filled in
// ["0", "0", "0"] tranche filled out
// ]
const result = []
// in-flight orders
for (const pend of ws.pendingOrders) {
console.log('pended order', pend)
result.push({
start: pend.placementTime,
order: pend.order,
filled: 0,
state: pend.state,
amountToken: pend.order.amountIsInput ? pend.order.tokenIn : pend.order.tokenOut
})
}
// historical orders
if( vaultAddr.value in s.orders ) {
for (const [index, status] of Object.entries(s.orders[vaultAddr.value]).reverse()) {
const st = {...status}
const o = {...st.order}
st.order = o
console.log('order status', st)
result.push(st)
st.index = parseInt(index)
o.tranches = o.tranches.map((tranche)=>{
const t = {...tranche}
// t.startTime = t.startTimeIsRelative ? intervalString(t.startTime) : dateString(t.startTime)
// t.endTime = t.endTimeIsRelative ? intervalString(t.endTime) : dateString(t.endTime)
return t
})
const fmtX18 = {decimals: 18, width: 256, signed: false};
st.filled = o.amountIsInput ? st.filledIn : st.filledOut
if(BigInt(st.filled) === 0n)
st.avg = null
else {
st.avg = FixedNumber.fromValue(status.filledOut, 0, fmtX18)
.div(FixedNumber.fromValue(status.filledIn, 0, fmtX18)).toUnsafeFloat();
if( o.tokenIn > o.tokenOut )
st.avg = 1/st.avg
}
st.trancheFilled = o.amountIsInput ? st.trancheFilledIn : st.trancheFilledOut
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
st.status = st.state
}
}
console.log('orders', result)
return result
})
function describeTrancheTime(st, isStart, t) {
let result = ''
if( t.minIsBarrier || t.maxIsBarrier )
return 'barrier'
if( isStart && t.startTime > 0 ) {
const started = t.startTimeIsRelative ? st.start + t.startTime : t.startTime
result += started*1000 < Date.now() ? 'Activated ' : 'Activates '
result += dateString(started) + ' '
}
if( !isStart && t.endTime < 4294967295 ) {
const ended = t.endTimeIsRelative ? st.start + t.endTime : t.endTime
result += ended*1000 < Date.now() ? 'Expired ' : 'Expires '
result += dateString(ended)
}
return result
}
function describeTrancheLine(st, isMin, b, m) {
// todo make this a PairPrice
if( b===0 && m===0 ) return ''
const chainId = useStore().chainId
console.log('tranche line', isMin, b, m)
const inverted = st.order.tokenIn > st.order.tokenOut
const t0 = inverted ? st.order.tokenIn : st.order.tokenOut
const t1 = !inverted ? st.order.tokenIn : st.order.tokenOut
if( m !== 0 ) {
const limit = b + m * s.clock
const price = pairPriceAddr(chainId, t0, t1, limit);
return 'diagonal ' + (price === null ? null : price.toPrecision(5))
}
const price = pairPriceAddr(chainId, t0, t1, b)
return (isMin === st.order.amountIsInput ? 'dont-chase ' : 'limit ') + (price === null ? null : price.toPrecision(5))
}
</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;
}
.status {
max-width: 8em;
}
.cancel {
}
</style>