Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ onMounted(async () => {
console.warn('Autonomi client init failed:', e)
})

// Only bypass WalletConnect for local Anvil devnet, not Sepolia
if (!settingsStore.devnetIsSepolia) {
// Only bypass WalletConnect for local Anvil devnet. Sepolia / mainnet
// manifests expect the user to connect their own wallet via WalletConnect.
const { ANVIL_CHAIN_ID } = await import('~/utils/constants')
if (settingsStore.devnetChainId === ANVIL_CHAIN_ID) {
const { initDevnetWallet } = await import('~/composables/useDevnetWallet')
initDevnetWallet()
}
Expand Down
12 changes: 9 additions & 3 deletions components/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<nav class="flex-1 px-2 py-2">
<!-- Network mode indicator -->
<div
v-if="settingsStore.devnetActive"
v-if="settingsStore.devnetActive && networkLabel"
class="mb-2 rounded-md bg-amber-500/10 border border-amber-500/20 px-3 py-1.5 text-center text-xs font-medium text-amber-400"
>
{{ networkLabel }}
Expand Down Expand Up @@ -60,18 +60,24 @@
</template>

<script setup lang="ts">
import { arbitrumSepolia } from 'viem/chains'
import { useNodesStore } from '~/stores/nodes'
import { useFilesStore } from '~/stores/files'
import { useUpdaterStore } from '~/stores/updater'
import { ANVIL_CHAIN_ID } from '~/utils/constants'

const route = useRoute()
const nodesStore = useNodesStore()
const filesStore = useFilesStore()
const updaterStore = useUpdaterStore()
const settingsStore = useSettingsStore()

const networkLabel = computed(() => {
return settingsStore.devnetIsSepolia ? 'SEPOLIA TESTNET' : 'DEVNET'
const networkLabel = computed<string | null>(() => {
switch (settingsStore.devnetChainId) {
case arbitrumSepolia.id: return 'SEPOLIA TESTNET'
case ANVIL_CHAIN_ID: return 'DEVNET'
default: return null // mainnet or unset — no badge
}
})

const mainNav = computed(() => [
Expand Down
66 changes: 45 additions & 21 deletions composables/useDevnetWallet.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { createConfig, http } from '@wagmi/core'
import { defineChain } from 'viem'
import { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts'
import { arbitrumSepolia } from 'viem/chains'
import { arbitrum, arbitrumSepolia } from 'viem/chains'
import { useWalletStore } from '~/stores/wallet'
import { useSettingsStore, getDevnetWalletKey } from '~/stores/settings'
import { getTokenAddress, getActiveChainId } from '~/utils/wallet-config'
import { ANVIL_CHAIN_ID } from '~/utils/constants'
import { formatEther, formatUnits, erc20Abi } from 'viem'
import { getBalance, readContract } from '@wagmi/core'

// Public Arbitrum One RPC fallback when no explicit URL is configured.
const ARBITRUM_ONE_RPC = 'https://arb1.arbitrum.io/rpc'
const ARBITRUM_SEPOLIA_RPC = 'https://sepolia-rollup.arbitrum.io/rpc'

let devnetWagmiConfig: any = null
let devnetAccount: PrivateKeyAccount | null = null

Expand All @@ -35,26 +39,46 @@ export function initDevnetWallet() {

devnetAccount = privateKeyToAccount(key as `0x${string}`)

// Determine which chain to use
if (settings.devnetIsSepolia || (!settings.devnetRpcUrl && !settings.devnetActive)) {
// Sepolia mode or direct key import (no manifest) — use Arbitrum Sepolia
devnetWagmiConfig = createConfig({
chains: [arbitrumSepolia],
transports: {
[arbitrumSepolia.id]: http(settings.devnetRpcUrl ?? 'https://sepolia-rollup.arbitrum.io/rpc'),
},
connectors: [],
})
} else {
// Local Anvil devnet
const chain = buildAnvilChain(settings.devnetRpcUrl!)
devnetWagmiConfig = createConfig({
chains: [chain],
transports: {
[ANVIL_CHAIN_ID]: http(settings.devnetRpcUrl!),
},
connectors: [],
})
switch (settings.devnetChainId) {
case arbitrumSepolia.id:
devnetWagmiConfig = createConfig({
chains: [arbitrumSepolia],
transports: {
[arbitrumSepolia.id]: http(settings.devnetRpcUrl ?? ARBITRUM_SEPOLIA_RPC),
},
connectors: [],
})
break

case arbitrum.id:
devnetWagmiConfig = createConfig({
chains: [arbitrum],
transports: {
[arbitrum.id]: http(settings.devnetRpcUrl ?? ARBITRUM_ONE_RPC),
},
connectors: [],
})
break

case ANVIL_CHAIN_ID: {
if (!settings.devnetRpcUrl) {
console.error('Anvil devnet requires devnetRpcUrl')
return null
}
const chain = buildAnvilChain(settings.devnetRpcUrl)
devnetWagmiConfig = createConfig({
chains: [chain],
transports: {
[ANVIL_CHAIN_ID]: http(settings.devnetRpcUrl),
},
connectors: [],
})
break
}

default:
console.error('initDevnetWallet: unknown devnetChainId', settings.devnetChainId)
return null
}

walletStore.connected = true
Expand Down
5 changes: 4 additions & 1 deletion pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@
import { invoke } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-dialog'
import { openUrl as tauriOpenUrl } from '@tauri-apps/plugin-opener'
import { arbitrum, arbitrumSepolia } from 'viem/chains'
import { setDevnetWalletKey } from '~/stores/settings'
import { useSettingsStore } from '~/stores/settings'
import { isValidEthAddress } from '~/utils/validators'
Expand Down Expand Up @@ -469,12 +470,14 @@ async function connectDirectWallet() {
setDevnetWalletKey(key)
settingsStore._devnetWalletKeySet = true
settingsStore.devnetActive = true
settingsStore.devnetIsSepolia = isSepolia
if (isSepolia) {
settingsStore.devnetChainId = arbitrumSepolia.id
settingsStore.devnetRpcUrl = 'https://sepolia-rollup.arbitrum.io/rpc'
settingsStore.devnetTokenAddress = '0x4bc1aCE0E66170375462cB4E6Af42Ad4D5EC689C'
settingsStore.devnetVaultAddress = '0xd742E8CFEf27A9a884F3EFfA239Ee2F39c276522'
} else {
// Arbitrum One mainnet — use mainnet token/vault defaults via wallet-config.
settingsStore.devnetChainId = arbitrum.id
settingsStore.devnetRpcUrl = null
settingsStore.devnetTokenAddress = null
settingsStore.devnetVaultAddress = null
Expand Down
3 changes: 2 additions & 1 deletion pages/wallet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
Connect Wallet
</button>
<p class="mt-2 text-xs text-autonomi-muted">
{{ settingsStore.devnetIsSepolia ? 'Arbitrum Sepolia testnet' : 'Arbitrum One network required' }}
{{ settingsStore.devnetChainId === arbitrumSepolia.id ? 'Arbitrum Sepolia testnet' : 'Arbitrum One network required' }}
</p>
<p class="mt-1 text-xs text-autonomi-muted">
Or import a private key in <NuxtLink to="/settings" class="text-autonomi-blue hover:underline">Settings &gt; Advanced</NuxtLink>
Expand Down Expand Up @@ -136,6 +136,7 @@
</template>

<script setup lang="ts">
import { arbitrumSepolia } from 'viem/chains'
import { useWalletStore } from '~/stores/wallet'
import { truncateAddress } from '~/utils/formatters'
import { isValidEthAddress } from '~/utils/validators'
Expand Down
18 changes: 14 additions & 4 deletions stores/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { defineStore } from 'pinia'
import { invoke } from '@tauri-apps/api/core'
import { arbitrumSepolia } from 'viem/chains'
import { ANVIL_CHAIN_ID } from '~/utils/constants'

// Private key stored outside reactive state — not visible in Vue DevTools.
let _walletKey: string | null = null
Expand Down Expand Up @@ -33,9 +35,14 @@ export const useSettingsStore = defineStore('settings', {
indelibleUserEmail: null as string | null,
themeMode: 'dark' as ThemeMode,
loaded: false,
// Devnet/testnet mode (auto-detected from manifest file)
// Devnet/testnet/direct-key mode (set by manifest or settings form).
// devnetChainId picks the chain:
// - null / not set → production mainnet (WalletConnect)
// - ANVIL_CHAIN_ID → local Anvil devnet (manifest)
// - arbitrumSepolia.id → Arbitrum Sepolia testnet
// - arbitrum.id → Arbitrum One mainnet via direct key
devnetActive: false,
devnetIsSepolia: false,
devnetChainId: null as number | null,
devnetRpcUrl: null as string | null,
devnetTokenAddress: null as string | null,
devnetVaultAddress: null as string | null,
Expand Down Expand Up @@ -154,7 +161,9 @@ export const useSettingsStore = defineStore('settings', {
const result = await invoke<any>('load_devnet_manifest')
if (result) {
this.devnetActive = true
this.devnetIsSepolia = (result.rpc_url ?? '').includes('sepolia')
this.devnetChainId = (result.rpc_url ?? '').includes('sepolia')
? arbitrumSepolia.id
: ANVIL_CHAIN_ID
this.devnetRpcUrl = result.rpc_url
this.devnetTokenAddress = result.payment_token_address
this.devnetVaultAddress = result.payment_vault_address
Expand All @@ -163,7 +172,8 @@ export const useSettingsStore = defineStore('settings', {
setDevnetWalletKey(result.wallet_private_key)
this._devnetWalletKeySet = true
}
console.info(`${this.devnetIsSepolia ? 'Sepolia' : 'Devnet'} mode active:`, result.rpc_url)
const modeLabel = this.devnetChainId === arbitrumSepolia.id ? 'Sepolia' : 'Devnet'
console.info(`${modeLabel} mode active:`, result.rpc_url)
}
} catch (e) {
// No manifest or invalid — stay in production mode
Expand Down
4 changes: 2 additions & 2 deletions tests/stores/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('settings store', () => {
await store.loadDevnetManifest()

expect(store.devnetActive).toBe(true)
expect(store.devnetIsSepolia).toBe(false)
expect(store.devnetChainId).toBe(31337) // ANVIL_CHAIN_ID
expect(store.devnetRpcUrl).toBe('http://127.0.0.1:8545')
expect(store.devnetTokenAddress).toBe('0xtoken')
expect(store.devnetVaultAddress).toBe('0xvault')
Expand All @@ -88,7 +88,7 @@ describe('settings store', () => {
await store.loadDevnetManifest()

expect(store.devnetActive).toBe(true)
expect(store.devnetIsSepolia).toBe(true)
expect(store.devnetChainId).toBe(421614) // arbitrumSepolia.id
})

it('stays in production mode when no manifest', async () => {
Expand Down
18 changes: 16 additions & 2 deletions tests/utils/wallet-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,32 @@ describe('wallet-config', () => {
expect(getActiveChainId()).toBe(42161)
})

it('returns Arbitrum mainnet (42161) when direct-key targets mainnet', () => {
settings.devnetActive = true
settings.devnetChainId = 42161

expect(getActiveChainId()).toBe(42161)
})

it('returns Anvil (31337) in local devnet', () => {
settings.devnetActive = true
settings.devnetIsSepolia = false
settings.devnetChainId = 31337

expect(getActiveChainId()).toBe(31337)
})

it('returns Sepolia (421614) in Sepolia mode', () => {
settings.devnetActive = true
settings.devnetIsSepolia = true
settings.devnetChainId = 421614

expect(getActiveChainId()).toBe(421614)
})

it('falls back to mainnet when devnetActive but chainId is null', () => {
settings.devnetActive = true
settings.devnetChainId = null

expect(getActiveChainId()).toBe(42161)
})
})
})
10 changes: 5 additions & 5 deletions utils/wallet-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { arbitrum, arbitrumSepolia } from '@reown/appkit/networks'
import { ANVIL_CHAIN_ID } from '~/utils/constants'
import { arbitrum } from '@reown/appkit/networks'
import { useSettingsStore } from '~/stores/settings'

// Project ID from cloud.reown.com (shared with project-dave)
Expand Down Expand Up @@ -36,10 +35,11 @@ export function getVaultAddress(): `0x${string}` {
return PAYMENT_VAULT_ADDRESS
}

/** Active chain ID — Anvil (31337) in local devnet, Sepolia (421614) in Sepolia mode, Arbitrum (42161) in production. */
/** Active chain ID — uses devnetChainId when set, falls back to Arbitrum One mainnet. */
export function getActiveChainId(): number {
const settings = useSettingsStore()
if (settings.devnetActive && settings.devnetIsSepolia) return arbitrumSepolia.id as number
if (settings.devnetActive) return ANVIL_CHAIN_ID
if (settings.devnetActive && settings.devnetChainId !== null) {
return settings.devnetChainId
}
return arbitrum.id
}
Loading