281 lines
7.7 KiB
JavaScript
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)
|
|
}
|
|
}
|
|
|