import {ethers} from "ethers"; import {setProvider, 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 // touch the chainId last. will cause any clients of the store's provider getter to refresh store.account = null const provider = new ethers.BrowserProvider(window.ethereum, chainId); setProvider(provider, chainId) provider.listAccounts().then((accounts)=>changeAccounts(accounts.map((a)=>a.address))) } } function changeAccounts(accounts) { console.log('change accounts', accounts) const store = useStore() if( accounts.length === 0 ) { store.account = null store.vaults = [] store.vaultBalances = {} } else { const addr = accounts[0] const store = useStore() store.account = addr console.log('set store.account to', addr, store.account) discoverVaults() flushTransactions() socket.emit('address', store.chainId, addr) } console.log('changeAccounts ended') } function onAccountsChanged(accounts) { const store = useStore() if (accounts.length === 0 || accounts[0] !== store.account) changeAccounts(accounts); } export async function watchWallet() { const chainId = (await new ethers.BrowserProvider(window.ethereum).getNetwork()).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() { const s = useStore() const owner = s.account if( owner === null ) s.vaults = [] else _discoverVaults(owner).then((result)=>{ console.log('read store.account', s.account) 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 ensureVault() } }) } async function _discoverVaults(owner) { const result = [] // todo multi-vault scan const addr = vaultAddress(owner, 0) const s = useStore() const vault = new ethers.Contract(addr, vaultAbi, s.provider) let version = -1 try { version = await vault.version(); if( version === 1 ) result.push(addr) else console.error(`bad vault version ${version}`) } catch (e) { } return result } export function ensureVault() { const s = useStore() if( !s.chain ) { console.log('cannot create vault: no chain selected') return } if( !s.account ) { console.log('cannot create vault: no account logged in') return } socket.emit('ensureVault', s.chainId, s.account, 0) } 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 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() } export function flushTransactions() { // noinspection JSIgnoredPromiseFromCall asyncFlushTransactions() } export async function asyncFlushTransactions() { // 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 ${s.transactionSenders.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? } }) }