order status updates working

This commit is contained in:
Tim Olson
2023-11-08 23:18:36 -04:00
parent 077e664a30
commit f1bc632074
14 changed files with 259 additions and 21 deletions

View File

@@ -2,6 +2,7 @@
"name": "dexorder",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",

11
src/blockchain/common.js Normal file
View File

@@ -0,0 +1,11 @@
export function applyFills( orderStatus, filled ) {
console.log('apply fills', orderStatus, filled)
orderStatus[4] = filled[0][0]
orderStatus[5] = filled[0][1]
for( const i in filled[1] ) {
const {filledIn, filledOut} = filled[1][i]
orderStatus[6][i] = filledIn
orderStatus[7][i] = filledOut
}
console.log('applied fills', orderStatus)
}

View File

@@ -3,6 +3,19 @@ import {useStore} from "@/store/store.js";
import {erc20Abi} from "@/blockchain/abi.js";
import {ethers} from "ethers";
// synchronous version may return null but will trigger a lookup
export function token(addr) {
const s = useStore()
if( !(addr in s.tokens) ) {
getToken(addr)
return null
}
return s.tokens[addr]
}
// async version doesnt return until it has a token value
export async function getToken(addr) {
const s = useStore()
if (!(addr in s.tokens))

View File

@@ -88,14 +88,27 @@ export async function connectWallet() {
const pendingOrders = []
export function ensureVault() {
const s = useStore()
if( !s.chain ) {
console.log('cannot create vault: no chain selected')
return
}
if( !s.account ) {
console.log('cannot create vault: no account logged in')
return
}
socket.emit('ensureVault', s.chainId, s.account, 0)
}
export async function pendOrder(order) {
console.log('order', JSON.stringify(order))
const s = useStore()
const signer = await s.provider.getSigner()
if (!s.vaults.length) {
pendingOrders.push(order)
const owner = await signer.getAddress();
socket.emit('ensureVault', s.chainId, owner, 0)
ensureVault()
}
else {
const vault = s.vaults[0];

20
src/components/Btn.vue Normal file
View File

@@ -0,0 +1,20 @@
<template>
<v-btn variant="outlined">
<v-icon v-if="icon" :icon="icon" :color="color"></v-icon>
<slot/>
</v-btn>
</template>
<script setup>
import {useStore} from "@/store/store";
import {useAttrs} from "vue";
const s = useStore()
const props = defineProps(['icon', 'color'])
const attrs = useAttrs()
</script>
<style scoped lang="scss">
@use "src/styles/vars" as *;
</style>

View File

@@ -2,22 +2,121 @@
<v-table>
<thead>
<tr>
<th></th>
<th>#</th>
<th>Buy Token</th>
<th>Sell Token</th>
<th>Amount</th>
<th>Filled</th>
<th>Remaining</th>
<th>Average Price</th>
<th>&nbsp;</th> <!-- actions -->
</tr>
</thead>
<tbody>
<tr v-for="order in orders">
<td></td>
<tr v-for="[index, inTokenAddr, outTokenAddr, amount, amountTokenAddr, filled, avgPrice] in orders">
<td>{{parseInt(index)+1}}</td>
<td>{{tokenSymbol(inTokenAddr)}}</td>
<td>{{tokenSymbol(outTokenAddr)}}</td>
<td>{{tokenAmount(amountTokenAddr, amount)}}</td>
<td>{{tokenAmount(amountTokenAddr, filled)}}</td>
<td>{{tokenAmount(amountTokenAddr, amount-filled)}}</td>
<td>
{{pairPrice(inTokenAddr, outTokenAddr, vaultAddr, index, avgPrice)}}
<btn v-if="pairPrice(inTokenAddr, outTokenAddr, vaultAddr, index, avgPrice)!==''" size="small"
@click="inverted[[vaultAddr,index]] = !inverted[[vaultAddr,index]]">
{{pair(inTokenAddr, outTokenAddr, vaultAddr,index)}}
</btn>
</td>
</tr>
</tbody>
</v-table>
</template>
<script setup>
import {FixedNumber} from "ethers";
import {useStore} from "@/store/store";
import {computed, reactive} from "vue";
import {token} from "@/blockchain/token.js";
import Btn from "@/components/Btn.vue"
const s = useStore()
const orders = 0; // todo tim here
const props = defineProps(['vault'])
const vaultAddr = computed(()=>props.vault?props.vault:s.vault)
const inverted = reactive({})
function tokenSymbol(addr) {
const t = token(addr)
return t ? t.symbol : addr
}
function tokenAmount(tokenAddr, amount) {
const t = token(tokenAddr)
if( !t )
return ''
const amt = FixedNumber.fromValue(amount, t.decimals, {width:256, decimals:t.decimals, signed:false})
return `${amt} ${t.symbol}`
}
// todo create a Price component that keeps inversion flags in the store and defaults to stablecoins as the quote
function pairPrice(inTokenAddr, outTokenAddr, vaultAddr, index, price) {
if( price === null )
return ''
const inToken = token(inTokenAddr)
const outToken = token(outTokenAddr)
if( !inToken || !outToken )
return ''
console.log('inout tokens', inToken, outToken)
console.log('price1', price, outToken.decimals, inToken.decimals)
const decimals = outToken.decimals-inToken.decimals
if( decimals > 0 )
price /= 10 ** decimals
else
price *= 10 ** -decimals
console.log('price2', price)
const invertedKey = [vaultAddr,index];
if( !(invertedKey in inverted) ) {
// todo prefer stablecoins as the quote asset
inverted[invertedKey] = false
}
const inv = inverted[invertedKey]
return inv ? (1 / price).toPrecision(5) : price.toPrecision(5)
}
function pair(inTokenAddr, outTokenAddr, vaultAddr, index) {
const inToken = token(inTokenAddr)
const outToken = token(outTokenAddr)
return !inToken || !outToken ? null : inverted[[vaultAddr,index]] ?
outToken.symbol+'/'+inToken.symbol : inToken.symbol+'/'+outToken.symbol
}
const orders = computed(()=>{
if( !(vaultAddr.value in s.orders) )
return {}
const result = []
for( const [index,status] of Object.entries(s.orders[vaultAddr.value]) ) {
console.log('order status', status)
// [index, symbolA, symbolB, amount, amountSymbol, filled]
const inTokenAddr = status[0][0]
const outTokenAddr = status[0][1]
const amountIsInput = !!(status[0][4])
const amountTokenAddr = amountIsInput ? inTokenAddr : outTokenAddr
console.log('getamount', status[0][3])
const amount = status[0][3]
console.log('amount', amount)
console.log('getfilled', amountIsInput ? status[4] : status[5])
const filled = amountIsInput ? status[4] : status[5]
console.log('filled', filled)
const amountIn = BigInt(status[4])
const amountOut = BigInt(status[5])
const fmtX18 = {decimals:18, width: 256, signed:false};
const avg = !amountIn || !amountOut ? null :
FixedNumber.fromValue(status[5],0, fmtX18).div(FixedNumber.fromValue(status[4], 0, fmtX18))
result.push([index, inTokenAddr, outTokenAddr, amount, amountTokenAddr, filled, avg])
}
console.log('result', result)
return result
})
</script>

View File

@@ -265,7 +265,7 @@ function placeOrder() {
window = duration
duration *= n // duration is the total time for all tranches
} else {
window = duration / n
window = Math.round(duration / n)
}
const oneHundredPercent = 65535n // by contract definition of uint16 fraction
const ceil = oneHundredPercent % BigInt(n) ? 1n : 0n

View File

@@ -23,18 +23,40 @@
</v-card-text>
</PhoneCard>
-->
<v-card v-if="s.vaults.length<=num">
<v-card-title><v-icon icon="mdi-safe-square-outline" size="small" color="red"/> No Vault Yet</v-card-title>
<phone-card v-if="s.vaults.length<=num">
<v-card-title><v-icon icon="mdi-safe-square" size="small" color="grey-darken-1"/> Create a Dexorder Vault</v-card-title>
<v-card-text v-if="num!==0"><!--todo-->Multiple vaults are not yet supported</v-card-text>
<!-- todo restore the vault-on-order approach for public beta
<v-card-text v-if="num===0">Create an order first, then your vault account will appear here to accept a deposit of trading funds.</v-card-text>
<v-card-actions><v-btn prepend-icon="mdi-plus" text="Create Order" @click="$router.push('/twap')"/></v-card-actions>
</v-card>
<v-card v-if="s.vaults.length>num">
<v-card-title><v-icon icon="mdi-safe-square-outline" size="small"/> My Vault {{s.vaults.length>1?'#'+(num+1):''}}</v-card-title> <!-- todo vault nicknames -->
-->
<!-- User-actioned but dexorder executed
<v-card-text v-if="num===0">
Your vault is a smart contract that securely holds your funds plus any orders you place. When your order
conditions are met, Dexorder creates a blockchain transaction for <code>vault.execute()</code>,
asking your vault to the order. Your vault then checks that order against the
current blockchain time and pool price, and only trades if everything looks good.
Start placing dexorders by clicking the button below to create your own personal Vault!
</v-card-text>
<v-card-actions><btn icon="mdi-safe-square" color="grey-darken-1" text="Create Vault" @click="ensureVault"/></v-card-actions>
-->
<v-card-text v-if="num===0">
Please wait while your vault is being created. This should only take a few seconds.
</v-card-text>
</phone-card>
<v-card v-if="s.vaults.length>num" :class="empty?'maxw':''">
<v-card-title><v-icon icon="mdi-safe-square" color="grey-darken-2" size="small"/> My Vault {{s.vaults.length>1?'#'+(num+1):''}}</v-card-title> <!-- todo vault nicknames -->
<v-card-subtitle v-if="exists" class="overflow-x-hidden"><copy-button :text="addr"/>{{addr}}</v-card-subtitle>
<v-card-text v-if="empty">
<p>There are no funds currently in your vault.</p>
<p>Send tokens to the address above to fund your vault.</p>
<p>
Your vault is a smart contract that securely holds your funds plus any orders you place. When your order
conditions are met, Dexorder creates a blockchain transaction for <code>vault.execute()</code>,
asking your vault to the order. Your vault then checks that order against the
current blockchain time and pool price, and only trades if everything looks good.
</p>
<p v-if="!s.mockenv">There are no funds currently in your vault. Send tokens to the address above to fund your vault.</p>
<p v-if="s.mockenv">There are no funds currently in your vault. Use the faucet below to mint some fake coins into your vault.</p>
</v-card-text>
<v-card-item v-if="!empty">
<v-table>
@@ -61,9 +83,12 @@
import {useStore} from "@/store/store.js";
import {computed, defineAsyncComponent, ref} from "vue";
import {vaultAddress} from "@/blockchain/contract.js";
import {ensureVault} from "@/blockchain/wallet.js";
import CopyButton from "@/components/CopyButton.vue";
import NeedsProvider from "@/components/NeedsProvider.vue";
import Withdraw from "@/components/Withdraw.vue";
import PhoneCard from "@/components/PhoneCard.vue";
import Btn from "@/components/Btn.vue";
const TokenRow = defineAsyncComponent(()=>import('./TokenRow.vue'))
const s = useStore()
@@ -86,8 +111,16 @@ function onWithdraw(addr) {
withdrawToken.value = token
withdrawShow.value = true
}
// todo remove automatic vault creation for Alpha 2
if(props.num===0 && !s.vault)
ensureVault()
</script>
<style scoped lang="scss">
@use "../styles/vars" as *;
p {
margin-top: 1.5em;
}
</style>

View File

@@ -8,7 +8,7 @@
<v-chip text="ALPHA" size="x-small" color="red" class="mx-1"/>
</v-app-bar-title>
<v-btn icon="mdi-safe-square-outline" text="Vault" @click="$router.push('/vault')"></v-btn>
<v-btn icon="mdi-safe-square" color="grey-darken-2" text="Vault" @click="$router.push('/vault')"></v-btn>
<v-btn icon="mdi-swap-horizontal-circle-outline" text="New Order" @click="$router.push('/twap')"></v-btn>
<v-btn icon="mdi-menu" text="Order Status" @click="$router.push('/orders')"></v-btn>

View File

@@ -12,19 +12,29 @@ import 'vuetify/styles'
import { createVuetify } from 'vuetify'
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
const black = '#333'
export default createVuetify({
theme: {
themes: {
light: {
colors: {
background: '#fffefd',
surface: '#fffefd',
primary: '#1A6CAB',
background: '#fdfffe',
surface: '#fdfffe',
// primary: '#1A6CAB',
primary: '#00CC33',
// secondary: '#59B8FF',
success: '#00CC33',
// info: '#fba92c',
warning: '#ffcc00',
error: '#CC0033',
"on-background": black,
"on-surface": black,
"on-primary": black,
"on-secondary": black,
"on-success": black,
"on-info": black,
"on-warning": black,
"on-error": black,
},
dark: false,
variables: {},

View File

@@ -2,6 +2,7 @@ import {io} from "socket.io-client";
import {useStore} from "@/store/store.js";
import {flushOrders, onChainChanged} from "@/blockchain/wallet.js";
import {ethers} from "ethers";
import {applyFills} from "@/blockchain/common.js";
export const socket = io(import.meta.env.VITE_WS_URL || undefined, {transports: ["websocket"]})
@@ -59,16 +60,42 @@ socket.on('vaults', (chainId, owner, vaults)=>{
}
})
socket.on( 'o', (chainId, vault, orderIndex, status)=>{
function handleOrderStatus(chainId, vault, orderIndex, status) {
const s = useStore()
if( s.chainId !== 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
}
socket.on('os', (chainId, vault, orders) => {
console.log('os', orders)
for( const [orderIndex, status] of orders )
handleOrderStatus(chainId, vault, orderIndex, status)
})
socket.on( 'o', handleOrderStatus)
socket.on( 'of', (chainId, vault, orderIndex, fills)=>{
const s = useStore()
if( s.chainId !== chainId )
return
console.log('of', chainId, vault, orderIndex, fills)
const orders = s.orders
if( !(vault in orders) ) {
console.log('warning: got fill on an order in an unknown vault')
return
}
if( !(orderIndex in orders[vault]) ) {
console.log(`warning: orderIndex ${orderIndex} missing from vault ${vault}`)
return
}
const order = orders[vault][orderIndex]
applyFills(order, fills)
orders[vault][orderIndex] = order
})

View File

@@ -25,6 +25,7 @@ export const useStore = defineStore('app', {
extraTokens: {},
poolPrices: {},
vaultBalances: {}, // indexed by vault addr then by token addr. value is an int
orders: {}, // indexed by vault, value is another dictionary with orderIndex as key and order status values
}),
getters: {
vault: (s)=>s.vaults.length===0 ? null : s.vaults[0],

View File

@@ -36,6 +36,10 @@
.v-text-field.text-end input {
text-align: end;
}
.maxw {
max-width: v.$card-maxw;
}
}
.uniswap-color {

View File

@@ -1,9 +1,15 @@
<template>
<orders/>
<needs-provider>
<needs-signer>
<orders/>
</needs-signer>
</needs-provider>
</template>
<script setup>
import Orders from "@/components/Orders.vue";
import NeedsSigner from "@/components/NeedsSigner.vue";
import NeedsProvider from "@/components/NeedsProvider.vue";
</script>
<style scoped lang="scss">