Files
web/src/blockchain/transaction.js
2025-01-16 20:17:03 -04:00

281 lines
7.7 KiB
JavaScript

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)
}
}