From 23e0deee916a70ae3d19a31be5309a7d7a2ab3ba Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 28 Mar 2024 15:34:37 -0400 Subject: [PATCH] order workflow --- src/blockchain/orderlib.js | 2 +- src/blockchain/token.js | 5 +- src/blockchain/wallet.js | 82 +++++++++++++++--------- src/components/NeedsProvider.vue | 5 +- src/components/Order.vue | 2 +- src/components/Orders.vue | 21 +++++- src/components/chart/ChartOrder.vue | 31 +++++---- src/components/chart/ChartPlaceOrder.vue | 45 +++++++------ src/components/chart/LimitBuilder.vue | 16 ++++- src/components/chart/Toolbar.vue | 15 ++--- src/components/chart/ToolbarButton.vue | 9 ++- src/layouts/default/AppBar.vue | 2 +- src/orderbuild.js | 29 ++++++--- src/router/index.js | 8 +-- 14 files changed, 170 insertions(+), 102 deletions(-) diff --git a/src/blockchain/orderlib.js b/src/blockchain/orderlib.js index f7e0680..fa18814 100644 --- a/src/blockchain/orderlib.js +++ b/src/blockchain/orderlib.js @@ -114,7 +114,7 @@ export function orderIsOpen(order) { } export function isOpen(state) { - return state < 3 + return state >= 1 && state < 3 } export function parseOrderStatus(status) { diff --git a/src/blockchain/token.js b/src/blockchain/token.js index 16d53c6..ac3370e 100644 --- a/src/blockchain/token.js +++ b/src/blockchain/token.js @@ -19,9 +19,8 @@ export function token(chainId, addr) { // async version doesnt return until it has a token value export async function getToken(chainId, addr) { - // todo deprecated. use metadataMap[chainId][addr] const s = useStore() - if (addr===undefined) { + if (addr===undefined) { // todo remove check console.warn('getToken(addr) is deprecated. use getToken(chainId,addr)') addr = chainId chainId = s.chainId @@ -38,7 +37,7 @@ export async function getToken(chainId, addr) { const _inFlightLookups = {} export async function addExtraToken(chainId, addr) { - if (addr===undefined) { + if (addr===undefined) { // todo remove check console.warn('addExtraToken(addr) is deprecated. use addExtraToken(chainId,addr)') addr = chainId chainId = s.chainId diff --git a/src/blockchain/wallet.js b/src/blockchain/wallet.js index b6e938f..0f2ad64 100644 --- a/src/blockchain/wallet.js +++ b/src/blockchain/wallet.js @@ -7,6 +7,7 @@ import {SingletonCoroutine} from "@/misc.js"; import {defineStore} from "pinia"; import {ref} from "vue"; import {metadata, metadataMap} from "@/version.js"; +import {OrderState} from "@/blockchain/orderlib.js"; export const useWalletStore = defineStore('wallet', ()=>{ @@ -17,7 +18,7 @@ export const useWalletStore = defineStore('wallet', ()=>{ // { // chainId: 31337, // must never be null, even if no wallet plugin exists. chosen by app, not wallet. // placementTime: Date.now(), - // submitted: false // true after the order has started to be sent to the wallet + // state: PendingOrderState.Submitted // tx: null // transaction ID // vault: '0x...', // or null if account not logged in yet // order: {tokenIn:..., tokenOut:..., ...} // blockchain binary order object @@ -213,11 +214,12 @@ async function _discoverVaults(owner) { } if( s.account === owner ) { // double-check the account since it could have changed during our await s.vaults = result - if( useWalletStore().pendingOrders.length ) - if( result.length ) + if( useWalletStore().pendingOrders.length ) { + if (result.length) flushOrders(result[0]) else ensureVault2(s.chainId, owner, 0) + } } } @@ -257,20 +259,28 @@ async function doEnsureVault(chainId, owner, num) { console.log(`requesting vault ${owner} ${num}`) socket.emit('ensureVault', chainId, owner, num) } - // await sleep(5000) // prevent this process from running more than once every 5 seconds } +// await sleep(5000) // prevent this process from running more than once every 5 seconds const ensureVaultRoutine = new SingletonCoroutine(doEnsureVault, 100) +export const PendingOrderState = { + Submitted: 100, // user clicked Place Order but the tx isn't sent to the wallet yet + Signing: 0, // tx is sent to the wallet + Rejected: 101, // user refused to sign the tx + Sent: 102, // tx is awaiting blockchain mining +} + + export async function pendOrder(order) { console.log('order', JSON.stringify(order)) const s = useStore() useWalletStore().pendingOrders.push({ chainId: s.chainId, - placementTime: new Date(), + placementTime: Date.now()/1000, vault: s.vaults.length ? s.vaults[0] : null, - submitted: false, + state: PendingOrderState.Submitted, order }) ensureVault() @@ -303,12 +313,13 @@ export async function cancelAll(vault) { export function flushOrders(vault) { const ws = useWalletStore(); let needsFlush = false - for( const order of ws.pendingOrders ) { - if (order.vault === null) - order.vault = vault - if (!order.submitted) { - pendOrderAsTransaction(order) - order.submitted = true + for( const pend of ws.pendingOrders ) { + if (pend.vault === null) + pend.vault = vault + if (pend.state === PendingOrderState.Submitted) { + console.log('pending', pend) + pendOrderAsTransaction(pend) + pend.state = PendingOrderState.Signing needsFlush = true } } @@ -317,40 +328,46 @@ export function flushOrders(vault) { } -function pendOrderAsTransaction(order) { +function pendOrderAsTransaction(pend) { pendTransaction(async (signer)=> { - const contract = contractOrNull(order.vault, vaultAbi, signer) + const contract = contractOrNull(pend.vault, vaultAbi, signer) if( contract === null ) { - console.error('vault contract was null while sending order transaction', order.vault) + console.error('vault contract was null while sending order transaction', pend.vault) return null } - if (!await switchChain(order.chainId)) { + if (!await switchChain(pend.chainId)) { console.log('user refused chain switch') + pend.state = PendingOrderState.Rejected return null } - console.log('placing order', order) - const tx = await contract.placeDexorder(order.order) // todo update status - order.tx = tx + console.log('placing order', pend) + const tx = await contract.placeDexorder(pend.order) // todo update status + pend.tx = tx tx.wait().then((txReceipt)=>{ const ws = useWalletStore(); - ws.pendingOrders = ws.pendingOrders.filter((o)=>o!==order) + ws.pendingOrders = ws.pendingOrders.filter((o)=>o!==pend) }) return tx + }, + (e) => { + if( e.info?.error?.code === 4001 ) { + console.log(`user rejected transaction`) + pend.state = PendingOrderState.Rejected + return true // returning true means we handled the error. any other return value will dump to console. + } }) } -export function pendTransaction(sender) { +export function pendTransaction(sender, errHandler) { const s = useStore() - s.transactionSenders.push(sender) + s.transactionSenders.push([sender,errHandler]) flushTransactions() } const flushTransactionsRoutine = new SingletonCoroutine(asyncFlushTransactions,1) -let flushing = 0 // semaphore - export function flushTransactions() { flushTransactionsRoutine.invoke() } @@ -376,11 +393,12 @@ async function asyncFlushTransactions() { console.log('signer denied') return } - for (const sender of senders) - doSendTransaction(sender, signer) + for (const [sender,errHandler] of senders) + doSendTransaction(sender, signer, errHandler) + s.transactionSenders = [] } -function doSendTransaction(sender, signer) { +function doSendTransaction(sender, signer, errHandler) { const s = useStore(); s.removeTransactionSender(sender) sender(signer).then((tx)=>{ @@ -388,11 +406,11 @@ function doSendTransaction(sender, signer) { console.log('sent transaction', tx) tx.wait().then((tr)=>console.log('tx receipt',tr)) } - }).catch((e)=>{ - if( e.info?.error?.code === 4001 ) { - console.log(`user rejected transaction`) - } - else { + }).catch(async (e)=>{ + let dumpErr = true + if (errHandler!==undefined) + dumpErr = await errHandler(e) !== true + if (dumpErr) { if( e.reason && e.info ) console.error('error sending transaction', e.reason, e.info) else diff --git a/src/components/NeedsProvider.vue b/src/components/NeedsProvider.vue index 06387be..7720ce2 100644 --- a/src/components/NeedsProvider.vue +++ b/src/components/NeedsProvider.vue @@ -66,10 +66,7 @@ const s = useStore() const walletOk = typeof window.ethereum !== 'undefined' const providerOk = computed(()=>s.provider!==null) const chainOk = computed(()=>providerOk.value && s.helper!==null) -const ok = computed(()=>{ - console.log('recompute provider ok') - return walletOk && providerOk.value && chainOk.value -}) +const ok = computed(()=>walletOk && providerOk.value && chainOk.value) function reload() { window.location.reload() } diff --git a/src/components/Order.vue b/src/components/Order.vue index 24b2c62..f180f94 100644 --- a/src/components/Order.vue +++ b/src/components/Order.vue @@ -11,7 +11,7 @@ - Cancel + Cancel Place Dexorder diff --git a/src/components/Orders.vue b/src/components/Orders.vue index c79ac77..d2e0770 100644 --- a/src/components/Orders.vue +++ b/src/components/Orders.vue @@ -43,7 +43,10 @@ - Cancel All Orders @@ -113,7 +115,7 @@ import {FixedNumber} from "ethers"; import {useStore} from "@/store/store"; import {computed, defineAsyncComponent, ref} from "vue"; import Btn from "@/components/Btn.vue" -import {cancelAll, cancelOrder} from "@/blockchain/wallet.js"; +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"; @@ -123,6 +125,7 @@ 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({}) @@ -196,6 +199,18 @@ const orders = computed(()=>{ // ] const result = [] + // in-flight orders + for (const pend of ws.pendingOrders) { + console.log('pended order', pend) + result.splice(0, 0, { + start: pend.placementTime, + order: pend.order, + filled: 0, + state: pend.state + }) + } + + // historical orders if( vaultAddr.value in s.orders ) { for (const [index, status] of Object.entries(s.orders[vaultAddr.value]).reverse()) { const st = {...status} diff --git a/src/components/chart/ChartOrder.vue b/src/components/chart/ChartOrder.vue index d9f6f17..e574574 100644 --- a/src/components/chart/ChartOrder.vue +++ b/src/components/chart/ChartOrder.vue @@ -23,14 +23,16 @@
- Add condition: - - Limit - - +
+ Add condition: + + Limit + + +
@@ -41,7 +43,7 @@ import BuilderFactory from "@/components/chart/BuilderFactory.vue"; import {builderFuncs, newBuilder, orderFuncs, useChartOrderStore} from "@/orderbuild.js"; import {useOrderStore} from "@/store/store.js"; -import {computed, onMounted, onUnmounted, watch} from "vue"; +import {computed, onMounted, onUnmounted, onUpdated, watch} from "vue"; import {lightenColor, lightenColor2} from "@/misc.js"; import {useTheme} from "vuetify"; import RowBar from "@/components/chart/RowBar.vue"; @@ -98,8 +100,15 @@ function buildOrder() { } -onMounted(()=>orderFuncs[props.order.id] = buildOrder) -onUnmounted(()=>delete orderFuncs[props.order.id]) +let lastId = props.order.id +orderFuncs[lastId] = buildOrder +onUpdated(()=>{ + console.log('onUpdated', lastId, ...arguments) + delete orderFuncs[lastId] + orderFuncs[props.order.id] = buildOrder + lastId = props.order.id +}) +onUnmounted(() => delete orderFuncs[lastId]) const theme = useTheme().current diff --git a/src/components/chart/ChartPlaceOrder.vue b/src/components/chart/ChartPlaceOrder.vue index d5a4a28..9e00f69 100644 --- a/src/components/chart/ChartPlaceOrder.vue +++ b/src/components/chart/ChartPlaceOrder.vue @@ -1,31 +1,32 @@ diff --git a/src/layouts/default/AppBar.vue b/src/layouts/default/AppBar.vue index 2daf1ba..246fec9 100644 --- a/src/layouts/default/AppBar.vue +++ b/src/layouts/default/AppBar.vue @@ -9,7 +9,7 @@ - + diff --git a/src/orderbuild.js b/src/orderbuild.js index 45c0dc2..28d95de 100644 --- a/src/orderbuild.js +++ b/src/orderbuild.js @@ -31,12 +31,15 @@ export const orderFuncs = {} // the key is order.builder.id and the value is a function() that returns an array of tranches export const builderFuncs = {} +function newDefaultOrder() { + return { id:uuid(), amount:1, amountIsTokenA: true, buy: true, builders:[] } +} export const useChartOrderStore = defineStore('chart_orders', () => { const chartReady = ref(false) - const orders = ref([]) // order models in UI format - const built = ref([]) // orders in blockchain format, ready to send + const orders = ref([newDefaultOrder()]) // order models in UI format + const built = ref([{}]) // orders in blockchain format, ready to send const selectedOrder = ref(null) const selectedSymbol = ref(null) const selectedPool = ref(null) @@ -45,7 +48,7 @@ export const useChartOrderStore = defineStore('chart_orders', () => { const drawingCallbacks = ref(null) // only during draw mode function newOrder() { - const order = {id:uuid(), amount:1, amountIsTokenA: true, buy: true, builders:[] } + const order = newDefaultOrder() orders.value.push(order) built.value.push({}) selectedOrder.value = order @@ -54,15 +57,25 @@ export const useChartOrderStore = defineStore('chart_orders', () => { function removeOrder(order) { let index = orders.value.findIndex((o)=>o.id===order.id) if (index === -1) return - orders.value = orders.value.filter((o)=>o.id!==order.id) - if (orders.value.length === 0) - selectedOrder.value = null + const result = orders.value.filter((o)=>o.id!==order.id) + if (result.length === 0) { + const order = newDefaultOrder() + result.push(order) + selectedOrder.value = order + } else - selectedOrder.value = orders.value[Math.max(0,index-1)] // select the order above the removed one + selectedOrder.value = result[Math.max(0,index-1)] // select the order above the removed one + orders.value = result + } + + function resetOrders() { + const order = newDefaultOrder() + orders.value = [order] + selectedOrder.value = order } return { - chartReady, selectedSymbol, selectedPool, orders, drawing, drawingCallbacks, newOrder, removeOrder, + chartReady, selectedSymbol, selectedPool, orders, drawing, drawingCallbacks, newOrder, removeOrder, resetOrders, } }) diff --git a/src/router/index.js b/src/router/index.js index 5e407a8..fc91303 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -14,16 +14,16 @@ const routes = [ component: () => import(/* webpackChunkName: "chartorder" */ '@/components/chart/ChartPlaceOrder.vue'), }, { - path: '/place', - name: 'Place', + path: '/create', + name: 'Edit', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "chartorder" */ '@/components/chart/ChartPlaceOrder.vue'), }, { - path: '/vault', - name: 'Vault', + path: '/assets', + name: 'Assets', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited.