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 = []
if (!tokenA || !tokenB)
return
console.log('finding route', s.chainId.value, tokenA, tokenB)
console.log('finding route', s.chainId, tokenA, tokenB)
os.routesPending = true
try {
console.log('getting query helper')
@@ -44,7 +44,7 @@ async function componentFindRoute() {
if (!helper) {
console.log('no helper')
} 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)
os.routes = result
}

View File

@@ -6,6 +6,8 @@ import {ethers} from "ethers";
// synchronous version may return null but will trigger a lookup
export function token(addr) {
// todo deprecated. use metadataMap[chainId][addr]
console.warn('token() is deprecated')
console.log('token', addr)
if( !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
export async function getToken(addr) {
// todo deprecated. use metadataMap[chainId][addr]
console.warn('getToken() is deprecated')
const s = useStore()
if (!(addr in s.tokens))
await addExtraToken(addr)

View File

@@ -6,20 +6,26 @@ import {vaultAbi} from "@/blockchain/abi.js";
import {SingletonCoroutine} from "@/misc.js";
import {defineStore} from "pinia";
import {ref} from "vue";
import {metadata, metadataMap} from "@/version.js";
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
// {
// chainId: 31337, // must never be null, even if no wallet plugin exists. chosen by app, not wallet.
// 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
// order: {tokenIn:..., tokenOut:..., ...} // blockchain binary order object
// }
const pendingOrders = ref([])
return {
pendingOrders,
chainId, pendingOrders,
}
})
@@ -27,27 +33,31 @@ export const useWalletStore = defineStore('wallet', ()=>{
export function onChainChanged(chainId) {
chainId = Number(chainId)
const store = useStore()
if( chainId !== store.chainId.value ) {
// todo check pending orders and confirm cancellation
const ws = useWalletStore()
if( chainId !== ws.chainId ) {
console.log('chain changed', chainId)
store.chainId.value = 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)))
ws.chainId = chainId
if (chainId.toString() in metadataMap) {
console.log('app chain changed', chainId)
store.chainId = chainId
store.account = null
}
}
}
export function updateAccounts(chainId, provider) {
provider.listAccounts().then((accounts) => changeAccounts(chainId, accounts.map((a) => a.address)))
}
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()
if( accounts.length === 0 ) {
console.log('account logged out')
store.account = null
store.vaults = []
store.vaultBalances = {}
}
else {
if (chainId === store.chainId && accounts.length) {
const addr = accounts[0]
console.log('account logged in', addr)
store.account = addr
@@ -55,12 +65,19 @@ function changeAccounts(chainId, accounts) {
flushTransactions()
socket.emit('address', chainId, addr)
}
else {
console.log('account logged out')
store.account = null
store.vaults = []
store.vaultBalances = {}
}
}
function onAccountsChanged(accounts) {
const store = useStore()
const ws = useWalletStore()
if (accounts.length === 0 || accounts[0] !== store.account)
changeAccounts(store.chainId.value, accounts);
changeAccounts(ws.chainId, accounts);
}
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) {
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) {
const result = []
const s = useStore()
if( !owner || !s.chainId.value || !s.account) {
if( !owner || !s.chainId || !s.account) {
s.vaults = []
return
}
@@ -159,7 +217,7 @@ async function _discoverVaults(owner) {
if( result.length )
flushOrders(result[0])
else
ensureVault2(s.chainId.value, owner, 0)
ensureVault2(s.chainId, owner, 0)
}
}
@@ -176,8 +234,8 @@ async function ensureVault1() {
const s = useStore()
const owner = s.account;
if (owner===null)
await connectWallet(s.chainId.value)
ensureVault2(s.chainId.value, owner, 0)
await connectWallet(s.chainId)
ensureVault2(s.chainId, owner, 0)
}
export function ensureVault2(chainId, owner, num) {
@@ -209,9 +267,10 @@ export async function pendOrder(order) {
console.log('order', JSON.stringify(order))
const s = useStore()
useWalletStore().pendingOrders.push({
chainId: s.chainId.value,
chainId: s.chainId,
placementTime: new Date(),
vault: s.vaults.length ? s.vaults[0] : null,
submitted: false,
order
})
ensureVault()
@@ -243,13 +302,18 @@ export async function cancelAll(vault) {
export function flushOrders(vault) {
const ws = useWalletStore();
let needsFlush = false
for( const order of ws.pendingOrders ) {
if (order.vault === null)
order.vault = vault
pendOrderAsTransaction(order)
if (!order.submitted) {
pendOrderAsTransaction(order)
order.submitted = true
needsFlush = true
}
}
ws.pendingOrders = []
flushTransactions()
if (needsFlush)
flushTransactions()
}
@@ -260,8 +324,18 @@ function pendOrderAsTransaction(order) {
console.error('vault contract was null while sending order transaction', order.vault)
return null
}
if (!await switchChain(order.chainId)) {
console.log('user refused chain switch')
return null
}
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
export function flushTransactions() {
flushing++
if( flushing === 1 )
// noinspection JSIgnoredPromiseFromCall
asyncFlushTransactions()
flushTransactionsRoutine.invoke()
}
export async function asyncFlushTransactions() {
let counter
do {
counter = flushing
await asyncFlushTransactions2()
} while( flushing > counter)
flushing = 0
}
export async function asyncFlushTransactions2() {
// todo rework into flushTransactions()
async function asyncFlushTransactions() {
const s = useStore()
if( s.provider === null ) {
console.log('warning: asyncFlushOrders() cancelled due to null provider')
@@ -319,22 +382,21 @@ export async function asyncFlushTransactions2() {
function doSendTransaction(sender, signer) {
const s = useStore();
s.removeTransactionSender(sender)
sender(signer).then((tx)=>{
console.log('sent transaction', tx)
s.removeTransactionSender(sender)
tx.wait().then((tr)=>console.log('tx receipt',tr))
if (tx!==null) {
console.log('sent transaction', tx)
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?
}
})
}

View File

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

View File

@@ -1,9 +1,9 @@
<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>
<v-icon :color="color"></v-icon>
</template>
<slot name="text">{{text}}</slot>
<template v-slot>{{text}}<slot/></template>
</v-btn>
</template>
@@ -12,7 +12,12 @@ import {useStore} from "@/store/store";
import {useAttrs} from "vue";
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()
</script>

View File

@@ -1,9 +1,11 @@
<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-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-item>
-->
<!--
<v-table plain>
<tbody>
@@ -14,9 +16,9 @@
</tbody>
</v-table>
-->
<btn icon='mdi-plus' color="green" :disabled="disabled" @click="gib">GIB!</btn>
</v-card-item>
</phone-card>
<btn icon='mdi-plus' color="green" :disabled="disabled" @click="gib" :text="text"/>
<!-- </v-card-item>-->
<!-- </div>-->
</template>
<script setup>
@@ -28,7 +30,11 @@ import {pendTransaction} from "@/blockchain/wallet.js";
import {mockErc20Abi} from "@/blockchain/abi.js";
import Btn from "@/components/Btn.vue";
import {socket} from "@/socket.js";
import {metadata} from "@/version.js";
const props = defineProps({
text: {default:'GIB!'},
})
const s = useStore()
/*
@@ -45,12 +51,37 @@ function gib(token) {
const disabled = ref(false)
const FAUCET_CONFIG = {
'': 1, // native coin
MEH: 1,
USXD: 1000,
WETH: 1,
ARB: 1000,
USDC: 1000,
}
function gib() {
const s = useStore()
if( s.account ) {
disabled.value = true
socket.emit('faucet', s.chainId.value, s.account)
setTimeout(()=>disabled.value=false, 60*1000)
const chainId = s.chainId
const tokenAmounts = {}
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>
<slot v-if="ok"/>
<phone-card v-if="!walletOk">
<v-card-title>Install Wallet</v-card-title>
<v-card-text>
<div v-if="!walletOk">
<h2>Install Wallet</h2>
<p>
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.
</v-card-text>
<v-card-actions>
</p>
<p>
<v-btn prepend-icon="mdi-reload" text="Reload After Installing Wallet" @click="reload"/>
</v-card-actions>
</phone-card>
<phone-card v-if="walletOk && !providerOk">
<v-card-text>
</p>
</div>
<div v-if="walletOk && !providerOk">
<p>
Please log in to your crypto wallet.
</v-card-text>
<v-card-actions v-if="walletOk && !providerOk">
<v-btn prepend-icon="mdi-power" text="Connect Wallet" @click="connectWallet"/>
</v-card-actions>
</phone-card>
<phone-card v-if="walletOk && providerOk && !chainOk">
</p>
<v-btn prepend-icon="mdi-power" text="Connect Wallet" @click="connectWallet"/>
</div>
<div v-if="walletOk && chainOk && !providerOk">
<!-- todo Alpha Live
<v-card-title><v-icon icon="mdi-reload-alert" color="warning"/> Change Blockchain</v-card-title>
<v-card-text>
@@ -26,40 +24,42 @@
<blockchain chain-id="42161"/> blockchain in your wallet.
</v-card-text>
-->
<v-card-title><v-icon icon="mdi-hand-wave" color="warning"/>&nbsp;Welcome to Dexorder Alpha!</v-card-title>
<v-card-text>
This alpha test runs on a private blockchain, which you need to set up.
</v-card-text>
<v-card-item>
<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:
<ul>
<li>Name: Dexorder Alpha</li>
<li>New RPC URL: https://rpc.alpha.dexorder.trade</li>
<li>Chain ID: 1337</li>
<li>Currency Symbol: TETH</li>
</ul>
</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-item>
</phone-card>
<h2><v-icon icon="mdi-hand-wave" color="warning"/>&nbsp;Welcome to Dexorder Alpha!</h2>
<p>
This alpha test runs on the Dexorder Testnet blockchain, which gives you free testnet tokens to trade.
</p>
<p>
<v-btn variant="tonal" @click="addChain">Setup Dexorder Testnet</v-btn>
</p>
<p>Manual Setup:</p>
<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:
<ul>
<li>Name: Dexorder Alpha Testnet</li>
<li>New RPC URL: https://rpc.alpha.dexorder.trade</li>
<li>Chain ID: 1337</li>
<li>Currency Symbol: TETH</li>
</ul>
</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>
</div>
</template>
<script setup>
import {useStore} from "@/store/store";
import PhoneCard from "@/components/PhoneCard.vue";
import {connectWallet} from "@/blockchain/wallet.js";
import {computed} from "vue";
import {computed, watch} from "vue";
import Blockchain from "@/components/Blockchain.vue";
const s = useStore()
@@ -67,11 +67,42 @@ const walletOk = typeof window.ethereum !== 'undefined'
const providerOk = computed(()=>s.provider!==null)
const chainOk = computed(()=>providerOk.value && s.helper!==null)
const ok = computed(()=>{
console.log('recompute provider ok')
return walletOk && providerOk.value && chainOk.value
})
function 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>
<style scoped lang="scss">

View File

@@ -1,29 +1,32 @@
<template>
<NeedsProvider>
<slot v-if="ok"/>
<phone-card v-if="!ok">
<v-card-title><v-icon icon="mdi-reload-alert" color="warning"/> Connect Wallet</v-card-title>
<v-card-text>
Please select an account to use from your wallet.
</v-card-text>
<v-card-actions>
<btn icon="mdi-wallet-outline" color="warning" @click="connectWallet">Connect Wallet</btn>
</v-card-actions>
</phone-card>
</NeedsProvider>
<needs-provider>
<slot v-if="ok" v-bind="$props"/>
<div v-if="!ok">
<btn icon="mdi-wallet-outline" color="warning" variant="outlined" @click="connect" :disabled="disabled" text="Connect Wallet"/>
</div>
</needs-provider>
</template>
<script setup>
import {useStore} from "@/store/store";
import NeedsProvider from "@/components/NeedsProvider.vue";
import {computed} from "vue";
import PhoneCard from "@/components/PhoneCard.vue";
import {computed, ref} from "vue";
import {connectWallet} from "@/blockchain/wallet.js";
import Btn from "@/components/Btn.vue";
const s = useStore()
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>

View File

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

View File

@@ -22,54 +22,66 @@
</v-card-text>
</PhoneCard>
-->
<phone-card v-if="s.vaults.length<=num" class="maxw">
<v-card-title><v-icon icon="mdi-safe-square" size="small" color="grey-darken-1"/> Create a Dexorder Vault</v-card-title>
<div>
<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>
<!-- 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-actions><v-btn prepend-icon="mdi-plus" text="Create Order" @click="$router.push('/twap')"/></v-card-actions>
-->
<!-- User-actioned but dexorder executed
<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
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
current blockchain time and pool price, and only trades if everything looks good.
<!-- 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-actions><v-btn prepend-icon="mdi-plus" text="Create Order" @click="$router.push('/twap')"/></v-card-actions>
-->
<!-- User-actioned but dexorder executed
<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
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
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!
</v-card-text>
<v-card-actions><btn icon="mdi-safe-square" color="grey-darken-1" text="Create Vault" @click="ensureVault"/></v-card-actions>
-->
Start placing dexorders by clicking the button below to create your own personal Vault!
</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-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>
</phone-card>
<v-card 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-subtitle v-if="exists" class="overflow-x-hidden"><copy-button :text="addr"/>{{addr}}</v-card-subtitle>
</div>
<div v-if="s.vaults.length>num" :class="empty?'maxw':''">
<v-card-title>
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">
<!--
<p>
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
vault then checks that order against the current blockchain time and pool price, and only trades if
everything looks good.
</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. Use the faucet below to mint some testnet coins into your vault.</p>
<!--
<p>
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
vault then checks that order against the current blockchain time and pool price, and only trades if
everything looks good.
</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. Use the faucet below to mint some testnet coins
into your vault.</p>
</v-card-text>
<v-card-item v-if="!empty">
<v-table>
<tbody>
<suspense v-for="(amount,addr) of balances">
<token-row :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
</suspense>
<suspense v-for="(amount,addr) of balances">
<token-row :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
</suspense>
</tbody>
</v-table>
</v-card-item>
</v-card>
</div>
<withdraw :vault="addr" :token="withdrawToken" v-model="withdrawShow"/>
</div>
<!--
<div>
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>
<div class="d-flex mb-1">
<span class="arrow align-self-start"><v-icon icon="mdi-arrow-up-bold" :color="theme.colors.success"/></span>
<span class="logo">dexorder</span>
<v-chip text="ALPHA" size='x-small' color="red" class="align-self-start pr-6" variant="text"/>
<div class="d-flex mb-1 align-center w-100">
<div class="d-flex align-end clickable" @click="$router.push('/place')">
<span class="arrow align-self-start"><v-icon icon="mdi-arrow-up-bold" :color="theme.colors.success"/></span>
<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/>
<div class="w-100 d-flex justify-end">
<v-btn variant="text" icon="mdi-safe-square" color="grey-darken-2" text="Vault" @click="$router.push('/vault')"></v-btn>
<v-btn variant="text" icon="mdi-information-outline" text="Order Status" @click="$router.push('/orders')"></v-btn>
<div class="ml-auto d-flex align-center">
<!-- <v-icon :icon="icon" size="small"/>&nbsp;-->
<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>
</template>
@@ -19,21 +28,17 @@ import {timestamp} from "@/misc.js";
import {ShapeType} from "@/charts/shape.js";
import {computed, ref} from "vue";
import {useTheme} from "vuetify";
import ToolbarButton from "@/components/chart/ToolbarButton.vue";
const props = defineProps(['title', 'icon'])
const s = useStore()
const co = useChartOrderStore()
const showCancel = ref(false)
const theme = useTheme().current
function cancelOrder() {
showCancel.value = true
}
function placeOrder() {
function showCorp() {
window.open('https://dexorder.trade', 'dexorder')
}
</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
// this generates a separate chunk (about.[hash].js) for this route
// 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',
@@ -19,7 +19,7 @@ const routes = [
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// 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',
@@ -27,7 +27,7 @@ const routes = [
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// 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',
@@ -35,13 +35,13 @@ const routes = [
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// 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',
name: 'Create',
component: () => import(/!* webpackChunkName: "chartorder" *!/ '@/components/chart/ChartOrderPane.vue'),
component: () => import(/!* webpackChunkName: "chartorder" *!/ '@/components/chart/ChartPlaceOrder.vue'),
},
{
path: '/twap',

View File

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

View File

@@ -3,6 +3,8 @@ import {defineStore} from 'pinia'
import {knownTokens} from "@/knownTokens.js";
import {computed, ref} from "vue";
import {version} from "@/version.js";
import {ethers} from "ethers";
import {updateAccounts} from "@/blockchain/wallet.js";
// USING THE STORE:
//
@@ -31,11 +33,15 @@ function timestamp() {
return Math.round(new Date().getTime() / 1000)
}
const UNKNOWN_PROVIDER = {}
export const useStore = defineStore('app', ()=> {
const clock = ref(timestamp())
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)
function getTokenList() {
@@ -54,25 +60,42 @@ export const useStore = defineStore('app', ()=> {
result[token.address] = token
return result
}
function refreshChain() {
useOrderStore().setDefaultTokens(getTokenList())
}
const chainId = computed({
get() {return _chainId},
set(v) {_chainId.value=v; refreshChain()}
get() {return _chainId.value},
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({
get() {return _chainInfo},
set(v) {_chainInfo.value=v; refreshChain()}
get() {return _chainInfo.value},
set(v) {_chainInfo.value=v}
})
const chain = computed(() => !_chainId.value ? null : (_chainInfo.value[_chainId.value] || null))
// making the provider directly reactive causes exceptions (calling private method...) when calling provider
// functions, so we use a separate ref to signal changes
let _provider = null
const _providerTouch = ref(false)
const provider = computed({
get() {_providerTouch.value; return _provider},
set(v) {_provider=v; _providerTouch.value = !_providerTouch.value}
// 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 provider = computed(()=>{
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 account = ref(null)

View File

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

View File

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

View File

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