Files
web/src/blockchain/transaction.js
2025-03-28 20:05:31 -04:00

284 lines
7.2 KiB
JavaScript

import {nav, sleep, uuid} from "@/misc.js";
import {vaultContract} from "@/blockchain/contract.js";
import {ensureVault, provider, switchChain, useWalletStore} from "@/blockchain/wallet.js";
import {toRaw} from "vue";
import {useChartOrderStore} from "@/orderbuild.js";
import {placementFee} from "@/fees.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() {
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)
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
}
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
}
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) {
const tries = 65;
let 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)
success = true
}
catch (e) {
console.warn('failed to get placement fee', e)
await sleep(1000)
}
}
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)})
}
end(state) {
super.end(state)
if (state === TransactionState.Mined) {
useChartOrderStore().resetOrders()
nav('Status')
}
}
}
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)
}
}