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"; 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() if( owner === null ) s.vaults = [] else _discoverVaults(owner).then((result)=>{ 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) } }) } async function _discoverVaults(owner) { const result = [] // todo multi-vault scan const num = 0 const addr = vaultAddress(owner, num) const s = useStore() const vault = new ethers.Contract(addr, vaultAbi, s.provider) let version = -1 try { version = await vault.version(); if( 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}`) } return result } 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 } socket.emit('ensureVault', chainId, owner, num) } 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) { 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? } }) }