wallet flow; new faucet; placing chart orders works!

This commit is contained in:
Tim
2024-03-25 21:04:14 -04:00
parent 6ee442d7ec
commit d11ad7cf40
21 changed files with 444 additions and 201 deletions

View File

@@ -36,7 +36,7 @@ async function componentFindRoute() {
os.routes = [] os.routes = []
if (!tokenA || !tokenB) if (!tokenA || !tokenB)
return return
console.log('finding route', s.chainId.value, tokenA, tokenB) console.log('finding route', s.chainId, tokenA, tokenB)
os.routesPending = true os.routesPending = true
try { try {
console.log('getting query helper') console.log('getting query helper')
@@ -44,7 +44,7 @@ async function componentFindRoute() {
if (!helper) { if (!helper) {
console.log('no helper') console.log('no helper')
} else { } else {
const result = await findRoute(helper, s.chainId.value, tokenA, tokenB) const result = await findRoute(helper, s.chainId, tokenA, tokenB)
console.log('found route', result) console.log('found route', result)
os.routes = result os.routes = result
} }

View File

@@ -6,6 +6,8 @@ import {ethers} from "ethers";
// synchronous version may return null but will trigger a lookup // synchronous version may return null but will trigger a lookup
export function token(addr) { export function token(addr) {
// todo deprecated. use metadataMap[chainId][addr]
console.warn('token() is deprecated')
console.log('token', addr) console.log('token', addr)
if( !addr ) { if( !addr ) {
// console.log('ignoring call to token', addr) // console.log('ignoring call to token', addr)
@@ -23,6 +25,8 @@ export function token(addr) {
// async version doesnt return until it has a token value // async version doesnt return until it has a token value
export async function getToken(addr) { export async function getToken(addr) {
// todo deprecated. use metadataMap[chainId][addr]
console.warn('getToken() is deprecated')
const s = useStore() const s = useStore()
if (!(addr in s.tokens)) if (!(addr in s.tokens))
await addExtraToken(addr) await addExtraToken(addr)

View File

@@ -6,20 +6,26 @@ import {vaultAbi} from "@/blockchain/abi.js";
import {SingletonCoroutine} from "@/misc.js"; import {SingletonCoroutine} from "@/misc.js";
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import {ref} from "vue"; import {ref} from "vue";
import {metadata, metadataMap} from "@/version.js";
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.
const chainId = ref(0)
// Pending Order Format // Pending Order Format
// { // {
// chainId: 31337, // must never be null, even if no wallet plugin exists. chosen by app, not wallet. // chainId: 31337, // must never be null, even if no wallet plugin exists. chosen by app, not wallet.
// placementTime: Date.now(), // placementTime: Date.now(),
// submitted: false // true after the order has started to be sent to the wallet
// tx: null // transaction ID
// vault: '0x...', // or null if account not logged in yet // vault: '0x...', // or null if account not logged in yet
// order: {tokenIn:..., tokenOut:..., ...} // blockchain binary order object // order: {tokenIn:..., tokenOut:..., ...} // blockchain binary order object
// } // }
const pendingOrders = ref([]) const pendingOrders = ref([])
return { return {
pendingOrders, chainId, pendingOrders,
} }
}) })
@@ -27,27 +33,31 @@ export const useWalletStore = defineStore('wallet', ()=>{
export function onChainChanged(chainId) { export function onChainChanged(chainId) {
chainId = Number(chainId) chainId = Number(chainId)
const store = useStore() const store = useStore()
if( chainId !== store.chainId.value ) { const ws = useWalletStore()
// todo check pending orders and confirm cancellation if( chainId !== ws.chainId ) {
console.log('chain changed', chainId) console.log('chain changed', chainId)
store.chainId.value = chainId ws.chainId = chainId
store.account = null if (chainId.toString() in metadataMap) {
const provider = new ethers.BrowserProvider(window.ethereum, chainId); console.log('app chain changed', chainId)
store.provider = provider store.chainId = chainId
provider.listAccounts().then((accounts)=>changeAccounts(chainId, accounts.map((a)=>a.address))) store.account = null
}
} }
} }
export function updateAccounts(chainId, provider) {
provider.listAccounts().then((accounts) => changeAccounts(chainId, accounts.map((a) => a.address)))
}
function changeAccounts(chainId, accounts) { function changeAccounts(chainId, accounts) {
// console.log('changeAccounts', chainId, accounts) // this is a notification from the wallet that the user selected a different blockchain. that chain may or may not
// be supported. we store this value in walletStore.chainId, which may or may not be the same as
// the application's useStore().chainId
console.log('changeAccounts', chainId, accounts)
const store = useStore() const store = useStore()
if( accounts.length === 0 ) { if (chainId === store.chainId && accounts.length) {
console.log('account logged out')
store.account = null
store.vaults = []
store.vaultBalances = {}
}
else {
const addr = accounts[0] const addr = accounts[0]
console.log('account logged in', addr) console.log('account logged in', addr)
store.account = addr store.account = addr
@@ -55,12 +65,19 @@ function changeAccounts(chainId, accounts) {
flushTransactions() flushTransactions()
socket.emit('address', chainId, addr) socket.emit('address', chainId, addr)
} }
else {
console.log('account logged out')
store.account = null
store.vaults = []
store.vaultBalances = {}
}
} }
function onAccountsChanged(accounts) { function onAccountsChanged(accounts) {
const store = useStore() const store = useStore()
const ws = useWalletStore()
if (accounts.length === 0 || accounts[0] !== store.account) if (accounts.length === 0 || accounts[0] !== store.account)
changeAccounts(store.chainId.value, accounts); changeAccounts(ws.chainId, accounts);
} }
export function detectChain() { export function detectChain() {
@@ -105,8 +122,49 @@ const errorHandlingProxy = {
} }
export async function connectProvider(chainId) {
console.log('connecting provider to chainId', 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) {
await new ethers.BrowserProvider(window.ethereum, chainId).getSigner(); console.log('connectWallet', chainId)
const ws = useWalletStore()
if (ws.chainId !== chainId && !await switchChain(chainId))
return null
const p = await connectProvider(chainId)
if (p!==null) {
try {
return await p.getSigner();
}
catch (e) {
if (e.reason!=='rejected')
console.error(e, e.reason)
return null
}
}
}
export async function switchChain(chainId) {
if (useWalletStore().chainId === chainId)
return true
try {
await window.ethereum.request({
"method": "wallet_switchEthereumChain",
"params": [{"chainId": '0x' + chainId.toString(16)}]
})
return true
} catch (e) {
return false
}
} }
@@ -123,7 +181,7 @@ const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50, false)
async function _discoverVaults(owner) { async function _discoverVaults(owner) {
const result = [] const result = []
const s = useStore() const s = useStore()
if( !owner || !s.chainId.value || !s.account) { if( !owner || !s.chainId || !s.account) {
s.vaults = [] s.vaults = []
return return
} }
@@ -159,7 +217,7 @@ async function _discoverVaults(owner) {
if( result.length ) if( result.length )
flushOrders(result[0]) flushOrders(result[0])
else else
ensureVault2(s.chainId.value, owner, 0) ensureVault2(s.chainId, owner, 0)
} }
} }
@@ -176,8 +234,8 @@ async function ensureVault1() {
const s = useStore() const s = useStore()
const owner = s.account; const owner = s.account;
if (owner===null) if (owner===null)
await connectWallet(s.chainId.value) await connectWallet(s.chainId)
ensureVault2(s.chainId.value, owner, 0) ensureVault2(s.chainId, owner, 0)
} }
export function ensureVault2(chainId, owner, num) { export function ensureVault2(chainId, owner, num) {
@@ -209,9 +267,10 @@ export async function pendOrder(order) {
console.log('order', JSON.stringify(order)) console.log('order', JSON.stringify(order))
const s = useStore() const s = useStore()
useWalletStore().pendingOrders.push({ useWalletStore().pendingOrders.push({
chainId: s.chainId.value, chainId: s.chainId,
placementTime: new Date(), placementTime: new Date(),
vault: s.vaults.length ? s.vaults[0] : null, vault: s.vaults.length ? s.vaults[0] : null,
submitted: false,
order order
}) })
ensureVault() ensureVault()
@@ -243,13 +302,18 @@ export async function cancelAll(vault) {
export function flushOrders(vault) { export function flushOrders(vault) {
const ws = useWalletStore(); const ws = useWalletStore();
let needsFlush = false
for( const order of ws.pendingOrders ) { for( const order of ws.pendingOrders ) {
if (order.vault === null) if (order.vault === null)
order.vault = vault order.vault = vault
pendOrderAsTransaction(order) if (!order.submitted) {
pendOrderAsTransaction(order)
order.submitted = true
needsFlush = true
}
} }
ws.pendingOrders = [] if (needsFlush)
flushTransactions() flushTransactions()
} }
@@ -260,8 +324,18 @@ function pendOrderAsTransaction(order) {
console.error('vault contract was null while sending order transaction', order.vault) console.error('vault contract was null while sending order transaction', order.vault)
return null return null
} }
if (!await switchChain(order.chainId)) {
console.log('user refused chain switch')
return null
}
console.log('placing order', order) console.log('placing order', order)
return await contract.placeOrder(order.order) // todo update status const tx = await contract.placeOrder(order.order) // todo update status
order.tx = tx
tx.wait().then((txReceipt)=>{
const ws = useWalletStore();
ws.pendingOrders = ws.pendingOrders.filter((o)=>o!==order)
})
return tx
}) })
} }
@@ -273,26 +347,15 @@ export function pendTransaction(sender) {
} }
const flushTransactionsRoutine = new SingletonCoroutine(asyncFlushTransactions,1)
let flushing = 0 // semaphore let flushing = 0 // semaphore
export function flushTransactions() { export function flushTransactions() {
flushing++ flushTransactionsRoutine.invoke()
if( flushing === 1 )
// noinspection JSIgnoredPromiseFromCall
asyncFlushTransactions()
} }
export async function asyncFlushTransactions() { 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() const s = useStore()
if( s.provider === null ) { if( s.provider === null ) {
console.log('warning: asyncFlushOrders() cancelled due to null provider') console.log('warning: asyncFlushOrders() cancelled due to null provider')
@@ -319,22 +382,21 @@ export async function asyncFlushTransactions2() {
function doSendTransaction(sender, signer) { function doSendTransaction(sender, signer) {
const s = useStore(); const s = useStore();
s.removeTransactionSender(sender)
sender(signer).then((tx)=>{ sender(signer).then((tx)=>{
console.log('sent transaction', tx) if (tx!==null) {
s.removeTransactionSender(sender) console.log('sent transaction', tx)
tx.wait().then((tr)=>console.log('tx receipt',tr)) tx.wait().then((tr)=>console.log('tx receipt',tr))
}
}).catch((e)=>{ }).catch((e)=>{
if( e.info?.error?.code === 4001 ) { if( e.info?.error?.code === 4001 ) {
console.log(`user rejected transaction`) console.log(`user rejected transaction`)
s.removeTransactionSender(sender)
} }
else { else {
if( e.reason && e.info ) if( e.reason && e.info )
console.error('error sending transaction', e.reason, e.info) console.error('error sending transaction', e.reason, e.info)
else else
console.error('error sending transaction', e) console.error('error sending transaction', e)
s.removeTransactionSender(sender)
// todo retry?
} }
}) })
} }

View File

@@ -152,7 +152,7 @@ function addSymbol(p, base, quote, inverted) {
async function getAllSymbols() { async function getAllSymbols() {
if (_symbols===null) { if (_symbols===null) {
const chainId = useStore().chainId.value; const chainId = useStore().chainId;
const md = metadata[chainId] const md = metadata[chainId]
if(!md) { if(!md) {
console.log('could not get metadata for chain', chainId) console.log('could not get metadata for chain', chainId)
@@ -340,7 +340,7 @@ export const DataFeed = {
onResetCacheNeededCallback, onResetCacheNeededCallback,
) => { ) => {
console.log('[subscribeBars]', symbolInfo, resolution, subscriberUID); console.log('[subscribeBars]', symbolInfo, resolution, subscriberUID);
const chainId = useStore().chainId.value; const chainId = useStore().chainId;
const poolAddr = useChartOrderStore().selectedPool[0]; const poolAddr = useChartOrderStore().selectedPool[0];
const period = tvResolutionToPeriodString(resolution); const period = tvResolutionToPeriodString(resolution);
subscriptions[subscriberUID] = [chainId, poolAddr, period, onRealtimeCallback, onResetCacheNeededCallback] subscriptions[subscriberUID] = [chainId, poolAddr, period, onRealtimeCallback, onResetCacheNeededCallback]

View File

@@ -1,9 +1,9 @@
<template> <template>
<v-btn :prepend-icon="icon" variant="plain" class="mx-2"> <v-btn :prepend-icon="icon" :variant="variant===undefined?'text':variant" class="mx-2">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon :color="color"></v-icon> <v-icon :color="color"></v-icon>
</template> </template>
<slot name="text">{{text}}</slot> <template v-slot>{{text}}<slot/></template>
</v-btn> </v-btn>
</template> </template>
@@ -12,7 +12,12 @@ import {useStore} from "@/store/store";
import {useAttrs} from "vue"; import {useAttrs} from "vue";
const s = useStore() const s = useStore()
const props = defineProps(['icon', 'color', 'text']) const props = defineProps({
icon: {default:null},
color: {default:null},
text: {default:null},
variant: {default:'text'},
})
const attrs = useAttrs() const attrs = useAttrs()
</script> </script>

View File

@@ -1,9 +1,11 @@
<template> <template>
<phone-card v-if="s.mockenv && s.vault" class="maxw"> <!-- <div>-->
<!--
<v-card-title><v-icon icon="mdi-faucet"/>&nbsp;Testnet Faucet</v-card-title> <v-card-title><v-icon icon="mdi-faucet"/>&nbsp;Testnet Faucet</v-card-title>
<v-card-text>The Dexorder testnet faucet will send 1 TETH (Testnet ETH) to your account, plus 10 MEH (Mock Ethernet Hardfork) and 10,000 USXD (Joke Currency XD) to your vault.</v-card-text> <v-card-text>The Dexorder testnet faucet will send 1 TETH (Testnet ETH) to your account, plus 10 MEH (Mock Ethernet Hardfork) and 10,000 USXD (Joke Currency XD) to your vault.</v-card-text>
<v-card-text>Click below to get free test tokens: </v-card-text> <v-card-text>Click below to get free test tokens: </v-card-text>
<v-card-item> <v-card-item>
-->
<!-- <!--
<v-table plain> <v-table plain>
<tbody> <tbody>
@@ -14,9 +16,9 @@
</tbody> </tbody>
</v-table> </v-table>
--> -->
<btn icon='mdi-plus' color="green" :disabled="disabled" @click="gib">GIB!</btn> <btn icon='mdi-plus' color="green" :disabled="disabled" @click="gib" :text="text"/>
</v-card-item> <!-- </v-card-item>-->
</phone-card> <!-- </div>-->
</template> </template>
<script setup> <script setup>
@@ -28,7 +30,11 @@ import {pendTransaction} from "@/blockchain/wallet.js";
import {mockErc20Abi} from "@/blockchain/abi.js"; import {mockErc20Abi} from "@/blockchain/abi.js";
import Btn from "@/components/Btn.vue"; import Btn from "@/components/Btn.vue";
import {socket} from "@/socket.js"; import {socket} from "@/socket.js";
import {metadata} from "@/version.js";
const props = defineProps({
text: {default:'GIB!'},
})
const s = useStore() const s = useStore()
/* /*
@@ -45,12 +51,37 @@ function gib(token) {
const disabled = ref(false) const disabled = ref(false)
const FAUCET_CONFIG = {
'': 1, // native coin
MEH: 1,
USXD: 1000,
WETH: 1,
ARB: 1000,
USDC: 1000,
}
function gib() { function gib() {
const s = useStore() const s = useStore()
if( s.account ) { if( s.account ) {
disabled.value = true const chainId = s.chainId
socket.emit('faucet', s.chainId.value, s.account) const tokenAmounts = {}
setTimeout(()=>disabled.value=false, 60*1000) const tmd = metadata[chainId].t // token metadata
for (const [symbol, amount] of Object.entries(FAUCET_CONFIG)) {
for (const t of tmd) {
if (t.s===symbol) {
if (t.x?.mock===true) {
tokenAmounts[t.a] = BigInt(Math.trunc(10 ** t.d * amount))
}
break
}
}
}
console.log('gib', s.chainId, s.account, s.vault, tokenAmounts )
if (Object.keys(tokenAmounts).length>0) {
disabled.value = true
socket.emit('gib', s.chainId, s.account, s.vault, tokenAmounts )
setTimeout(()=>disabled.value=false, 1*1000) // todo disable longer
}
} }
} }

View File

@@ -1,24 +1,22 @@
<template> <template>
<slot v-if="ok"/> <slot v-if="ok"/>
<phone-card v-if="!walletOk"> <div v-if="!walletOk">
<v-card-title>Install Wallet</v-card-title> <h2>Install Wallet</h2>
<v-card-text> <p>
A cryptocurrency wallet such as <a href="https://metamask.io/download/" target="MetaMask">MetaMask</a> is A cryptocurrency wallet such as <a href="https://metamask.io/download/" target="MetaMask">MetaMask</a> is
required to use Dexorder. Please install a crypto wallet into your browser to experience the power of Dexorder. required to use Dexorder. Please install a crypto wallet into your browser to experience the power of Dexorder.
</v-card-text> </p>
<v-card-actions> <p>
<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"/>
</v-card-actions> </p>
</phone-card> </div>
<phone-card v-if="walletOk && !providerOk"> <div v-if="walletOk && !providerOk">
<v-card-text> <p>
Please log in to your crypto wallet. Please log in to your crypto wallet.
</v-card-text> </p>
<v-card-actions v-if="walletOk && !providerOk"> <v-btn prepend-icon="mdi-power" text="Connect Wallet" @click="connectWallet"/>
<v-btn prepend-icon="mdi-power" text="Connect Wallet" @click="connectWallet"/> </div>
</v-card-actions> <div v-if="walletOk && chainOk && !providerOk">
</phone-card>
<phone-card v-if="walletOk && providerOk && !chainOk">
<!-- todo Alpha Live <!-- 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>
@@ -26,40 +24,42 @@
<blockchain chain-id="42161"/> blockchain in your wallet. <blockchain chain-id="42161"/> blockchain in your wallet.
</v-card-text> </v-card-text>
--> -->
<v-card-title><v-icon icon="mdi-hand-wave" color="warning"/>&nbsp;Welcome to Dexorder Alpha!</v-card-title> <h2><v-icon icon="mdi-hand-wave" color="warning"/>&nbsp;Welcome to Dexorder Alpha!</h2>
<v-card-text> <p>
This alpha test runs on a private blockchain, which you need to set up. This alpha test runs on the Dexorder Testnet blockchain, which gives you free testnet tokens to trade.
</v-card-text> </p>
<v-card-item> <p>
<ol class="ml-6"> <v-btn variant="tonal" @click="addChain">Setup Dexorder Testnet</v-btn>
<li>Open Metamask</li> </p>
<li>Click in the upper-left to choose a Network</li> <p>Manual Setup:</p>
<li>Click the "Add Network" button</li> <ol class="ml-6">
<li>Choose "Add a Network Manually"</li> <li>Open Metamask</li>
<li>Enter the following information: <li>Click in the upper-left to choose a Network</li>
<ul> <li>Click the "Add Network" button</li>
<li>Name: Dexorder Alpha</li> <li>Choose "Add a Network Manually"</li>
<li>New RPC URL: https://rpc.alpha.dexorder.trade</li> <li>Enter the following information:
<li>Chain ID: 1337</li> <ul>
<li>Currency Symbol: TETH</li> <li>Name: Dexorder Alpha Testnet</li>
</ul> <li>New RPC URL: https://rpc.alpha.dexorder.trade</li>
</li> <li>Chain ID: 1337</li>
<li> <li>Currency Symbol: TETH</li>
Save the private test network </ul>
</li> </li>
<li> <li>
Open Metamask again and select the "Dexorder Alpha" blockchain for use with this website. Save the private test network
</li> </li>
</ol> <li>
</v-card-item> Open Metamask again and select the "Dexorder Alpha" blockchain for use with this website.
</phone-card> </li>
</ol>
</div>
</template> </template>
<script setup> <script setup>
import {useStore} from "@/store/store"; import {useStore} from "@/store/store";
import PhoneCard from "@/components/PhoneCard.vue"; import PhoneCard from "@/components/PhoneCard.vue";
import {connectWallet} from "@/blockchain/wallet.js"; import {connectWallet} from "@/blockchain/wallet.js";
import {computed} from "vue"; import {computed, watch} from "vue";
import Blockchain from "@/components/Blockchain.vue"; import Blockchain from "@/components/Blockchain.vue";
const s = useStore() const s = useStore()
@@ -67,11 +67,42 @@ const walletOk = typeof window.ethereum !== 'undefined'
const providerOk = computed(()=>s.provider!==null) const providerOk = computed(()=>s.provider!==null)
const chainOk = computed(()=>providerOk.value && s.helper!==null) const chainOk = computed(()=>providerOk.value && s.helper!==null)
const ok = computed(()=>{ const ok = computed(()=>{
console.log('recompute provider ok')
return walletOk && providerOk.value && chainOk.value return walletOk && providerOk.value && chainOk.value
}) })
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,29 +1,32 @@
<template> <template>
<NeedsProvider> <needs-provider>
<slot v-if="ok"/> <slot v-if="ok" v-bind="$props"/>
<phone-card v-if="!ok"> <div v-if="!ok">
<v-card-title><v-icon icon="mdi-reload-alert" color="warning"/> Connect Wallet</v-card-title> <btn icon="mdi-wallet-outline" color="warning" variant="outlined" @click="connect" :disabled="disabled" text="Connect Wallet"/>
<v-card-text> </div>
Please select an account to use from your wallet. </needs-provider>
</v-card-text>
<v-card-actions>
<btn icon="mdi-wallet-outline" color="warning" @click="connectWallet">Connect Wallet</btn>
</v-card-actions>
</phone-card>
</NeedsProvider>
</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} from "vue"; import {computed, ref} from "vue";
import PhoneCard from "@/components/PhoneCard.vue";
import {connectWallet} from "@/blockchain/wallet.js"; import {connectWallet} from "@/blockchain/wallet.js";
import Btn from "@/components/Btn.vue"; import Btn from "@/components/Btn.vue";
const s = useStore() const s = useStore()
const ok = computed(()=>s.account!==null) const ok = computed(()=>s.account!==null)
const disabled = ref(false)
async function connect() {
disabled.value = true
try {
await connectWallet(s.chainId)
}
finally {
disabled.value = false
}
}
</script> </script>

View File

@@ -40,7 +40,7 @@ const price = computed(()=>{
if( route.value ) { if( route.value ) {
subPrices([route.value]) subPrices([route.value])
subOHLCs( s.chainId.value, [[route.value.pool,'1D']]) subOHLCs( s.chainId, [[route.value.pool,'1D']])
} }
else else
console.log('route is empty: no price') console.log('route is empty: no price')

View File

@@ -22,54 +22,66 @@
</v-card-text> </v-card-text>
</PhoneCard> </PhoneCard>
--> -->
<phone-card v-if="s.vaults.length<=num" class="maxw"> <div>
<v-card-title><v-icon icon="mdi-safe-square" size="small" color="grey-darken-1"/> Create a Dexorder Vault</v-card-title> <div v-if="s.vaults.length<=num">
<v-card-title>
<v-icon icon="mdi-safe-square" size="small" color="grey-darken-1"/>
Create a Dexorder Vault
</v-card-title>
<v-card-text v-if="num!==0"><!--todo-->Multiple vaults are not yet supported</v-card-text> <v-card-text v-if="num!==0"><!--todo-->Multiple vaults are not yet supported</v-card-text>
<!-- todo restore the vault-on-order approach for public beta <!-- todo restore the vault-on-order approach for public beta
<v-card-text v-if="num===0">Create an order first, then your vault account will appear here to accept a deposit of trading funds.</v-card-text> <v-card-text v-if="num===0">Create an order first, then your vault account will appear here to accept a deposit of trading funds.</v-card-text>
<v-card-actions><v-btn prepend-icon="mdi-plus" text="Create Order" @click="$router.push('/twap')"/></v-card-actions> <v-card-actions><v-btn prepend-icon="mdi-plus" text="Create Order" @click="$router.push('/twap')"/></v-card-actions>
--> -->
<!-- User-actioned but dexorder executed <!-- User-actioned but dexorder executed
<v-card-text v-if="num===0"> <v-card-text v-if="num===0">
Your vault is a smart contract that securely holds your funds plus any orders you place. When your order Your vault is a smart contract that securely holds your funds plus any orders you place. When your order
conditions are met, Dexorder creates a blockchain transaction for <code>vault.execute()</code>, conditions are met, Dexorder creates a blockchain transaction for <code>vault.execute()</code>,
asking your vault to the order. Your vault then checks that order against the asking your vault to the order. Your vault then checks that order against the
current blockchain time and pool price, and only trades if everything looks good. current blockchain time and pool price, and only trades if everything looks good.
Start placing dexorders by clicking the button below to create your own personal Vault! Start placing dexorders by clicking the button below to create your own personal Vault!
</v-card-text> </v-card-text>
<v-card-actions><btn icon="mdi-safe-square" color="grey-darken-1" text="Create Vault" @click="ensureVault"/></v-card-actions> <v-card-actions><btn icon="mdi-safe-square" color="grey-darken-1" text="Create Vault" @click="ensureVault"/></v-card-actions>
--> -->
<v-card-text v-if="num===0"> <v-card-text v-if="num===0">
Please wait while your vault is being created. This should only take a few seconds. Please wait while your vault is being created. This should only take a few seconds.
</v-card-text> </v-card-text>
</phone-card> </div>
<v-card v-if="s.vaults.length>num" :class="empty?'maxw':''"> <div v-if="s.vaults.length>num" :class="empty?'maxw':''">
<v-card-title><v-icon icon="mdi-safe-square" color="grey-darken-2" size="small"/> Vault Assets {{s.vaults.length>1?'#'+(num+1):''}}</v-card-title> <!-- todo vault nicknames --> <v-card-title>
<v-card-subtitle v-if="exists" class="overflow-x-hidden"><copy-button :text="addr"/>{{addr}}</v-card-subtitle> Your Deposit Address {{ s.vaults.length > 1 ? '#' + (num + 1) : '' }}
</v-card-title> <!-- todo vault nicknames -->
<v-card-subtitle v-if="exists" class="overflow-x-hidden">
<copy-button :text="addr"/>
{{ addr }}
</v-card-subtitle>
<v-card-text v-if="empty"> <v-card-text v-if="empty">
<!-- <!--
<p> <p>
Your vault is a smart contract that securely holds your funds plus any orders you place. When your order Your vault is a smart contract that securely holds your funds plus any orders you place. When your order
conditions are met, Dexorder creates a blockchain transaction asking your vault to execute the order. Your conditions are met, Dexorder creates a blockchain transaction asking your vault to execute the order. Your
vault then checks that order against the current blockchain time and pool price, and only trades if vault then checks that order against the current blockchain time and pool price, and only trades if
everything looks good. everything looks good.
</p> </p>
--> -->
<p v-if="!s.mockenv">There are no funds currently in your vault. Send tokens to the address above to fund your vault.</p> <p v-if="!s.mockenv">There are no funds currently in your vault. Send tokens to the address above to fund your
<p v-if="s.mockenv">There are no funds currently in your vault. Use the faucet below to mint some testnet coins into your vault.</p> vault.</p>
<p v-if="s.mockenv">There are no funds currently in your vault. Use the faucet below to mint some testnet coins
into your vault.</p>
</v-card-text> </v-card-text>
<v-card-item v-if="!empty"> <v-card-item v-if="!empty">
<v-table> <v-table>
<tbody> <tbody>
<suspense v-for="(amount,addr) of balances"> <suspense v-for="(amount,addr) of balances">
<token-row :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/> <token-row :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
</suspense> </suspense>
</tbody> </tbody>
</v-table> </v-table>
</v-card-item> </v-card-item>
</v-card> </div>
<withdraw :vault="addr" :token="withdrawToken" v-model="withdrawShow"/> <withdraw :vault="addr" :token="withdrawToken" v-model="withdrawShow"/>
</div>
<!-- <!--
<div> <div>
addr {{ addr }}<br/> addr {{ addr }}<br/>

View File

@@ -0,0 +1,17 @@
<template>
<div class="d-flex flex-column h-100">
<toolbar title="Orders" icon="mdi-format-list-bulleted-square">
</toolbar>
<orders-view/>
</div>
</template>
<script setup>
import Toolbar from "@/components/chart/Toolbar.vue";
import OrdersView from "@/views/OrdersView.vue";
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,25 @@
<template>
<div class="d-flex flex-column h-100">
<toolbar title="Assets" icon="mdi-currency-btc"></toolbar>
<needs-signer>
<vault :owner="s.account" :num="0"/>
<faucet variant="outlined" text="Get Free Testnet Coins!" style="width: 15em"/>
</needs-signer>
</div>
</template>
<script setup>
import Toolbar from "@/components/chart/Toolbar.vue";
import Vault from "@/components/Vault.vue";
import NeedsSigner from "@/components/NeedsSigner.vue";
import Faucet from "@/components/Faucet.vue";
import {useStore} from "@/store/store.js";
const s = useStore()
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,12 +1,21 @@
<template> <template>
<div class="d-flex mb-1"> <div class="d-flex mb-1 align-center w-100">
<span class="arrow align-self-start"><v-icon icon="mdi-arrow-up-bold" :color="theme.colors.success"/></span> <div class="d-flex align-end clickable" @click="$router.push('/place')">
<span class="logo">dexorder</span> <span class="arrow align-self-start"><v-icon icon="mdi-arrow-up-bold" :color="theme.colors.success"/></span>
<v-chip text="ALPHA" size='x-small' color="red" class="align-self-start pr-6" variant="text"/> <span class="logo">dexorder</span>
<v-chip text="ALPHA" size='x-small' color="red" class="align-self-start pr-6" variant="text"/>
</div>
<!--
<div class="d-flex align-center">
</div>
-->
<slot/> <slot/>
<div class="w-100 d-flex justify-end"> <div class="ml-auto d-flex align-center">
<v-btn variant="text" icon="mdi-safe-square" color="grey-darken-2" text="Vault" @click="$router.push('/vault')"></v-btn> <!-- <v-icon :icon="icon" size="small"/>&nbsp;-->
<v-btn variant="text" icon="mdi-information-outline" text="Order Status" @click="$router.push('/orders')"></v-btn> <span class="title">{{title}}</span>
<toolbar-button icon="mdi-currency-btc" path="/vault"/>
<toolbar-button icon="mdi-format-list-bulleted-square" path="/orders"/>
<v-btn variant="text" icon="mdi-help-circle-outline" text="Info" @click="showCorp" disabled></v-btn>
</div> </div>
</div> </div>
</template> </template>
@@ -19,21 +28,17 @@ import {timestamp} from "@/misc.js";
import {ShapeType} from "@/charts/shape.js"; import {ShapeType} from "@/charts/shape.js";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import {useTheme} from "vuetify"; import {useTheme} from "vuetify";
import ToolbarButton from "@/components/chart/ToolbarButton.vue";
const props = defineProps(['title', 'icon'])
const s = useStore() const s = useStore()
const co = useChartOrderStore() const co = useChartOrderStore()
const showCancel = ref(false)
const theme = useTheme().current const theme = useTheme().current
function cancelOrder() { function showCorp() {
showCancel.value = true window.open('https://dexorder.trade', 'dexorder')
}
function placeOrder() {
} }
</script> </script>

View File

@@ -0,0 +1,15 @@
<template>
<v-btn :color="isCurrent?'primary':undefined" variant="text" :icon="icon" @click="$router.push(path)"/>
</template>
<script setup>
import {computed} from "vue";
import {useRoute} from "vue-router";
const props = defineProps(['icon', 'path'])
const isCurrent = computed(() => useRoute().path === props.path)
</script>
<style scoped lang="scss">
</style>

View File

@@ -11,7 +11,7 @@ const routes = [
// route level code-splitting // route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route // this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "chartorder" */ '@/components/chart/ChartOrderPane.vue'), component: () => import(/* webpackChunkName: "chartorder" */ '@/components/chart/ChartPlaceOrder.vue'),
}, },
{ {
path: '/place', path: '/place',
@@ -19,7 +19,7 @@ const routes = [
// route level code-splitting // route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route // this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "chartorder" */ '@/components/chart/ChartOrderPane.vue'), component: () => import(/* webpackChunkName: "chartorder" */ '@/components/chart/ChartPlaceOrder.vue'),
}, },
{ {
path: '/vault', path: '/vault',
@@ -27,7 +27,7 @@ const routes = [
// route level code-splitting // route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route // this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "vaultview" */ '@/views/VaultView.vue'), component: () => import(/* webpackChunkName: "vaultview" */ '@/components/chart/ChartVault.vue'),
}, },
{ {
path: '/orders', path: '/orders',
@@ -35,13 +35,13 @@ const routes = [
// route level code-splitting // route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route // this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "ordersview" */ '@/views/OrdersView.vue'), component: () => import(/* webpackChunkName: "ordersview" */ '@/components/chart/ChartOrders.vue'),
}, },
/* /*
{ {
path: '/create', path: '/create',
name: 'Create', name: 'Create',
component: () => import(/!* webpackChunkName: "chartorder" *!/ '@/components/chart/ChartOrderPane.vue'), component: () => import(/!* webpackChunkName: "chartorder" *!/ '@/components/chart/ChartPlaceOrder.vue'),
}, },
{ {
path: '/twap', path: '/twap',

View File

@@ -17,7 +17,7 @@ socket.on('disconnect', () => {
socket.on('p', async (chainId, pool, price) => { socket.on('p', async (chainId, pool, price) => {
console.log('pool price from message', chainId, pool, price) console.log('pool price from message', chainId, pool, price)
const s = useStore() const s = useStore()
if( s.chainId.value !== chainId ) if( s.chainId !== chainId )
return return
s.poolPrices[[chainId,pool]] = price s.poolPrices[[chainId,pool]] = price
}) })
@@ -29,7 +29,7 @@ socket.on('ohlc', async (chainId, pool, ohlcs) => {
socket.on('vb', async (chainId, vault, balances) => { socket.on('vb', async (chainId, vault, balances) => {
const s = useStore() const s = useStore()
if( s.chainId.value !== chainId ) if( s.chainId !== chainId )
return return
console.log('vb', vault, balances) console.log('vb', vault, balances)
s.vaultBalances[vault] = JSON.parse(balances) s.vaultBalances[vault] = JSON.parse(balances)
@@ -39,7 +39,7 @@ socket.on('vb', async (chainId, vault, balances) => {
socket.on('vaults', (chainId, owner, vaults)=>{ socket.on('vaults', (chainId, owner, vaults)=>{
const s = useStore() const s = useStore()
console.log('vaults', vaults) console.log('vaults', vaults)
if( s.chainId.value !== chainId || s.account !== owner ) if( s.chainId !== chainId || s.account !== owner )
return return
if( vaults.length > s.vaults.length ) { if( vaults.length > s.vaults.length ) {
s.vaults = vaults s.vaults = vaults
@@ -53,7 +53,7 @@ socket.on('vaults', (chainId, owner, vaults)=>{
function handleOrderStatus(chainId, vault, orderIndex, status) { function handleOrderStatus(chainId, vault, orderIndex, status) {
const s = useStore() const s = useStore()
if( s.chainId.value !== chainId ) if( s.chainId !== chainId )
return return
// message 'o' is a single order status // message 'o' is a single order status
const parsed = parseOrderStatus(status); const parsed = parseOrderStatus(status);
@@ -74,7 +74,7 @@ socket.on( 'o', handleOrderStatus)
socket.on( 'of', (chainId, vault, orderIndex, filled)=>{ socket.on( 'of', (chainId, vault, orderIndex, filled)=>{
const s = useStore() const s = useStore()
if( s.chainId.value !== chainId ) if( s.chainId !== chainId )
return return
console.log('of', chainId, vault, orderIndex, filled) console.log('of', chainId, vault, orderIndex, filled)
if( !(vault in s.orders) ) { if( !(vault in s.orders) ) {

View File

@@ -3,6 +3,8 @@ 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:
// //
@@ -31,11 +33,15 @@ function timestamp() {
return Math.round(new Date().getTime() / 1000) return Math.round(new Date().getTime() / 1000)
} }
const UNKNOWN_PROVIDER = {}
export const useStore = defineStore('app', ()=> { export const useStore = defineStore('app', ()=> {
const clock = ref(timestamp()) const clock = ref(timestamp())
const nav = ref(false) // controls opening navigation drawer const nav = ref(false) // controls opening navigation drawer
const _chainId = ref(Object.keys(version.chainInfo)[0]) const _chainId = ref(Number(Object.keys(version.chainInfo)[0]))
const _chainInfo = ref(version.chainInfo) const _chainInfo = ref(version.chainInfo)
function getTokenList() { function getTokenList() {
@@ -54,25 +60,42 @@ export const useStore = defineStore('app', ()=> {
result[token.address] = token result[token.address] = token
return result return result
} }
function refreshChain() {
useOrderStore().setDefaultTokens(getTokenList())
}
const chainId = computed({ const chainId = computed({
get() {return _chainId}, get() {return _chainId.value},
set(v) {_chainId.value=v; refreshChain()} set(v) {
console.log('setting chainid',_chainId.value, v)
if (_chainId.value!==v) {
console.log('do set')
_chainId.value = v
account.value = null
}
if (_chainId.value!==v || _provider.value === null) {
console.log('update provider')
_provider.value = UNKNOWN_PROVIDER
}
}
}) })
const chainInfo = computed({ const chainInfo = computed({
get() {return _chainInfo}, get() {return _chainInfo.value},
set(v) {_chainInfo.value=v; refreshChain()} set(v) {_chainInfo.value=v}
}) })
const chain = computed(() => !_chainId.value ? null : (_chainInfo.value[_chainId.value] || null)) const chain = computed(() => !_chainId.value ? null : (_chainInfo.value[_chainId.value] || null))
// making the provider directly reactive causes exceptions (calling private method...) when calling provider // this provider is for the app's chainId not the wallet's chainId.
// functions, so we use a separate ref to signal changes let _provider = UNKNOWN_PROVIDER // null indicates a known-unavailable provider whereas undefined means provider status is unknown
let _provider = null const provider = computed(()=>{
const _providerTouch = ref(false) if (_provider===UNKNOWN_PROVIDER) {
const provider = computed({ // unknown provider status
get() {_providerTouch.value; return _provider}, try {
set(v) {_provider=v; _providerTouch.value = !_providerTouch.value} _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)

View File

@@ -7,6 +7,12 @@
font-size: 24px; font-size: 24px;
} }
.title {
font-family: v.$heading-font-family;
font-weight: 500;
font-size: 21px;
}
.clickable { .clickable {
:hover { :hover {
cursor: pointer; cursor: pointer;

View File

@@ -25,6 +25,6 @@ for (const [chain, info] of Object.entries(metadata)) {
map[poolMeta.a] = poolMeta map[poolMeta.a] = poolMeta
for (const tokenMeta of info.t) for (const tokenMeta of info.t)
map[tokenMeta.a] = tokenMeta map[tokenMeta.a] = tokenMeta
metadataMap[chain] = map metadataMap[Number(chain)] = map
} }
console.log('metadataMap', metadataMap) console.log('metadataMap', metadataMap)

View File

@@ -1,8 +1,11 @@
<template> <template>
<!-- todo needs account -->
<needs-signer> <needs-signer>
<vault :owner="s.account" :num="0"/> <phone-card class="maxw">
<faucet class="mt-3"/> <vault :owner="s.account" :num="0"/>
</phone-card>
<phone-card v-if="s.mockenv && s.vault" class="maxw">
<faucet class="mt-3"/>
</phone-card>
<new-order class="my-6"/> <new-order class="my-6"/>
</needs-signer> </needs-signer>
</template> </template>
@@ -14,6 +17,7 @@ import NeedsSigner from "@/components/NeedsSigner.vue";
import Faucet from "@/components/Faucet.vue"; import Faucet from "@/components/Faucet.vue";
import NeedsProvider from "@/components/NeedsProvider.vue"; import NeedsProvider from "@/components/NeedsProvider.vue";
import NewOrder from "@/components/NewOrder.vue"; import NewOrder from "@/components/NewOrder.vue";
import PhoneCard from "@/components/PhoneCard.vue";
const s = useStore() const s = useStore()