import {nav, uuid} from "@/misc.js"; import {newContract, vaultContract} from "@/blockchain/contract.js"; import {ensureVault, provider, switchChain, useWalletStore} from "@/blockchain/wallet.js"; import {toRaw} from "vue"; import {useChartOrderStore} from "@/orderbuild.js"; import {timestamp} from "@/common.js"; export const TransactionState = { Created: 0, // user requested a transaction Proposed: 1, // tx is sent to the wallet Signed: 2, // tx is awaiting blockchain mining Rejected: 3, // user refused to sign the tx Error: 3, // unknown error sending the tx to the wallet Mined: 4, // transaction has been confirmed on-chain } export const TransactionType = { PlaceOrder: 1, CancelOrder: 2, CancelAll: 3, Wrap: 4, Unwrap: 5, WithdrawNative: 6, Withdraw: 7, } export class Transaction { constructor(chainId, type) { this.id = uuid() this.type = type this.state = TransactionState.Created this.tx = null this.chainId = chainId this.owner = null this.vault = null this.error = null } submit() { useWalletStore().transaction = this ensureVault() } propose(owner, vault) { if (this.vault !== null && this.vault !== vault) { this.failed('proposed vault did not match withdrawl vault', vault, this.vault) return } this.owner = owner this.vault = vault this.send().catch(this.catchSend.bind(this)) this.state = TransactionState.Proposed } async createTx(vaultContract) { throw Error('unimplemented') } signed(tx) { this.tx = tx this.state = TransactionState.Signed } rejected() { this.tx = null this.chainId = null this.owner = null this.vault = null this.end(TransactionState.Rejected) console.log('transaction rejected', this.id) } failed(e) { this.error = e this.end(TransactionState.Error) console.log('transaction failed', this.id, e) } mined(receipt) { this.receipt = receipt this.end(TransactionState.Mined) console.log('mined transaction', this.id, receipt) } isOpen() { return this.state >= TransactionState.Rejected } isClosed() { return this.state < TransactionState.Rejected } end(state) { this.state = state useWalletStore().transaction = null } async send() { console.log('sendTransaction', this) try { await switchChain(this.chainId) } catch (e) { if (e.code === 4001) { this.rejected() return null } else { this.failed(e) return null } } let signer try { signer = await provider.getSigner(); } catch (e) { // { // "code": -32002, // "message": "Already processing eth_requestAccounts. Please wait." // } this.rejected() return null } let contract try { contract = await vaultContract(this.vault, signer) } catch (e) { this.failed('vault contract was null while sending order transaction') return null } const tx = toRaw(await this.createTx(contract)) this.signed(tx) console.log(`sent transaction`, tx) tx.wait().then(this.mined.bind(this)).catch(this.failed.bind(this)) return this.tx } catchSend(e) { this.error = e if (e.info?.error?.code === 4001) { console.log(`wallet refused transaction`, this.id) this.rejected() } else { this.failed(e) } } } export class PlaceOrderTransaction extends Transaction { constructor(chainId, order) { super(chainId, TransactionType.PlaceOrder) this.order = order this.placementTime = Date.now()/1000 this.fee = null // dexorder place and gas fee total } async createTx(vaultContract) { this.fee = await placementFee(this.vault, this.order) console.log('placing order', this.id, this.fee, this.order) return await vaultContract.placeDexorder(this.order, {value: this.fee.reduce((a, b) => a + b)}) } end(state) { super.end(state) if (state === TransactionState.Mined) { useChartOrderStore().resetOrders() nav('Status') } } } // todo move to orderlib async function placementFee(vault, 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. const v = await vaultContract(vault, provider) const feeManagerAddr = await v.feeManager() const feeManager = await newContract(feeManagerAddr, 'IFeeManager', provider) const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()]) console.log('sched', order, sched) // single order placement selector const placementFeeSelector = 'placementFee((address,address,(uint8,uint24),uint256,uint256,bool,bool,bool,uint64,(uint16,bool,bool,bool,bool,bool,bool,bool,bool,uint16,uint24,uint32,uint32,(uint32,uint32),(uint32,uint32))[]),(uint8,uint8,uint8,uint8,uint8))' let [orderFee, gasFee] = await v[placementFeeSelector](order, [...sched]) console.log('placementFee', orderFee, gasFee) if (Number(changeTimestamp) - timestamp() < window) { const nextSched = await feeManager.proposedFees() const [nextOrderFee, nextGasFee] = await v[placementFeeSelector](order, [...nextSched]) if (nextOrderFee + nextGasFee > orderFee + gasFee) [orderFee, gasFee] = [nextOrderFee, nextGasFee] } return [orderFee, gasFee] } export class CancelOrderTransaction extends Transaction { constructor(chainId, index) { super(chainId, TransactionType.CancelOrder) this.index = index } async createTx(vaultContract) { return await vaultContract.cancelDexorder(this.index) } } export class CancelAllTransaction extends Transaction { constructor(chainId, vault) { super(chainId, TransactionType.CancelAll) this.vault = vault } async createTx(vaultContract) { return await vaultContract.cancelAllDexorders() } } export class WithdrawTransaction extends Transaction { constructor(chainId, vault, token, amount) { super(chainId, TransactionType.Withdraw) this.token = token this.amount = amount this.vault = vault } async createTx(vaultContract) { return await vaultContract['withdraw(address,uint256)'](this.token.a, this.amount) } } export class WithdrawNativeTransaction extends Transaction { constructor(chainId, vault, amount) { super(chainId, TransactionType.WithdrawNative) this.amount = amount this.vault = vault } async createTx(vaultContract) { return await vaultContract['withdraw(uint256)'](this.amount) } } export class WrapTransaction extends Transaction { constructor(chainId, vault, amount) { super(chainId, TransactionType.Wrap) this.vault = vault this.amount = amount } async createTx(vaultContract) { return await vaultContract.wrap(this.amount) } } export class UnwrapTransaction extends Transaction { constructor(chainId, vault, amount) { super(chainId, TransactionType.Unwrap) this.amount = amount } async createTx(vaultContract) { return await vaultContract.unwrap(this.amount) } }