transaction progressor
This commit is contained in:
@@ -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<tries; i++ ) {
|
||||
let success = false
|
||||
for (i=0; !success && i<tries; i++ ) {
|
||||
try {
|
||||
console.error('getting placement fee', vaultContract, this.order)
|
||||
this.fee = await placementFee(vaultContract, this.order)
|
||||
break
|
||||
success = true
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('failed to get placement fee', e)
|
||||
await sleep(1000)
|
||||
}
|
||||
}
|
||||
if (i===tries)
|
||||
if (!success)
|
||||
throw Error('failed to get placement fee')
|
||||
console.log('placing order', this.id, this.fee, this.order)
|
||||
return await vaultContract.placeDexorder(this.order, {value: this.fee.reduce((a, b) => a + b)})
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
<tr>
|
||||
<td class="text-right" colspan="2">
|
||||
<div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitFraction === MAX_FRACTION">market order</div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitFraction < MAX_FRACTION">DCA {{Math.round(MAX_FRACTION/t.rateLimitFraction)}} parts</div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitPeriod === 0">market order</div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitPeriod !== 0">DCA {{Math.round(MAX_FRACTION/t.rateLimitFraction)}} parts</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"
|
||||
|
||||
@@ -38,6 +38,14 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="placementError" max-width="300">
|
||||
<v-card prepend-icon="mdi-alert" title="Order Error" text="There was an error placing your order. Please try again or contact support.">
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn @click="()=>{placementError=false;ws.transaction=null}">OK</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</needs-chart>
|
||||
</div>
|
||||
</toolbar-pane>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
<v-dialog v-model="showTransactionDialog" max-width="300">
|
||||
<v-card :title="title">
|
||||
<v-card-text v-if="description!==null">{{description}}</v-card-text>
|
||||
<v-card-text v-if="s.vault">Confirm this {{noun}} in your wallet.</v-card-text>
|
||||
<v-card-text v-if="!s.vault">Creating your trading vault smart contract. Please wait a few seconds...</v-card-text>
|
||||
<v-card-text v-if="s.vault && s.creatingVault">Verifying your trading vault...</v-card-text>
|
||||
<v-card-text v-if="s.vault && !s.creatingVault && ws.transaction !== null && ws.transaction.state === TransactionState.Proposed">Confirm this {{noun}} in your wallet.</v-card-text>
|
||||
<v-card-text v-if="s.vault && !s.creatingVault && ws.transaction !== null && ws.transaction.state !== TransactionState.Proposed">Signed and sent! Waiting for blockchain confirmation...</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-app>
|
||||
@@ -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()
|
||||
|
||||
@@ -259,3 +259,7 @@ export function toPrecision(value, significantDigits = 3) {
|
||||
const decimalsNeeded = Math.max(0, significantDigits - 1 - magnitude);
|
||||
return value.toFixed(decimalsNeeded); // Use toFixed to completely avoid scientific notation
|
||||
}
|
||||
|
||||
export function errorSuggestsMissingVault(e) {
|
||||
return e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION';
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user