wallet flow; new faucet; placing chart orders works!
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
ws.chainId = chainId
|
||||
if (chainId.toString() in metadataMap) {
|
||||
console.log('app chain changed', chainId)
|
||||
store.chainId = chainId
|
||||
store.account = null
|
||||
const provider = new ethers.BrowserProvider(window.ethereum, chainId);
|
||||
store.provider = provider
|
||||
provider.listAccounts().then((accounts)=>changeAccounts(chainId, accounts.map((a)=>a.address)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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,12 +302,17 @@ 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
|
||||
if (!order.submitted) {
|
||||
pendOrderAsTransaction(order)
|
||||
order.submitted = true
|
||||
needsFlush = true
|
||||
}
|
||||
ws.pendingOrders = []
|
||||
}
|
||||
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();
|
||||
sender(signer).then((tx)=>{
|
||||
console.log('sent transaction', tx)
|
||||
s.removeTransactionSender(sender)
|
||||
sender(signer).then((tx)=>{
|
||||
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?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<phone-card v-if="s.mockenv && s.vault" class="maxw">
|
||||
<!-- <div>-->
|
||||
<!--
|
||||
<v-card-title><v-icon icon="mdi-faucet"/> 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 ) {
|
||||
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('faucet', s.chainId.value, s.account)
|
||||
setTimeout(()=>disabled.value=false, 60*1000)
|
||||
socket.emit('gib', s.chainId, s.account, s.vault, tokenAmounts )
|
||||
setTimeout(()=>disabled.value=false, 1*1000) // todo disable longer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
</p>
|
||||
<v-btn prepend-icon="mdi-power" text="Connect Wallet" @click="connectWallet"/>
|
||||
</v-card-actions>
|
||||
</phone-card>
|
||||
<phone-card v-if="walletOk && providerOk && !chainOk">
|
||||
</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,11 +24,14 @@
|
||||
<blockchain chain-id="42161"/> blockchain in your wallet.
|
||||
</v-card-text>
|
||||
-->
|
||||
<v-card-title><v-icon icon="mdi-hand-wave" color="warning"/> 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>
|
||||
<h2><v-icon icon="mdi-hand-wave" color="warning"/> 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>
|
||||
@@ -38,7 +39,7 @@
|
||||
<li>Choose "Add a Network Manually"</li>
|
||||
<li>Enter the following information:
|
||||
<ul>
|
||||
<li>Name: Dexorder Alpha</li>
|
||||
<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>
|
||||
@@ -51,15 +52,14 @@
|
||||
Open Metamask again and select the "Dexorder Alpha" blockchain for use with this website.
|
||||
</li>
|
||||
</ol>
|
||||
</v-card-item>
|
||||
</phone-card>
|
||||
</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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -22,14 +22,18 @@
|
||||
</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
|
||||
<!-- 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
|
||||
-->
|
||||
<!-- 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>,
|
||||
@@ -39,25 +43,32 @@
|
||||
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.
|
||||
</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 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>
|
||||
@@ -68,8 +79,9 @@
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card-item>
|
||||
</v-card>
|
||||
</div>
|
||||
<withdraw :vault="addr" :token="withdrawToken" v-model="withdrawShow"/>
|
||||
</div>
|
||||
<!--
|
||||
<div>
|
||||
addr {{ addr }}<br/>
|
||||
|
||||
17
src/components/chart/ChartOrders.vue
Normal file
17
src/components/chart/ChartOrders.vue
Normal 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>
|
||||
25
src/components/chart/ChartVault.vue
Normal file
25
src/components/chart/ChartVault.vue
Normal 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>
|
||||
@@ -1,12 +1,21 @@
|
||||
<template>
|
||||
<div class="d-flex mb-1">
|
||||
<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"/> -->
|
||||
<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>
|
||||
|
||||
15
src/components/chart/ToolbarButton.vue
Normal file
15
src/components/chart/ToolbarButton.vue
Normal 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>
|
||||
@@ -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',
|
||||
|
||||
@@ -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) ) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: v.$heading-font-family;
|
||||
font-weight: 500;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<!-- todo needs account -->
|
||||
<needs-signer>
|
||||
<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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user