From a6bce1613ba1b543c7f833e0de2065bd911313b6 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 28 Mar 2025 20:05:31 -0400 Subject: [PATCH] transaction progressor --- src/blockchain/transaction.js | 21 ++++--- src/blockchain/wallet.js | 75 +++++++++++++++++++++--- src/components/Status.vue | 4 +- src/components/chart/ChartPlaceOrder.vue | 30 +++++++++- src/fees.js | 2 +- src/layouts/chart/ChartLayout.vue | 6 +- src/misc.js | 6 +- src/socket.js | 2 +- src/store/store.js | 3 +- src/version.js | 6 ++ 10 files changed, 130 insertions(+), 25 deletions(-) diff --git a/src/blockchain/transaction.js b/src/blockchain/transaction.js index 2bd4b64..8b845f9 100644 --- a/src/blockchain/transaction.js +++ b/src/blockchain/transaction.js @@ -37,10 +37,15 @@ export class Transaction { } submit() { - useWalletStore().transaction = this - ensureVault() + const ws = useWalletStore(); + if ( ws.transaction !== null ) { + console.error('Transaction already in progress', ws.transaction) + return + } + ws.transaction = this } + // "propose" means attach the transaction to a specific vault propose(owner, vault) { if (this.vault !== null && this.vault !== vault) { this.failed('proposed vault did not match withdrawl vault', vault, this.vault) @@ -131,13 +136,13 @@ export class Transaction { try { const tx = toRaw(await this.createTx(contract)) this.signed(tx) + tx.wait().then(this.mined.bind(this)).catch(this.failed.bind(this)) console.log(`sent transaction`, tx) } catch (e) { this.failed(e) return null } - tx.wait().then(this.mined.bind(this)).catch(this.failed.bind(this)) return this.tx } @@ -165,19 +170,21 @@ export class PlaceOrderTransaction extends Transaction { async createTx(vaultContract) { - const tries = 3; + const tries = 65; let i; - for (i=0; i a + b)}) diff --git a/src/blockchain/wallet.js b/src/blockchain/wallet.js index 67c6c94..0b6e02f 100644 --- a/src/blockchain/wallet.js +++ b/src/blockchain/wallet.js @@ -1,18 +1,17 @@ import {BrowserProvider, ethers} from "ethers"; import {useStore} from "@/store/store"; import {socket} from "@/socket.js"; -import {SingletonCoroutine} from "@/misc.js"; +import {errorSuggestsMissingVault, SingletonCoroutine} from "@/misc.js"; import {newContract, vaultAddress, vaultContract} from "@/blockchain/contract.js"; import {defineStore} from "pinia"; -import {ref} from "vue"; +import {computed, ref} from "vue"; import {metadataMap, version} from "@/version.js"; -import {CancelAllTransaction, TransactionState} from "@/blockchain/transaction.js"; +import {CancelAllTransaction, TransactionState, TransactionType} from "@/blockchain/transaction.js"; export let provider = null -// DEPRECATED export const useWalletStore = defineStore('wallet', ()=>{ // this is what the wallet is logged into. it could be different than the application's store.chainId. const chainId = ref(0) @@ -29,7 +28,24 @@ export const useWalletStore = defineStore('wallet', ()=>{ const pendingOrders = ref([]) // NEW Format is a single Transaction class - const transaction = ref(null) + const _tx = ref(null) + const transaction = computed({ + get() {return _tx.value}, + set(v) { + _tx.value = v; + if (v===null) { + if (progressionInvoker!==null) { + clearTimeout(progressionInvoker) + progressionInvoker = null + } + } + else { + transactionProgressor.invoke(); + if (progressionInvoker===null) + progressionInvoker = setInterval(()=>transactionProgressor.invoke(), 1000) + } + }, + }) return { chainId, pendingOrders, transaction, @@ -183,6 +199,7 @@ function discoverVaults(owner) { } const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50) + async function _discoverVaults(owner) { const result = [] const versions = [] @@ -199,7 +216,6 @@ async function _discoverVaults(owner) { // console.log(`vault ${num} at`, addr) if (addr === null) // no more vaults break - console.log('provider', provider) if (!provider) { console.log('No provider') return // do not change whatever was already found @@ -211,7 +227,7 @@ async function _discoverVaults(owner) { result.push(addr) versions.push(version) } catch (e) { - if (e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION') + if (errorSuggestsMissingVault(e)) console.log(`no vault ${num} at ${addr}`) else console.error(`discoverVaults failed`, e) @@ -293,10 +309,51 @@ export async function cancelAll(vault) { new CancelAllTransaction(useStore().chainId, vault).submit() } + +async function progressTransactions() { + const s = useStore() + const ws = useWalletStore(); + if( ws.transaction===null ) + return + if( s.account === null ) { + let signer = null + try { + signer = await provider.getSigner() + } + catch (e) { + console.log('signer error', e.code, e.info.error.code) + if (e?.info?.error?.code === 4001) { + console.log('signer rejected') + signer = null + } + else + throw e + } + if (signer === null) { + console.log('setting tx state to rejected') + ws.transaction.state = TransactionState.Rejected + ws.transaction = null + return + } + } + if( s.vault === null ) { + ensureVault() + return + } + if( ws.transaction.type === TransactionType.PlaceOrder ) { + flushOrders(s.chainId, s.account, 0, s.vault) + } +} + +const transactionProgressor = new SingletonCoroutine(progressTransactions, 10) + +let progressionInvoker = null + + export function flushOrders(chainId, owner, num, vault) { const ws = useWalletStore(); - console.log('flushOrders', ws.transaction) - if (ws.transaction!==null && ws.transaction.state < TransactionState.Proposed) + console.log('flushOrders', chainId, owner, num, vault) + if (ws.transaction!==null && ws.transaction.type === TransactionType.PlaceOrder && ws.transaction.state < TransactionState.Proposed) ws.transaction.propose(owner, vault) let needsFlush = false for( const pend of ws.pendingOrders ) { diff --git a/src/components/Status.vue b/src/components/Status.vue index 8aa87b1..347b273 100644 --- a/src/components/Status.vue +++ b/src/components/Status.vue @@ -94,8 +94,8 @@
-
market order
-
DCA {{Math.round(MAX_FRACTION/t.rateLimitFraction)}} parts
+
market order
+
DCA {{Math.round(MAX_FRACTION/t.rateLimitFraction)}} parts
+ + + + + OK + + +
@@ -56,6 +64,7 @@ import {useWalletStore} from "@/blockchain/wallet.js"; import ToolbarPane from "@/components/chart/ToolbarPane.vue"; import NeedsChart from "@/components/NeedsChart.vue"; import {PlaceOrderTransaction} from "@/blockchain/transaction.js"; +import {errorSuggestsMissingVault} from "@/misc.js"; const s = useStore() const co = useChartOrderStore() @@ -96,6 +105,8 @@ const orderChanged = computed(()=>!(co.orders.length===1 && co.orders[0].builder const showWarnings = ref(false) const orderWarnings = ref([]) +const placementError = ref(false) + function resetOrder() { showResetDialog.value = true } @@ -103,7 +114,9 @@ function resetOrder() { function doResetOrder() { co.resetOrders(); orderWarnings.value = [] + showWarnings.value = false showResetDialog.value = false + placementError.value = false } watchEffect(()=>{ @@ -157,12 +170,27 @@ async function placeOrder() { async function doPlaceOrder() { console.log('place orders') + placementError.value = false showWarnings.value = false if (ws.transaction!==null) { console.error('Transaction already in progress') } else { - new PlaceOrderTransaction(s.chainId, toRaw(built[0])).submit() + const tx = new PlaceOrderTransaction(s.chainId, toRaw(built[0])); + tx.retries = 60 + const oldFailed = tx.failed + tx.failed = function(e) { + console.error('place order failed', errorSuggestsMissingVault(e), e.code, e) + if (errorSuggestsMissingVault(e) && e.action === 'feeManager' && this.retries-- >= 0) { + s.creatingVault = true + } + else { + s.creatingVault = false + oldFailed.bind(this)(e) + placementError.value = true + } + } + tx.submit() // this assigns the tx to walletStore.transaction } } diff --git a/src/fees.js b/src/fees.js index d814d58..8893c8f 100644 --- a/src/fees.js +++ b/src/fees.js @@ -31,7 +31,6 @@ export async function getFeeSchedule(vaultAddr) { export async function placementFee(vaultContractOrAddr, order, window = 300) { // If the fees are about to change within `window` seconds of now, we send the higher native amount of the two fees. // If the fees sent are too much, the vault will refund the sender. - console.log('placementFee', vaultContractOrAddr, order) const vault = typeof vaultContractOrAddr === 'string' ? await vaultContract(vaultContractOrAddr, provider) : vaultContractOrAddr const feeManager = await getFeeManagerContract(vault); const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()]) @@ -42,6 +41,7 @@ export async function placementFee(vaultContractOrAddr, order, window = 300) { console.log('placementFee', orderFee, gasFee) if (Number(changeTimestamp) - timestamp() < window) { const nextSched = await feeManager.proposedFees() + console.log('nextSched', new Date(Number(changeTimestamp)*1000), nextSched) const [nextOrderFee, nextGasFee] = await vault[placementFeeSelector](order, [...nextSched]) if (nextOrderFee + nextGasFee > orderFee + gasFee) [orderFee, gasFee] = [nextOrderFee, nextGasFee] diff --git a/src/layouts/chart/ChartLayout.vue b/src/layouts/chart/ChartLayout.vue index 5629d0a..06cb7ec 100644 --- a/src/layouts/chart/ChartLayout.vue +++ b/src/layouts/chart/ChartLayout.vue @@ -4,8 +4,10 @@ {{description}} - Confirm this {{noun}} in your wallet. Creating your trading vault smart contract. Please wait a few seconds... + Verifying your trading vault... + Confirm this {{noun}} in your wallet. + Signed and sent! Waiting for blockchain confirmation... @@ -16,7 +18,7 @@ import MainView from './MainView.vue' import {useStore} from "@/store/store.js"; import {computed} from "vue"; import {useWalletStore} from "@/blockchain/wallet.js"; -import {TransactionType} from "@/blockchain/transaction.js"; +import {TransactionState, TransactionType} from "@/blockchain/transaction.js"; import {FixedNumber} from "ethers"; const s = useStore() diff --git a/src/misc.js b/src/misc.js index 0874e6d..2dcc96a 100644 --- a/src/misc.js +++ b/src/misc.js @@ -258,4 +258,8 @@ export function toPrecision(value, significantDigits = 3) { const magnitude = Math.floor(Math.log10(Math.abs(value))); const decimalsNeeded = Math.max(0, significantDigits - 1 - magnitude); return value.toFixed(decimalsNeeded); // Use toFixed to completely avoid scientific notation -} \ No newline at end of file +} + +export function errorSuggestsMissingVault(e) { + return e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION'; +} diff --git a/src/socket.js b/src/socket.js index 6228177..85eb043 100644 --- a/src/socket.js +++ b/src/socket.js @@ -1,6 +1,6 @@ import {io} from "socket.io-client"; import {useStore} from "@/store/store.js"; -import {flushOrders} from "@/blockchain/wallet.js"; +import {ensureVault, flushOrders} from "@/blockchain/wallet.js"; import {parseElaboratedOrderStatus} from "@/blockchain/orderlib.js"; import { DataFeed } from "./charts/datafeed"; import {notifyFillEvent} from "@/notify.js"; diff --git a/src/store/store.js b/src/store/store.js index 3f14122..fb0c99e 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -101,6 +101,7 @@ export const useStore = defineStore('app', ()=> { const orders = ref({}) // indexed by vault value is another dictionary with orderIndex as key and order status values const vault = computed(() => vaults.value.length === 0 ? null : vaults.value[0] ) + const creatingVault = ref(false) // used when a vault is first created but not yet responsive via metamask. If vault is not null, but creatingVault is true, then the vault is not yet ready for interaction. const upgrade = ref(null) const version = computed( () => vaultVersions.value.length === 0 ? 0 : vaultVersions.value[0] ) const balances = computed( () => vault.value === null ? {} : vaultBalances.value[vault.value] || {} ) @@ -145,7 +146,7 @@ export const useStore = defineStore('app', ()=> { mockenv, mockCoins, removeTransactionSender, error, closeError, addToken, clock, balances, approved, regionApproved, walletApproved, - getBalance + getBalance, creatingVault, } }) diff --git a/src/version.js b/src/version.js index 7be560a..f4324f2 100644 --- a/src/version.js +++ b/src/version.js @@ -20,4 +20,10 @@ console.log('version', version) export const metadata = await metadataPromise console.log('metadata', metadata) +export function dexorderAddress(chainId) { return version['chainInfo'][chainId]['dexorder'] } +export function factoryAddress(chainId) { return version['chainInfo'][chainId]['factory'] } +export function helperAddress(chainId) { return version['chainInfo'][chainId]['helper'] } +export function vaultInitCodeHash(chainId) { return version['chainInfo'][chainId]['vaultInitCodeHash'] } + +// maps [chainId][addr] to pool or token metadata export const metadataMap = buildMetadataMap(metadata)