305 lines
7.8 KiB
JavaScript
305 lines
7.8 KiB
JavaScript
import {ethers} from "ethers";
|
|
import {useStore} from "@/store/store";
|
|
import {socket} from "@/socket.js";
|
|
import {contractOrNull, vaultAddress} from "@/blockchain/contract.js";
|
|
import {vaultAbi} from "@/blockchain/abi.js";
|
|
import {SingletonCoroutine, sleep} from "@/misc.js";
|
|
|
|
|
|
export function onChainChanged(chainId) {
|
|
chainId = Number(chainId)
|
|
const store = useStore()
|
|
if( chainId !== store.chainId ) {
|
|
console.log('chain changed', chainId)
|
|
store.chainId = chainId
|
|
store.account = null
|
|
const provider = new ethers.BrowserProvider(window.ethereum, chainId);
|
|
store.provider = provider
|
|
provider.listAccounts().then((accounts)=>changeAccounts(chainId, accounts.map((a)=>a.address)))
|
|
}
|
|
}
|
|
|
|
function changeAccounts(chainId, accounts) {
|
|
// console.log('changeAccounts', chainId, accounts)
|
|
const store = useStore()
|
|
if( accounts.length === 0 ) {
|
|
console.log('account logged out')
|
|
store.account = null
|
|
store.vaults = []
|
|
store.vaultBalances = {}
|
|
}
|
|
else {
|
|
const addr = accounts[0]
|
|
console.log('account logged in', addr)
|
|
store.account = addr
|
|
discoverVaults(addr)
|
|
flushTransactions()
|
|
socket.emit('address', chainId, addr)
|
|
}
|
|
}
|
|
|
|
function onAccountsChanged(accounts) {
|
|
const store = useStore()
|
|
if (accounts.length === 0 || accounts[0] !== store.account)
|
|
changeAccounts(store.chainId.value, accounts);
|
|
}
|
|
|
|
export function detectChain() {
|
|
new ethers.BrowserProvider(window.ethereum).getNetwork().then((network)=>{
|
|
const chainId = network.chainId
|
|
onChainChanged(chainId)
|
|
window.ethereum.on('chainChanged', onChainChanged);
|
|
window.ethereum.on('accountsChanged', onAccountsChanged);
|
|
})
|
|
}
|
|
|
|
|
|
const errorHandlingProxy = {
|
|
get(target, prop, proxy) {
|
|
const got = Reflect.get(target, prop, proxy);
|
|
if( typeof got !== 'function' ) {
|
|
return got
|
|
}
|
|
else {
|
|
return async function (...args) {
|
|
try {
|
|
return await got.apply(target, args)
|
|
}
|
|
catch (x) {
|
|
target._connected(false)
|
|
target._enabled = false
|
|
if( x.code === 'NETWORK_ERROR' ) {
|
|
// todo available chain names
|
|
// store.error('Wrong Blockchain', 'Your wallet is connected to a different blockchain. Please select Arbitrum in your wallet.') // todo hardcoded arb
|
|
console.error('wallet chain error', x)
|
|
// store.chainId = store.chainInfo[]
|
|
throw x
|
|
}
|
|
else {
|
|
console.error('wallet error')
|
|
throw x
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export async function connectWallet() {
|
|
// eth_getaccounts
|
|
const s = useStore()
|
|
await s.provider.getSigner()
|
|
}
|
|
|
|
let pendingOrders = []
|
|
|
|
|
|
function discoverVaults(owner) {
|
|
const s = useStore()
|
|
console.log('discoverVaults', owner)
|
|
if( owner === null )
|
|
s.vaults = []
|
|
else
|
|
doDiscoverVaults.invoke(owner)
|
|
}
|
|
|
|
const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50, false)
|
|
async function _discoverVaults(owner) {
|
|
console.log('_discoverVaults',owner)
|
|
const result = []
|
|
// todo multi-vault scan
|
|
const num = 0
|
|
const addr = vaultAddress(owner, num)
|
|
console.log(`vault ${num} at`, addr)
|
|
const s = useStore()
|
|
const vault = new ethers.Contract(addr, vaultAbi, s.provider)
|
|
let version = -1
|
|
try {
|
|
version = await vault.version();
|
|
if( Number(version) === 1 ) {
|
|
console.log(`found vault ${num} at ${addr}`)
|
|
result.push(addr)
|
|
}
|
|
else
|
|
console.error(`bad vault version ${version}`)
|
|
}
|
|
catch (e) {
|
|
console.log(`no vault ${num}`, e)
|
|
}
|
|
if( s.account === owner ) { // double-check the account since it could have changed during our await
|
|
s.vaults = result
|
|
if( pendingOrders.length )
|
|
if( result.length )
|
|
flushOrders(result[0])
|
|
else
|
|
ensureVault2(s.chainId.value, owner, 0)
|
|
}
|
|
}
|
|
|
|
|
|
export function ensureVault() {
|
|
const s = useStore()
|
|
const owner = s.account;
|
|
console.log('ensureVault', s.chainId.value, owner)
|
|
if( !owner )
|
|
return
|
|
ensureVault2(s.chainId.value, owner, 0)
|
|
}
|
|
|
|
|
|
export function ensureVault2(chainId, owner, num) {
|
|
console.log('ensureVault2', chainId, owner, num)
|
|
if( !chainId ) {
|
|
console.log('cannot create vault: no chain selected')
|
|
return
|
|
}
|
|
if( !owner ) {
|
|
console.log('cannot create vault: no account logged in')
|
|
return
|
|
}
|
|
ensureVaultRoutine.invoke(chainId, owner, num)
|
|
}
|
|
|
|
async function doEnsureVault(chainId, owner, num) {
|
|
await _discoverVaults(owner)
|
|
if( !useStore().vaults[num] ) {
|
|
console.log(`requesting vault ${owner} ${num}`)
|
|
socket.emit('ensureVault', chainId, owner, num)
|
|
}
|
|
await sleep(5)
|
|
}
|
|
|
|
const ensureVaultRoutine = new SingletonCoroutine(doEnsureVault, 100, false)
|
|
|
|
|
|
export async function pendOrder(order) {
|
|
console.log('order', JSON.stringify(order))
|
|
const s = useStore()
|
|
if (!s.vaults.length) {
|
|
pendingOrders.push(order)
|
|
ensureVault()
|
|
}
|
|
else {
|
|
const vault = s.vaults[0];
|
|
pendOrderAsTransaction(vault, order)
|
|
}
|
|
}
|
|
|
|
|
|
export async function cancelOrder(vault, orderIndex) {
|
|
console.log('cancel order', vault, orderIndex)
|
|
pendTransaction(async (signer)=> {
|
|
const contract = contractOrNull(vault, vaultAbi, signer)
|
|
if( contract === null ) {
|
|
console.error('vault contract was null while canceling order', vault, orderIndex)
|
|
return null
|
|
}
|
|
return await contract.cancelOrder(orderIndex)
|
|
})
|
|
}
|
|
|
|
export async function cancelAll(vault) {
|
|
pendTransaction(async (signer)=> {
|
|
const contract = contractOrNull(vault, vaultAbi, signer)
|
|
if( contract === null ) {
|
|
console.error('vault contract was null while canceling order', vault)
|
|
return null
|
|
}
|
|
return await contract.cancelAll()
|
|
})
|
|
}
|
|
|
|
export function flushOrders(vault) {
|
|
for( const order of pendingOrders )
|
|
pendOrderAsTransaction(vault, order)
|
|
pendingOrders = []
|
|
flushTransactions()
|
|
}
|
|
|
|
|
|
function pendOrderAsTransaction(vault, order) {
|
|
pendTransaction(async (signer)=> {
|
|
const contract = contractOrNull(vault, vaultAbi, signer)
|
|
if( contract === null ) {
|
|
console.error('vault contract was null while sending order transaction', vault)
|
|
return null
|
|
}
|
|
return await contract.placeOrder(order)
|
|
})
|
|
}
|
|
|
|
|
|
export function pendTransaction(sender) {
|
|
const s = useStore()
|
|
s.transactionSenders.push(sender)
|
|
flushTransactions()
|
|
}
|
|
|
|
|
|
let flushing = 0 // semaphore
|
|
|
|
export function flushTransactions() {
|
|
flushing++
|
|
if( flushing === 1 )
|
|
// noinspection JSIgnoredPromiseFromCall
|
|
asyncFlushTransactions()
|
|
}
|
|
|
|
export async function asyncFlushTransactions() {
|
|
let counter
|
|
do {
|
|
counter = flushing
|
|
await asyncFlushTransactions2()
|
|
} while( flushing > counter)
|
|
flushing = 0
|
|
}
|
|
|
|
export async function asyncFlushTransactions2() {
|
|
// todo rework into flushTransactions()
|
|
const s = useStore()
|
|
if( s.provider === null ) {
|
|
console.log('warning: asyncFlushOrders() cancelled due to null provider')
|
|
return
|
|
}
|
|
const senders = s.transactionSenders
|
|
if (!senders.length)
|
|
return
|
|
console.log(`flushing ${senders.length} transactions`)
|
|
let signer
|
|
try {
|
|
signer = await s.provider.getSigner();
|
|
} catch (e) {
|
|
// {
|
|
// "code": -32002,
|
|
// "message": "Already processing eth_requestAccounts. Please wait."
|
|
// }
|
|
console.log('signer denied')
|
|
return
|
|
}
|
|
for (const sender of senders)
|
|
doSendTransaction(sender, signer)
|
|
}
|
|
|
|
function doSendTransaction(sender, signer) {
|
|
const s = useStore();
|
|
sender(signer).then((tx)=>{
|
|
console.log('sent transaction', tx)
|
|
s.removeTransactionSender(sender)
|
|
tx.wait().then((tr)=>console.log('tx receipt',tr))
|
|
}).catch((e)=>{
|
|
if( e.info?.error?.code === 4001 ) {
|
|
console.log(`user rejected transaction`)
|
|
s.removeTransactionSender(sender)
|
|
}
|
|
else {
|
|
if( e.reason && e.info )
|
|
console.error('error sending transaction', e.reason, e.info)
|
|
else
|
|
console.error('error sending transaction', e)
|
|
s.removeTransactionSender(sender)
|
|
// todo retry?
|
|
}
|
|
})
|
|
}
|