7 Commits

16 changed files with 7926 additions and 1081 deletions

View File

@@ -1,2 +1,2 @@
VITE_WS_URL=wss://ws.dexorder.com VITE_WS_URL=wss://ws.dexorder.com
VITE_SHARE_URL=https://dexorder.com VITE_SHARE_URL=https://app.dexorder.com

5731
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -15,9 +15,10 @@
"dependencies": { "dependencies": {
"@isaacs/ttlcache": "^1.4.1", "@isaacs/ttlcache": "^1.4.1",
"@mdi/font": "6.9.96", "@mdi/font": "6.9.96",
"@web3modal/ethers": "^5.1.11",
"color": "^4.2.3", "color": "^4.2.3",
"core-js": "^3.29.0", "core-js": "^3.29.0",
"ethers": "^6.7.1", "ethers": "^6.16.0",
"flexsearch": "^0.7.43", "flexsearch": "^0.7.43",
"lru-cache": "^11.0.2", "lru-cache": "^11.0.2",
"luxon": "^3.4.4", "luxon": "^3.4.4",

View File

@@ -9,6 +9,7 @@ import {metadataMap, version} from "@/version.js";
import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js"; import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js";
import {track} from "@/track.js"; import {track} from "@/track.js";
import {socket} from "@/socket.js"; import {socket} from "@/socket.js";
import {web3modal} from "@/blockchain/web3modal.js";
export const useWalletStore = defineStore('wallet', ()=>{ export const useWalletStore = defineStore('wallet', ()=>{
@@ -131,7 +132,8 @@ export function detectChain() {
} }
} }
detectChain() // Commented out - Web3Modal handles wallet detection now
// detectChain()
const errorHandlingProxy = { const errorHandlingProxy = {
get(target, prop, proxy) { get(target, prop, proxy) {
@@ -166,29 +168,123 @@ const errorHandlingProxy = {
export async function connectWallet(chainId) { export async function connectWallet(chainId) {
console.log('connectWallet', chainId) console.log('connectWallet via Web3Modal', chainId)
// Return a promise that resolves when connection is complete
return new Promise((resolve, reject) => {
let resolved = false
let unsubscribeProvider = null
let unsubscribeState = null
// Subscribe to provider changes (fires when wallet connects)
unsubscribeProvider = web3modal.subscribeProvider(async (newState) => {
console.log('Provider state changed:', newState)
if (newState.provider && !resolved) {
resolved = true
unsubscribeProvider?.()
unsubscribeState?.()
try { try {
await switchChain(chainId) const walletProvider = newState.provider
console.log('getSigner')
const p = new BrowserProvider(window.ethereum, chainId) // Create ethers provider from Web3Modal provider
await p.getSigner() const p = new BrowserProvider(walletProvider)
await updateAccounts(chainId, p) setProvider(p)
// Get network info from the provider
const network = await p.getNetwork()
const connectedChainId = Number(network.chainId)
console.log('Connected to chain', connectedChainId)
// Update wallet store
const ws = useWalletStore()
ws.chainId = connectedChainId
// Get accounts
await updateAccounts(connectedChainId, p)
// Subscribe to Web3Modal provider events
subscribeToProviderEvents(walletProvider)
resolve()
} catch (e) {
console.error('Error after connection', e)
reject(e)
} }
catch (e) { }
console.log('connectWallet error', e.reason, e) })
if (e.reason==='rejected') {
// Subscribe to modal state (fires when modal opens/closes)
unsubscribeState = web3modal.subscribeState((state) => {
// If modal closes without connection, resolve the promise
if (state.open === false && !resolved) {
console.log('Modal closed without connection')
resolved = true
unsubscribeProvider?.()
unsubscribeState?.()
resolve() // Resolve (not reject) so the button re-enables
}
})
// Open the modal
web3modal.open().catch((e) => {
if (!resolved) {
resolved = true
unsubscribeProvider?.()
unsubscribeState?.()
if (e.reason === 'rejected' || e.message?.includes('rejected')) {
const ws = useWalletStore(); const ws = useWalletStore();
const tx = ws.transaction const tx = ws.transaction
if (tx) { if (tx) {
tx.state = TransactionState.Rejected tx.state = TransactionState.Rejected
ws.transaction = null ws.transaction = null
} }
} resolve() // Don't reject on user cancellation
else { } else {
console.error(e, e.reason) reject(e)
throw e
} }
} }
})
// Timeout after 60 seconds
setTimeout(() => {
if (!resolved) {
resolved = true
unsubscribeProvider?.()
unsubscribeState?.()
console.log('Connection timeout')
resolve() // Resolve instead of reject so button re-enables
}
}, 60000)
})
}
// Subscribe to provider events from Web3Modal
function subscribeToProviderEvents(walletProvider) {
// Handle account changes
walletProvider.on('accountsChanged', (accounts) => {
console.log('accountsChanged from Web3Modal', accounts)
if (accounts.length === 0) {
disconnectWallet()
} else {
onAccountsChanged(accounts)
}
})
// Handle chain changes
walletProvider.on('chainChanged', (chainId) => {
console.log('chainChanged from Web3Modal', chainId)
onChainChanged(chainId)
})
// Handle disconnect
walletProvider.on('disconnect', () => {
console.log('Provider disconnected from Web3Modal')
disconnectWallet()
})
} }
@@ -637,21 +733,7 @@ export async function addNetwork(chainId) {
} }
export async function addNetworkAndConnectWallet(chainId) { export async function addNetworkAndConnectWallet(chainId) {
try {
await switchChain(chainId)
} catch (e) {
if (e.code === 4001) {
// explicit user rejection
return
} else if (e.code === 4902) {
try {
await addNetwork(chainId)
} catch (e) {
console.log(`Could not add network ${chainId}`)
}
} else
console.log('switchChain() failure', e)
}
try { try {
await connectWallet(chainId) await connectWallet(chainId)
} catch (e) { } catch (e) {
@@ -659,3 +741,44 @@ export async function addNetworkAndConnectWallet(chainId) {
console.log('connectWallet() failed', e) console.log('connectWallet() failed', e)
} }
} }
export async function disconnectWallet() {
console.log('disconnectWallet')
const store = useStore()
const ws = useWalletStore()
// Disconnect from Web3Modal
try {
await web3modal.disconnect()
} catch (e) {
console.log('Web3Modal disconnect error (ignoring)', e)
}
// Clear provider
setProvider(null)
// Clear account and vaults
store.account = null
store.vaults = []
store.vaultVersions = []
// Clear any pending transactions
if (ws.transaction !== null) {
ws.transaction = null
}
ws.pendingOrders = []
// Clear transaction senders
store.transactionSenders = []
// Emit socket disconnect
socket.emit('address', ws.chainId, null)
track('logout', {chainId: ws.chainId})
console.log('Wallet disconnected')
}
// Make it globally accessible from console
if (typeof window !== 'undefined') {
window.disconnectWallet = disconnectWallet
}

View File

@@ -0,0 +1,53 @@
// src/blockchain/web3modal.js
import { createWeb3Modal, defaultConfig } from '@web3modal/ethers'
const projectId = 'a2302c95fedb32f4c64672a41159df37' // Get from https://cloud.walletconnect.com
const chains = [
{
chainId: 42161,
name: 'Arbitrum One',
currency: 'ETH',
explorerUrl: 'https://arbiscan.io',
rpcUrl: 'https://arbitrum-mainnet.infura.io'
},
{
chainId: 1337,
name: 'Dexorder Alpha Testnet',
currency: 'TETH',
explorerUrl: '',
rpcUrl: 'https://rpc.alpha.dexorder.com'
},
{
chainId: 31337,
name: 'Mockchain',
currency: 'TETH',
explorerUrl: '',
rpcUrl: 'http://localhost:8545'
}
]
const ethersConfig = defaultConfig({
metadata: {
name: 'Dexorder',
description: 'Dexorder Trading Platform',
url: window.location.origin,
icons: ['https://your-icon-url.com']
},
defaultChainId: 42161,
enableEIP6963: false, // Disable to prevent wallets from auto-announcing
enableInjected: true,
enableCoinbase: true,
rpcUrl: 'https://arbitrum-mainnet.infura.io'
})
export const web3modal = createWeb3Modal({
ethersConfig,
chains,
projectId,
enableAnalytics: false,
themeMode: 'dark',
featuredWalletIds: [
'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96' // MetaMask
],
})

View File

@@ -1,4 +1,5 @@
<template> <template>
<v-alert class="d-sm-none" type="info" title="Mobile Screen" text="Dexorder is designed for desktop. Mobile coming soon!" rounded="0"/>
<v-alert v-if='!s.connected' icon="mdi-wifi-off" type="error" title="Not Connected" class="mb-3" rounded="0" density="compact"/> <v-alert v-if='!s.connected' icon="mdi-wifi-off" type="error" title="Not Connected" class="mb-3" rounded="0" density="compact"/>
<v-alert v-for="e in s.errors" icon="mdi-alert" type="error" :title="e.title" :text="e.text" class="mb-3" :closable="e.closeable" rounded="0"/> <v-alert v-for="e in s.errors" icon="mdi-alert" type="error" :title="e.title" :text="e.text" class="mb-3" :closable="e.closeable" rounded="0"/>
</template> </template>

View File

@@ -0,0 +1,142 @@
<template>
<div
ref="floatingDiv"
:style="divStyle"
@mousedown="startDrag"
>
<slot></slot>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({
id: {
type: String,
required: false,
}
});
const floatingDiv = ref(null);
const position = reactive({ x: 0, y: 0 });
const isDragging = ref(false);
const isFloating = ref(false);
const dragStartOffset = reactive({ x: 0, y: 0 });
const divStyle = reactive({
position: "",
top: "",
left: "",
zIndex: "",
cursor: "default",
});
let activeComponent = null;
function positionKey() {
return props.id ? `floating-div-pos:${props.id}` : null;
}
function savePosition() {
if (props.id) {
localStorage.setItem(
positionKey(),
JSON.stringify({ x: position.x, y: position.y })
);
}
}
function loadPosition() {
if (props.id) {
const data = localStorage.getItem(positionKey());
if (data) {
try {
const { x, y } = JSON.parse(data);
position.x = x;
position.y = y;
divStyle.position = "fixed";
divStyle.top = `${position.y}px`;
divStyle.left = `${position.x}px`;
divStyle.zIndex = 9999;
isFloating.value = true;
} catch {
// Ignore corrupted data
}
}
}
}
const startDrag = (event) => {
if (event.shiftKey || event.ctrlKey) {
if (!isFloating.value) {
const rect = floatingDiv.value.getBoundingClientRect();
position.x = rect.left;
position.y = rect.top;
divStyle.position = "fixed";
divStyle.top = `${position.y}px`;
divStyle.left = `${position.x}px`;
divStyle.zIndex = 9999;
isFloating.value = true;
}
isDragging.value = true;
dragStartOffset.x = event.clientX - position.x;
dragStartOffset.y = event.clientY - position.y;
divStyle.cursor = "move";
document.body.style.userSelect = "none";
activeComponent = floatingDiv;
window.addEventListener("mousemove", handleDrag);
window.addEventListener("mouseup", stopDrag);
}
};
const handleDrag = (event) => {
if (isDragging.value && activeComponent === floatingDiv) {
position.x = event.clientX - dragStartOffset.x;
position.y = event.clientY - dragStartOffset.y;
divStyle.top = `${position.y}px`;
divStyle.left = `${position.x}px`;
}
};
const stopDrag = () => {
if (isDragging.value && activeComponent === floatingDiv) {
isDragging.value = false;
divStyle.cursor = "default";
document.body.style.userSelect = "auto";
activeComponent = null;
savePosition();
window.removeEventListener("mousemove", handleDrag);
window.removeEventListener("mouseup", stopDrag);
}
};
const handleOutsideDrag = (event) => {
if (event.key === "Shift" || event.key === "Control") {
stopDrag();
}
};
onMounted(() => {
document.addEventListener("keyup", handleOutsideDrag);
loadPosition();
});
onBeforeUnmount(() => {
document.removeEventListener("keyup", handleOutsideDrag);
window.removeEventListener("mousemove", handleDrag);
window.removeEventListener("mouseup", stopDrag);
});
</script>
<style>
div[ref="floatingDiv"] {
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
</style>

View File

@@ -28,8 +28,8 @@
</div> </div>
<v-card-actions v-if="status>Status.NEEDS_PLUGIN"> <v-card-actions v-if="status>Status.NEEDS_PLUGIN">
<btn v-if="pluginOk" icon="mdi-wallet-outline" color="warning" variant="outlined" <btn v-if="pluginOk" icon="mdi-wallet-outline" color="green-accent-4" variant="flat"
@click="connect" :disabled="disabled" text="Connect Wallet"/> @click="connect" :disabled="disabled" text="Connect Wallet" class="connect-wallet-btn"/>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@@ -87,4 +87,16 @@ async function connect() {
#manualsetup tr td:last-child { #manualsetup tr td:last-child {
padding-left: 1em; padding-left: 1em;
} }
:deep(.connect-wallet-btn) {
background-color: #00ff41 !important;
color: #000 !important;
font-weight: 600;
box-shadow: 0 0 8px rgba(0, 255, 65, 0.3) !important;
}
:deep(.connect-wallet-btn:hover) {
background-color: #00cc34 !important;
box-shadow: 0 0 12px rgba(0, 255, 65, 0.4) !important;
}
</style> </style>

View File

@@ -5,7 +5,7 @@
<v-card-text class="text-center">Last Updated November 18, 2024</v-card-text> <v-card-text class="text-center">Last Updated November 18, 2024</v-card-text>
<v-card-text> <v-card-text>
Please read these Terms of Service (the <b>Terms</b>) and our <a href="https://www.dexorder.com/privacy-policy" target="dexorder">Privacy Policy</a> carefully because they govern your Please read these Terms of Service (the <b>Terms</b>) and our <a href="https://dexorder.com/privacy-policy" target="dexorder">Privacy Policy</a> carefully because they govern your
use of the use of the
website (and all subdomains and subpages thereon) located at dexorder.com, including without limitation the website (and all subdomains and subpages thereon) located at dexorder.com, including without limitation the
subdomains app.dexorder.com and www.dexorder.com (collectively, the <b>Site</b>), and the Dexorder web subdomains app.dexorder.com and www.dexorder.com (collectively, the <b>Site</b>), and the Dexorder web
@@ -205,7 +205,7 @@
(i) Subject to your compliance with these Terms, Dexorder will use its commercially (i) Subject to your compliance with these Terms, Dexorder will use its commercially
reasonable efforts to provide you with access to the Dexorder Service and to cause your Interactions to be reasonable efforts to provide you with access to the Dexorder Service and to cause your Interactions to be
executed on the applicable DEX in accordance with Dexorders Execution Policy located at executed on the applicable DEX in accordance with Dexorders Execution Policy located at
<a href="https://www.dexorder.com/execution-policy">https://www.dexorder.com/execution-policy</a> <a href="https://dexorder.com/execution-policy">https://dexorder.com/execution-policy</a>
(<b>Execution Policy</b>), however from time to time the Site and the Dexorder Service may be inaccessible or (<b>Execution Policy</b>), however from time to time the Site and the Dexorder Service may be inaccessible or
inoperable for any inoperable for any
reason, including, without limitation: (a) if an Interaction repeatedly fails to be executed (such as due to an reason, including, without limitation: (a) if an Interaction repeatedly fails to be executed (such as due to an

View File

@@ -59,7 +59,7 @@ function tryIt() {
function learnMore() { function learnMore() {
track('learn-more') track('learn-more')
window.open('https://www.dexorder.com/introduction.html', 'dexorderwww') window.open('https://dexorder.com/introduction.html', 'dexorderwww')
modelValue.value = false modelValue.value = false
} }

View File

@@ -132,7 +132,7 @@ const flippedSign = computed(()=>props.flip?-1:1)
const balance100 = computed( { const balance100 = computed( {
get() {return flippedSign.value*props.builder.balance*100}, get() {return flippedSign.value*props.builder.balance*100},
set(v) { set(v) {
if (v<-60) v = -60; // if (v<-60) v = -60;
props.builder.balance = flippedSign.value*v/100; } props.builder.balance = flippedSign.value*v/100; }
} ) } )

View File

@@ -10,7 +10,7 @@
<toolbar-button tooltip="Assets" icon="mdi-currency-btc" route="Assets"/> <toolbar-button tooltip="Assets" icon="mdi-currency-btc" route="Assets"/>
<!-- mdi-format-list-checks mdi-format-list-bulleted-square --> <!-- mdi-format-list-checks mdi-format-list-bulleted-square -->
<toolbar-button tooltip="Status" icon="mdi-format-list-checks" route="Status"/> <toolbar-button tooltip="Status" icon="mdi-format-list-checks" route="Status"/>
<toolbar-button tooltip="About" icon="mdi-information-outline" href="https://www.dexorder.com/" target="dexorderwww"/> <toolbar-button tooltip="About" icon="mdi-information-outline" href="https://dexorder.com/" target="dexorderwww"/>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -4,7 +4,7 @@
<script setup> <script setup>
function openApp() { function openApp() {
window.open('https://dexorder.com/', 'dexorderapp') window.open('https://app.dexorder.com/', 'dexorderapp')
} }
</script> </script>

View File

@@ -24,7 +24,7 @@ import {router} from "@/router/router.js";
const theme = useTheme().current const theme = useTheme().current
function openApp() { function openApp() {
window.open('https://dexorder.com/', 'dexorderapp') window.open('https://app.dexorder.com/', 'dexorderapp')
} }
</script> </script>

View File

@@ -15,6 +15,11 @@ import { registerPlugins } from '@/plugins'
import '@/styles/style.scss' import '@/styles/style.scss'
import "./socketInit.js" import "./socketInit.js"
import "./version.js" import "./version.js"
import { web3modal } from '@/blockchain/web3modal.js'
import { BrowserProvider } from 'ethers'
import { setProvider } from '@/blockchain/provider.js'
import { updateAccounts } from '@/blockchain/wallet.js'
import { useWalletStore } from '@/blockchain/wallet.js'
BigInt.prototype.toJSON = function() { return this.toString() } BigInt.prototype.toJSON = function() { return this.toString() }
@@ -22,3 +27,24 @@ const app = createApp(App)
registerPlugins(app) registerPlugins(app)
app.mount('#app') app.mount('#app')
window.$vuetify = app window.$vuetify = app
// Check if Web3Modal has an existing connection on app start
web3modal.subscribeProvider(async (state) => {
if (state.isConnected && state.provider) {
try {
console.log('Restoring existing Web3Modal connection')
const p = new BrowserProvider(state.provider)
setProvider(p)
const network = await p.getNetwork()
const chainId = Number(network.chainId)
const ws = useWalletStore()
ws.chainId = chainId
await updateAccounts(chainId, p)
} catch (e) {
console.log('Error restoring connection', e)
}
}
})

2822
yarn.lock

File diff suppressed because it is too large Load Diff