wallet connection fixes

This commit is contained in:
Tim
2024-04-11 19:50:28 -04:00
parent b0daa446b3
commit db2feb8842
9 changed files with 188 additions and 114 deletions

View File

@@ -5,6 +5,7 @@ import {uniswapV3PoolAddress} from "@/blockchain/uniswap.js";
import {ethers, FixedNumber} from "ethers"; import {ethers, FixedNumber} from "ethers";
import {uniswapV3PoolAbi} from "@/blockchain/abi.js"; import {uniswapV3PoolAbi} from "@/blockchain/abi.js";
import {subOHLCs} from "@/blockchain/ohlcs.js"; import {subOHLCs} from "@/blockchain/ohlcs.js";
import {provider} from "@/blockchain/wallet.js";
const subscriptionCounts = {} // key is route and value is a subscription counter const subscriptionCounts = {} // key is route and value is a subscription counter
export const WIDE_PRICE_FORMAT = {decimals:38, width:512, signed:false}; // 38 decimals is 127 bits export const WIDE_PRICE_FORMAT = {decimals:38, width:512, signed:false}; // 38 decimals is 127 bits
@@ -79,7 +80,6 @@ async function getPriceForRoute(route) {
if( route.exchange === Exchange.UniswapV3 ) { if( route.exchange === Exchange.UniswapV3 ) {
const addr = uniswapV3PoolAddress(route.chainId, route.token0.address, route.token1.address, route.fee) const addr = uniswapV3PoolAddress(route.chainId, route.token0.address, route.token1.address, route.fee)
const store = useStore(); const store = useStore();
const provider = store.provider;
if( provider === null ) { if( provider === null ) {
console.error('provider was null during getPriceForRoute') console.error('provider was null during getPriceForRoute')
return null return null

View File

@@ -2,6 +2,7 @@ import {Exchange} from "@/blockchain/orderlib.js";
import {useOrderStore, useStore} from "@/store/store.js"; import {useOrderStore, useStore} from "@/store/store.js";
import {queryHelperContract} from "@/blockchain/contract.js"; import {queryHelperContract} from "@/blockchain/contract.js";
import {SingletonCoroutine} from "@/misc.js"; import {SingletonCoroutine} from "@/misc.js";
import {provider} from "@/blockchain/wallet.js";
export async function findRoute(helper, chainId, tokenA, tokenB) { export async function findRoute(helper, chainId, tokenA, tokenB) {
@@ -40,7 +41,7 @@ async function componentFindRoute() {
os.routesPending = true os.routesPending = true
try { try {
console.log('getting query helper') console.log('getting query helper')
const helper = await queryHelperContract(s.helper, s.provider) const helper = await queryHelperContract(s.helper, provider)
if (!helper) { if (!helper) {
console.log('no helper') console.log('no helper')
} else { } else {

View File

@@ -3,6 +3,7 @@ import {useStore} from "@/store/store.js";
import {erc20Abi} from "@/blockchain/abi.js"; import {erc20Abi} from "@/blockchain/abi.js";
import {ethers} from "ethers"; import {ethers} from "ethers";
import {metadata, metadataMap} from "@/version.js"; import {metadata, metadataMap} from "@/version.js";
import {provider} from "@/blockchain/wallet.js";
// synchronous version may return null but will trigger a lookup // synchronous version may return null but will trigger a lookup
@@ -58,12 +59,12 @@ export async function addExtraToken(chainId, addr) {
resolve(info) resolve(info)
} }
else { else {
if( s.provider===null ) { if( provider===null ) {
console.log('warning: token lookup cancelled due to null provider', addr) console.log('warning: token lookup cancelled due to null provider', addr)
resolve(null) resolve(null)
} }
else { else {
const token = new ethers.Contract(addr, erc20Abi, s.provider) const token = new ethers.Contract(addr, erc20Abi, provider)
Promise.all( [token.name(), token.symbol(), token.decimals()] ).then((name,symbol,decimals)=>{ Promise.all( [token.name(), token.symbol(), token.decimals()] ).then((name,symbol,decimals)=>{
info = { info = {
a: addr, a: addr,

View File

@@ -1,4 +1,4 @@
import {ethers} from "ethers"; import {BrowserProvider, ethers} from "ethers";
import {useStore} from "@/store/store"; import {useStore} from "@/store/store";
import {socket} from "@/socket.js"; import {socket} from "@/socket.js";
import {contractOrNull, vaultAddress} from "@/blockchain/contract.js"; import {contractOrNull, vaultAddress} from "@/blockchain/contract.js";
@@ -9,6 +9,9 @@ import {ref} from "vue";
import {metadataMap} from "@/version.js"; import {metadataMap} from "@/version.js";
export let provider = null
export const useWalletStore = defineStore('wallet', ()=>{ export const useWalletStore = defineStore('wallet', ()=>{
// this is what the wallet is logged into. it could be different than the application's store.chainId. // this is what the wallet is logged into. it could be different than the application's store.chainId.
const chainId = ref(0) const chainId = ref(0)
@@ -31,6 +34,7 @@ export const useWalletStore = defineStore('wallet', ()=>{
export function onChainChanged(chainId) { export function onChainChanged(chainId) {
console.log('onChainChanged', chainId)
chainId = Number(chainId) chainId = Number(chainId)
const store = useStore() const store = useStore()
const ws = useWalletStore() const ws = useWalletStore()
@@ -41,6 +45,8 @@ export function onChainChanged(chainId) {
console.log('app chain changed', chainId) console.log('app chain changed', chainId)
store.chainId = chainId store.chainId = chainId
store.account = null store.account = null
provider = new BrowserProvider(window.ethereum, chainId)
updateAccounts(chainId, provider)
} }
else { else {
console.log('app chain NOT changed') console.log('app chain NOT changed')
@@ -77,6 +83,7 @@ function changeAccounts(chainId, accounts) {
} }
function onAccountsChanged(accounts) { function onAccountsChanged(accounts) {
console.log('onAccountsChanged', accounts)
const store = useStore() const store = useStore()
const ws = useWalletStore() const ws = useWalletStore()
if (accounts.length === 0 || accounts[0] !== store.account) if (accounts.length === 0 || accounts[0] !== store.account)
@@ -84,11 +91,17 @@ function onAccountsChanged(accounts) {
} }
export function detectChain() { export function detectChain() {
try {
window.ethereum.on('chainChanged', onChainChanged);
window.ethereum.on('accountsChanged', onAccountsChanged);
}
catch (e) {
console.log('Could not connect change hooks to wallet', e)
return
}
new ethers.BrowserProvider(window.ethereum).getNetwork().then((network)=>{ new ethers.BrowserProvider(window.ethereum).getNetwork().then((network)=>{
const chainId = network.chainId const chainId = network.chainId
onChainChanged(chainId) onChainChanged(chainId)
window.ethereum.on('chainChanged', onChainChanged);
window.ethereum.on('accountsChanged', onAccountsChanged);
}) })
} }
@@ -125,50 +138,50 @@ const errorHandlingProxy = {
} }
export async function connectProvider(chainId) {
console.log('connectProvider', chainId)
try {
return new ethers.BrowserProvider(window.ethereum, chainId)
}
catch (e) {
console.log('provider error', e)
}
return null
}
export async function connectWallet(chainId) { export async function connectWallet(chainId) {
console.log('connectWallet', chainId) console.log('connectWallet', chainId)
const ws = useWalletStore() await switchChain(chainId)
if (ws.chainId !== chainId && !await switchChain(chainId)) if (provider!==null) {
return null
const p = await connectProvider(chainId)
if (p!==null) {
try { try {
console.log('getSigner') console.log('getSigner')
return await p.getSigner(); await provider.getSigner()
await updateAccounts(chainId, provider)
} }
catch (e) { catch (e) {
if (e.reason!=='rejected') if (e.reason!=='rejected')
console.error(e, e.reason) console.error(e, e.reason)
return null
} }
} }
else
console.error('provider was null after chain switch')
} }
export async function switchChain(chainId) { export async function switchChain(chainId) {
if (useWalletStore().chainId === chainId) if (useWalletStore().chainId === chainId)
return true return
try {
await window.ethereum.request({ await window.ethereum.request({
"method": "wallet_switchEthereumChain", "method": "wallet_switchEthereumChain",
"params": [{"chainId": '0x' + chainId.toString(16)}] "params": [{"chainId": '0x' + chainId.toString(16)}]
}) })
return true }
} catch (e) {
return false
export async function addTestnet() {
const info = {
"chainId": "0x539",
"chainName": "Dexorder Alpha Testnet",
"rpcUrls": ["https://rpc.alpha.dexorder.trade"],
"nativeCurrency": {
"name": "Test Ethereum",
"symbol": "TETH",
"decimals": 18
} }
};
await window.ethereum.request({
"method": "wallet_addEthereumChain",
"params": [info]
});
} }
@@ -198,7 +211,7 @@ async function _discoverVaults(owner) {
s.vaults = [] s.vaults = []
return return
} }
const provider = s.provider; console.log('provider', provider)
if (!provider) { if (!provider) {
console.log('No provider') console.log('No provider')
return // do not change whatever was already found return // do not change whatever was already found
@@ -350,11 +363,20 @@ function pendOrderAsTransaction(pend) {
console.error('vault contract was null while sending order transaction', pend.vault) console.error('vault contract was null while sending order transaction', pend.vault)
return null return null
} }
if (!await switchChain(pend.chainId)) { try {
await switchChain(pend.chainId)
}
catch (e) {
if(e.code===4001) {
console.log('user refused chain switch') console.log('user refused chain switch')
pend.state = PendingOrderState.Rejected pend.state = PendingOrderState.Rejected
return null return null
} }
else {
console.error('Unknown error while switching chain to pend order', e)
return null
}
}
console.log('placing order', pend.id) console.log('placing order', pend.id)
const tx = await contract.placeDexorder(pend.order) // todo update status const tx = await contract.placeDexorder(pend.order) // todo update status
pend.tx = tx pend.tx = tx
@@ -393,7 +415,7 @@ export function flushTransactions() {
async function asyncFlushTransactions() { async function asyncFlushTransactions() {
const s = useStore() const s = useStore()
if( s.provider === null ) { if( provider === null ) {
console.log('warning: asyncFlushOrders() cancelled due to null provider') console.log('warning: asyncFlushOrders() cancelled due to null provider')
return return
} }
@@ -403,7 +425,7 @@ async function asyncFlushTransactions() {
console.log(`flushing ${senders.length} transactions`) console.log(`flushing ${senders.length} transactions`)
let signer let signer
try { try {
signer = await s.provider.getSigner(); signer = await provider.getSigner();
} catch (e) { } catch (e) {
// { // {
// "code": -32002, // "code": -32002,

View File

@@ -7,7 +7,7 @@
:icon="error?'mdi-close-box-outline':copied?'mdi-check-circle-outline':'mdi-content-copy'" :icon="error?'mdi-close-box-outline':copied?'mdi-check-circle-outline':'mdi-content-copy'"
:text="showText?text:''" :text="showText?text:''"
/> />
<slot/> <slot>{{text}}</slot>
</span> </span>
</template> </template>
<span>Copied!</span> <span>Copied!</span>

View File

@@ -10,20 +10,24 @@
<v-btn prepend-icon="mdi-reload" text="Reload After Installing Wallet" @click="reload"/> <v-btn prepend-icon="mdi-reload" text="Reload After Installing Wallet" @click="reload"/>
</p> </p>
</div> </div>
<!--
<div v-if="walletOk && !providerOk"> <div v-if="walletOk && !providerOk">
<p> <p>
Please log in to your crypto wallet. Please switch to a supported blockchain.
</p> </p>
<v-btn prepend-icon="mdi-power" text="Connect Wallet" @click="connectWallet"/> <v-btn prepend-icon="mdi-power" text="Switch Blockchain" @click="switchChain(s.chainId)"/>
</div> </div>
<div v-if="walletOk && chainOk && !providerOk"> -->
<!-- todo Alpha Live <div v-if="walletOk && !providerOk">
TODO
<!--
&lt;!&ndash; todo Alpha Live
<v-card-title><v-icon icon="mdi-reload-alert" color="warning"/> Change Blockchain</v-card-title> <v-card-title><v-icon icon="mdi-reload-alert" color="warning"/> Change Blockchain</v-card-title>
<v-card-text> <v-card-text>
Dexorder works only with <blockchain chain-id="42161"/>. Please switch to the Dexorder works only with <blockchain chain-id="42161"/>. Please switch to the
<blockchain chain-id="42161"/> blockchain in your wallet. <blockchain chain-id="42161"/> blockchain in your wallet.
</v-card-text> </v-card-text>
--> &ndash;&gt;
<h2><v-icon icon="mdi-hand-wave" color="warning"/>&nbsp;Welcome to Dexorder Alpha!</h2> <h2><v-icon icon="mdi-hand-wave" color="warning"/>&nbsp;Welcome to Dexorder Alpha!</h2>
<p> <p>
This alpha test runs on the Dexorder Testnet blockchain, which gives you free testnet tokens to trade. This alpha test runs on the Dexorder Testnet blockchain, which gives you free testnet tokens to trade.
@@ -52,54 +56,42 @@
Open Metamask again and select the "Dexorder Alpha" blockchain for use with this website. Open Metamask again and select the "Dexorder Alpha" blockchain for use with this website.
</li> </li>
</ol> </ol>
-->
</div> </div>
</template> </template>
<script setup> <script setup>
import {useStore} from "@/store/store"; import {useStore} from "@/store/store";
import PhoneCard from "@/components/PhoneCard.vue"; import {switchChain, useWalletStore} from "@/blockchain/wallet.js";
import {connectWallet} from "@/blockchain/wallet.js"; import {computed, ref, watchEffect} from "vue";
import {computed, watch} from "vue";
import Blockchain from "@/components/Blockchain.vue";
const s = useStore() const s = useStore()
const ws = useWalletStore()
const walletOk = typeof window.ethereum !== 'undefined' const walletOk = typeof window.ethereum !== 'undefined'
const providerOk = computed(()=>s.provider!==null) const providerOk = computed(()=>ws.chainId === s.chainId)
const chainOk = computed(()=>providerOk.value && s.helper!==null) const ok = computed(()=>walletOk && providerOk.value)
const ok = computed(()=>walletOk && providerOk.value && chainOk.value) const connecting = ref(false)
watchEffect(()=>{
if( !providerOk.value && connecting.value ) {
try {
switchChain(s.chainId)
}
catch (e) {
console.log('switchChain failed', e)
if (e.code===-9999999) {
// todo
addTestnet()
}
}
}
})
function reload() { function reload() {
window.location.reload() window.location.reload()
} }
async function addChain() {
await window.ethereum.request({
"method": "wallet_addEthereumChain",
"params": [
{
"chainId": "0x539",
"chainName": "Dexorder Alpha Testnet",
"rpcUrls": ["https://rpc.alpha.dexorder.trade"],
"nativeCurrency": {
"name": "Test Ethereum",
"symbol": "TETH",
"decimals": 18
}
}
]
});
}
watch([providerOk, chainOk], async () => {
console.log('checking chain')
if (walletOk && providerOk.value && !chainOk.value) {
console.log('switching chain')
const result = await window.ethereum.request({
"method": "wallet_switchEthereumChain",
"params": [{"chainId": '0x' + Object.keys(metadata)[0].toString(16)}]
});
console.log('chain switch result', result)
}
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,26 +1,94 @@
<template> <template>
<needs-provider>
<slot v-if="ok" v-bind="$props"/> <slot v-if="ok" v-bind="$props"/>
<div v-if="!ok"> <div v-if="!ok">
<btn icon="mdi-wallet-outline" color="warning" variant="outlined" @click="connect" :disabled="disabled" text="Connect Wallet"/> <v-card-title><v-icon icon="mdi-hand-wave" color="grey"/>&nbsp;Welcome to Dexorder Alpha!</v-card-title>
<v-card-text>
This alpha test runs on the Dexorder Testnet blockchain, which gives you free testnet tokens to trade.
</v-card-text>
<btn icon="mdi-wallet-outline" color="warning" variant="outlined" @click="connect" :disabled="disabled"
text="Connect Wallet"/>
<!-- todo Alpha Live
<v-card-title><v-icon icon="mdi-reload-alert" color="warning"/> Change Blockchain</v-card-title>
<v-card-text>
Dexorder works only with <blockchain chain-id="42161"/>. Please switch to the
<blockchain chain-id="42161"/> blockchain in your wallet.
</v-card-text>
-->
<v-card v-if="needsNetwork" rounded="0">
<v-card-text>
<table id="manualsetup">
<thead>
<tr><th colspan="4">Manual Setup</th></tr>
</thead>
<tbody>
<tr><td><b>Network Name:</b></td><td><copy-button text="Dexorder Alpha Testnet"/></td></tr>
<tr><td><b>New RPC URL:</b></td><td><copy-button text="https://rpc.alpha.dexorder.trade"/></td></tr>
<tr><td><b>Chain ID:</b></td><td><copy-button text="1337"/></td></tr>
<tr><td><b>Currency Symbol:</b></td><td><copy-button text="TETH"/></td></tr>
</tbody>
</table>
<!--
<ul class="ml-6">
<li> Dexorder Alpha Testnet</li>
<li><b>New RPC URL:</b> </li>
<li><b>Chain ID:</b> 1337</li>
<li><b>Currency Symbol:</b> TETH</li>
</ul>
<ol class="ml-6">
<li>Open Metamask</li>
<li>Click in the upper-left to choose a Network</li>
<li>Click the "Add Network" button</li>
<li>Choose "Add a Network Manually"</li>
<li>Enter the following information:
</li>
<li>
Save the private test network
</li>
<li>
Open Metamask again and select the "Dexorder Alpha" blockchain for use with this website.
</li>
</ol>
-->
</v-card-text>
</v-card>
</div> </div>
</needs-provider>
</template> </template>
<script setup> <script setup>
import {useStore} from "@/store/store"; import {useStore} from "@/store/store";
import NeedsProvider from "@/components/NeedsProvider.vue"; import NeedsProvider from "@/components/NeedsProvider.vue";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import {connectWallet} from "@/blockchain/wallet.js"; import {addTestnet, connectWallet, switchChain, useWalletStore} from "@/blockchain/wallet.js";
import Btn from "@/components/Btn.vue"; import Btn from "@/components/Btn.vue";
import CopyButton from "@/components/CopyButton.vue";
const s = useStore() const s = useStore()
const ws = useWalletStore()
const providerOk = computed(()=>s.chainId===ws.chainId)
const ok = computed(()=>s.account!==null) const ok = computed(()=>s.account!==null)
const needsNetwork = ref(false)
const disabled = ref(false) const disabled = ref(false)
async function connect() { async function connect() {
disabled.value = true disabled.value = true
try { try {
try {
await switchChain(s.chainId)
}
catch (e) {
if (e.code===4902) {
try {
await addTestnet()
needsNetwork.value = false
}
catch (e) {
needsNetwork.value = true
return
}
}
else
console.log('wallet connect failure',e)
}
await connectWallet(s.chainId) await connectWallet(s.chainId)
} }
finally { finally {
@@ -32,5 +100,10 @@ async function connect() {
<style scoped lang="scss"> <style scoped lang="scss">
@use "src/styles/vars" as *; @use "src/styles/vars" as *;
#manualsetup td {
text-align: right;
}
#manualsetup tr td:last-child {
padding-left: 1em;
}
</style> </style>

View File

@@ -266,6 +266,7 @@ const orders = computed(()=>{
st.token1 = o.tokenIn < o.tokenOut ? o.tokenOut : o.tokenIn st.token1 = o.tokenIn < o.tokenOut ? o.tokenOut : o.tokenIn
} }
} }
result.sort((a,b)=>b.start-a.start)
console.log('orders', result) console.log('orders', result)
return result return result
}) })

View File

@@ -3,8 +3,6 @@ import {defineStore} from 'pinia'
import {knownTokens} from "@/knownTokens.js"; import {knownTokens} from "@/knownTokens.js";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import {version} from "@/version.js"; import {version} from "@/version.js";
import {ethers} from "ethers";
import {updateAccounts} from "@/blockchain/wallet.js";
// USING THE STORE: // USING THE STORE:
// //
@@ -70,9 +68,9 @@ export const useStore = defineStore('app', ()=> {
_chainId.value = v _chainId.value = v
account.value = null account.value = null
} }
if (changed || _provider.value === null) { if (changed || providerRef.value === null) {
console.log('invalidating provider') console.log('invalidating provider')
_provider.value = UNKNOWN_PROVIDER providerRef.value = UNKNOWN_PROVIDER
} }
} }
}) })
@@ -82,22 +80,8 @@ export const useStore = defineStore('app', ()=> {
}) })
const chain = computed(() => !_chainId.value ? null : (_chainInfo.value[_chainId.value] || null)) const chain = computed(() => !_chainId.value ? null : (_chainInfo.value[_chainId.value] || null))
// this provider is for the app's chainId not the wallet's chainId. // this provider is for the app's chainId not the wallet's chainId.
let _provider = UNKNOWN_PROVIDER // null indicates a known-unavailable provider whereas undefined means provider status is unknown const providerRef = ref(null)
const provider = computed(()=>{ const provider = computed(()=>providerRef.value)
if (_provider===UNKNOWN_PROVIDER) {
// unknown provider status
try {
_provider = new ethers.BrowserProvider(window.ethereum, _chainId.value);
updateAccounts(_chainId.value, _provider)
}
catch (e) {
console.error('could not get provider', _chainId.value, e)
_provider = null
}
}
// if _provider is null then it is known to be an unavailable chain
return _provider
})
const vaultInitCodeHash = computed(() => !chain.value ? null : chain.value.vaultInitCodeHash) const vaultInitCodeHash = computed(() => !chain.value ? null : chain.value.vaultInitCodeHash)
const account = ref(null) const account = ref(null)
const vaults = ref([]) const vaults = ref([])
@@ -140,8 +124,8 @@ export const useStore = defineStore('app', ()=> {
setInterval(()=>clock.value=timestamp(), 10*1000) setInterval(()=>clock.value=timestamp(), 10*1000)
return { return {
nav, chainId, chainInfo, chain, provider, vaultInitCodeHash, account, vaults, transactionSenders, errors, nav, chainId, chainInfo, chain, provider, providerRef, vaultInitCodeHash, account, vaults, transactionSenders,
extraTokens, poolPrices, vaultBalances, orders, vault, tokens, factory, helper, mockenv, mockCoins, errors, extraTokens, poolPrices, vaultBalances, orders, vault, tokens, factory, helper, mockenv, mockCoins,
removeTransactionSender, error, closeError, addToken, clock, balances, removeTransactionSender, error, closeError, addToken, clock, balances,
} }
}) })