Add optional Chain 2138 frontend support
This commit is contained in:
@@ -12,6 +12,15 @@ VITE_RPC_URL_138=https://rpc-http-pub.d-bis.org
|
||||
# VITE_ENABLE_CHAIN2138=true
|
||||
# VITE_RPC_URL_2138=https://rpc.public-2138.defi-oracle.io
|
||||
# VITE_EXPLORER_URL_2138=https://public-2138.defi-oracle.io
|
||||
# Trustless bridge: L2 lockbox side — default 138; use 2138 for testnet (requires ENABLE_CHAIN2138)
|
||||
# VITE_TRUSTLESS_L2_CHAIN_ID=138
|
||||
# VITE_LOCKBOX_2138=0x...
|
||||
# VITE_WETH_CHAIN2138=0x...
|
||||
# VITE_CUSDT_CHAIN2138=0x...
|
||||
# VITE_CUSDC_CHAIN2138=0x...
|
||||
# VITE_TRANSACTION_MIRROR_CHAIN2138=0x...
|
||||
# Optional: set the preferred wallet/switch target to 2138 when the flag above is enabled
|
||||
# VITE_DEFAULT_FRONTEND_CHAIN_ID=2138
|
||||
|
||||
# Optional Environment Variables
|
||||
VITE_ETHERSCAN_API_KEY=YourApiKeyToken
|
||||
|
||||
@@ -22,6 +22,10 @@ cp .env.example .env.local
|
||||
# - VITE_WALLETCONNECT_PROJECT_ID (get from https://cloud.walletconnect.com)
|
||||
# - VITE_THIRDWEB_CLIENT_ID (get from https://thirdweb.com/dashboard)
|
||||
# - VITE_RPC_URL_138 (your Chain 138 RPC endpoint)
|
||||
# Optional:
|
||||
# - VITE_ENABLE_CHAIN2138=true
|
||||
# - VITE_RPC_URL_2138 / VITE_EXPLORER_URL_2138
|
||||
# - VITE_DEFAULT_FRONTEND_CHAIN_ID=2138
|
||||
```
|
||||
|
||||
### Step 3: Start Development Server
|
||||
@@ -39,6 +43,11 @@ Minimum required variables for development:
|
||||
VITE_WALLETCONNECT_PROJECT_ID=your_project_id
|
||||
VITE_THIRDWEB_CLIENT_ID=your_client_id
|
||||
VITE_RPC_URL_138=http://192.168.11.250:8545
|
||||
# Optional:
|
||||
# VITE_ENABLE_CHAIN2138=true
|
||||
# VITE_RPC_URL_2138=https://rpc.public-2138.defi-oracle.io
|
||||
# VITE_EXPLORER_URL_2138=https://public-2138.defi-oracle.io
|
||||
# VITE_DEFAULT_FRONTEND_CHAIN_ID=2138
|
||||
```
|
||||
|
||||
## 🎯 First Time Setup Checklist
|
||||
@@ -75,6 +84,7 @@ VITE_RPC_URL_138=http://192.168.11.250:8545
|
||||
### Build Errors
|
||||
- Clear cache: `rm -rf node_modules/.vite`
|
||||
- Reinstall: `rm -rf node_modules && pnpm install`
|
||||
- Run `pnpm run build:check` to verify both typecheck and production build
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -23,14 +23,24 @@ The app will be available at `http://localhost:3002`
|
||||
- DEX swap interface
|
||||
- Reserve status and peg monitoring
|
||||
- Transaction history
|
||||
- Optional Chain 2138 frontend wallet/network support behind env flags
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a `.env` file:
|
||||
Copy `.env.example` to `.env.local` and set the values you need:
|
||||
|
||||
```
|
||||
VITE_WALLETCONNECT_PROJECT_ID=your_project_id
|
||||
VITE_BRIDGE_CONTRACT_ADDRESS=0x...
|
||||
VITE_RESERVE_CONTRACT_ADDRESS=0x...
|
||||
VITE_THIRDWEB_CLIENT_ID=your_client_id
|
||||
VITE_RPC_URL_138=https://rpc-http-pub.d-bis.org
|
||||
# Optional Chain 2138 frontend support
|
||||
# VITE_ENABLE_CHAIN2138=true
|
||||
# VITE_RPC_URL_2138=https://rpc.public-2138.defi-oracle.io
|
||||
# VITE_EXPLORER_URL_2138=https://public-2138.defi-oracle.io
|
||||
# VITE_DEFAULT_FRONTEND_CHAIN_ID=2138
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The shared network source of truth lives in `src/config/networks.ts`.
|
||||
- `VITE_ENABLE_CHAIN2138` only enables optional frontend wallet/network flows.
|
||||
- Trustless bridge and Chain 138-specific operational flows remain pinned to Chain 138 unless explicitly expanded.
|
||||
|
||||
@@ -57,7 +57,7 @@ const CONTRACT_FUNCTIONS = {
|
||||
export default function FunctionPermissions() {
|
||||
const { address } = useAccount()
|
||||
const { addAuditLog } = useAdmin()
|
||||
const [roles, setRoles] = useState<Role[]>(DEFAULT_ROLES)
|
||||
const [roles] = useState<Role[]>(DEFAULT_ROLES)
|
||||
const [permissions, setPermissions] = useState<FunctionPermission[]>([])
|
||||
const [selectedContract, setSelectedContract] = useState<string>(CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER)
|
||||
const [selectedRole, setSelectedRole] = useState<string>('operator')
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useState } from 'react'
|
||||
import { useChainId, useSwitchChain } from 'wagmi'
|
||||
import { CONTRACT_ADDRESSES } from '../../config/contracts'
|
||||
import toast from 'react-hot-toast'
|
||||
import { chain138 } from '../../config/networks'
|
||||
|
||||
interface ChainConfig {
|
||||
chainId: number
|
||||
@@ -26,8 +27,8 @@ const CHAIN_CONFIGS: ChainConfig[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
chainId: 138,
|
||||
name: 'Chain 138',
|
||||
chainId: chain138.id,
|
||||
name: chain138.name,
|
||||
contractAddresses: {},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { CONTRACT_ADDRESSES } from '../../config/contracts'
|
||||
import { chain138 } from '../../config/networks'
|
||||
|
||||
interface ServiceStatus {
|
||||
name: string
|
||||
@@ -12,21 +13,23 @@ interface ServiceStatus {
|
||||
endpoint?: string
|
||||
}
|
||||
|
||||
const INITIAL_SERVICES: ServiceStatus[] = [
|
||||
{
|
||||
name: 'State Anchoring Service',
|
||||
status: 'unknown',
|
||||
lastUpdate: null,
|
||||
endpoint: 'http://192.168.11.221:8545', // Chain 138 RPC (VMID 2201, public/monitoring)
|
||||
},
|
||||
{
|
||||
name: 'Transaction Mirroring Service',
|
||||
status: 'unknown',
|
||||
lastUpdate: null,
|
||||
endpoint: 'http://192.168.11.221:8545',
|
||||
},
|
||||
]
|
||||
|
||||
export default function OffChainServices() {
|
||||
const [services, setServices] = useState<ServiceStatus[]>([
|
||||
{
|
||||
name: 'State Anchoring Service',
|
||||
status: 'unknown',
|
||||
lastUpdate: null,
|
||||
endpoint: 'http://192.168.11.221:8545', // Chain 138 RPC (VMID 2201, public/monitoring)
|
||||
},
|
||||
{
|
||||
name: 'Transaction Mirroring Service',
|
||||
status: 'unknown',
|
||||
lastUpdate: null,
|
||||
endpoint: 'http://192.168.11.221:8545',
|
||||
},
|
||||
])
|
||||
const [services, setServices] = useState<ServiceStatus[]>(INITIAL_SERVICES)
|
||||
|
||||
const checkServiceStatus = async (service: ServiceStatus): Promise<ServiceStatus> => {
|
||||
if (!service.endpoint) {
|
||||
@@ -86,7 +89,7 @@ export default function OffChainServices() {
|
||||
status: 'stopped' as 'running' | 'stopped' | 'unknown',
|
||||
lastUpdate: Date.now(),
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
// Timeout or network error
|
||||
console.error(`Health check failed for ${service.name}:`, error)
|
||||
return {
|
||||
@@ -99,7 +102,7 @@ export default function OffChainServices() {
|
||||
|
||||
useEffect(() => {
|
||||
const checkAllServices = async () => {
|
||||
const updated = await Promise.all(services.map(checkServiceStatus))
|
||||
const updated = await Promise.all(INITIAL_SERVICES.map(checkServiceStatus))
|
||||
setServices(updated)
|
||||
}
|
||||
|
||||
@@ -174,7 +177,7 @@ export default function OffChainServices() {
|
||||
<div>
|
||||
<p className="text-white/70 mb-1">State Anchoring Service</p>
|
||||
<p className="text-white/60 text-xs">
|
||||
Monitors ChainID 138 blocks and submits state proofs to MainnetTether contract.
|
||||
Monitors {chain138.name} blocks and submits state proofs to MainnetTether contract.
|
||||
</p>
|
||||
<p className="text-white/60 text-xs font-mono mt-1">
|
||||
Contract: {CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}
|
||||
@@ -183,7 +186,7 @@ export default function OffChainServices() {
|
||||
<div>
|
||||
<p className="text-white/70 mb-1">Transaction Mirroring Service</p>
|
||||
<p className="text-white/60 text-xs">
|
||||
Monitors ChainID 138 transactions and mirrors them to TransactionMirror contract.
|
||||
Monitors {chain138.name} transactions and mirrors them to TransactionMirror contract.
|
||||
</p>
|
||||
<p className="text-white/60 text-xs font-mono mt-1">
|
||||
Contract: {CONTRACT_ADDRESSES.mainnet.TRANSACTION_MIRROR}
|
||||
|
||||
@@ -4,10 +4,12 @@ import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from
|
||||
import { CONTRACT_ADDRESSES } from '../../config/contracts'
|
||||
import { PAYMENT_CHANNEL_MANAGER_ABI } from '../../abis/PaymentChannelManager'
|
||||
import toast from 'react-hot-toast'
|
||||
import { chain138, chain2138Testnet } from '../../config/networks'
|
||||
|
||||
function getManagerAddress(chainId: number): `0x${string}` | undefined {
|
||||
if (chainId === 1) return CONTRACT_ADDRESSES.mainnet.PAYMENT_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
if (chainId === 138) return CONTRACT_ADDRESSES.chain138.PAYMENT_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
if (chainId === 2138) return CONTRACT_ADDRESSES.chain2138.PAYMENT_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -80,7 +82,10 @@ export default function PaymentChannelAdmin() {
|
||||
if (managerAddress == null) {
|
||||
return (
|
||||
<div className="rounded-xl bg-white/5 p-6 text-white/80">
|
||||
<p>Payment channel manager not deployed on this chain. Set PAYMENT_CHANNEL_MANAGER in config.</p>
|
||||
<p>
|
||||
Payment channel manager not deployed on this chain. Set `PAYMENT_CHANNEL_MANAGER`
|
||||
in config for Ethereum Mainnet, {chain138.name}, or {chain2138Testnet.name}.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from
|
||||
import { CONTRACT_ADDRESSES } from '../../config/contracts'
|
||||
import { PAYMENT_CHANNEL_MANAGER_ABI } from '../../abis/PaymentChannelManager'
|
||||
import toast from 'react-hot-toast'
|
||||
import { chain138, chain2138Testnet } from '../../config/networks'
|
||||
|
||||
const CHANNEL_STATUS = ['None', 'Open', 'Dispute', 'Closed'] as const
|
||||
|
||||
function getManagerAddress(chainId: number): `0x${string}` | undefined {
|
||||
if (chainId === 1) return CONTRACT_ADDRESSES.mainnet.PAYMENT_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
if (chainId === 138) return CONTRACT_ADDRESSES.chain138.PAYMENT_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
if (chainId === 2138) return CONTRACT_ADDRESSES.chain2138.PAYMENT_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -48,7 +50,7 @@ export default function PaymentChannels() {
|
||||
})
|
||||
|
||||
const { writeContract, data: hash, isPending } = useWriteContract()
|
||||
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash })
|
||||
const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash })
|
||||
|
||||
const channelIds = useMemo(() => {
|
||||
const n = Number(channelCount)
|
||||
@@ -192,7 +194,10 @@ export default function PaymentChannels() {
|
||||
if (managerAddress == null) {
|
||||
return (
|
||||
<div className="rounded-xl bg-white/5 p-6 text-white/80">
|
||||
<p>Payment channel manager not deployed on this chain. Set PAYMENT_CHANNEL_MANAGER in config for Mainnet (1) or Chain 138.</p>
|
||||
<p>
|
||||
Payment channel manager not deployed on this chain. Set `PAYMENT_CHANNEL_MANAGER`
|
||||
in config for Ethereum Mainnet, {chain138.name}, or {chain2138Testnet.name}.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -205,7 +210,7 @@ export default function PaymentChannels() {
|
||||
<div className="mb-4 p-3 rounded-lg bg-amber-500/20 text-amber-200 text-sm">Contract is paused. Only close/finalize allowed.</div>
|
||||
)}
|
||||
<p className="text-white/70 text-sm mb-4">
|
||||
Open a channel with a counterparty, fund it (optional second side), then close cooperatively or via dispute window. On Chain 138, channel txs are mirrored to Mainnet.
|
||||
Open a channel with a counterparty, fund it (optional second side), then close cooperatively or via dispute window. On {chain138.name}, channel transactions are mirrored to Mainnet.
|
||||
</p>
|
||||
<div className="text-white/60 text-xs mb-4 space-y-1">
|
||||
<p>Payment channels (above) = state channels for payments. For cross-chain or routed payments, general state channels, or channel networks:</p>
|
||||
@@ -382,7 +387,6 @@ function ChannelRow({
|
||||
const status = Number(ch.status)
|
||||
const statusLabel = CHANNEL_STATUS[status] ?? 'Unknown'
|
||||
const isMine = currentUser && (ch.participantA.toLowerCase() === currentUser.toLowerCase() || ch.participantB.toLowerCase() === currentUser.toLowerCase())
|
||||
const total = ch.depositA + ch.depositB
|
||||
return (
|
||||
<div className="rounded-lg border border-white/20 p-4 bg-black/20">
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
|
||||
@@ -26,12 +26,16 @@ export default function RealtimeMonitor() {
|
||||
useEffect(() => {
|
||||
if (monitoring && publicClient) {
|
||||
const monitor = getRealtimeMonitor(wsUrl || undefined)
|
||||
let isMounted = true
|
||||
let stopMainnetTether: (() => void) | null = null
|
||||
let stopTransactionMirror: (() => void) | null = null
|
||||
|
||||
// Start monitoring MainnetTether
|
||||
const stopMainnetTether = monitorContractState(
|
||||
monitorContractState(
|
||||
CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER,
|
||||
publicClient,
|
||||
(state) => {
|
||||
if (!isMounted) return
|
||||
setContractStates((prev) => ({
|
||||
...prev,
|
||||
[CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER]: {
|
||||
@@ -41,13 +45,18 @@ export default function RealtimeMonitor() {
|
||||
},
|
||||
}))
|
||||
}
|
||||
)
|
||||
).then((unsubscribe) => {
|
||||
stopMainnetTether = unsubscribe
|
||||
}).catch(() => {
|
||||
stopMainnetTether = null
|
||||
})
|
||||
|
||||
// Start monitoring TransactionMirror
|
||||
const stopTransactionMirror = monitorContractState(
|
||||
monitorContractState(
|
||||
CONTRACT_ADDRESSES.mainnet.TRANSACTION_MIRROR,
|
||||
publicClient,
|
||||
(state) => {
|
||||
if (!isMounted) return
|
||||
setContractStates((prev) => ({
|
||||
...prev,
|
||||
[CONTRACT_ADDRESSES.mainnet.TRANSACTION_MIRROR]: {
|
||||
@@ -57,7 +66,11 @@ export default function RealtimeMonitor() {
|
||||
},
|
||||
}))
|
||||
}
|
||||
)
|
||||
).then((unsubscribe) => {
|
||||
stopTransactionMirror = unsubscribe
|
||||
}).catch(() => {
|
||||
stopTransactionMirror = null
|
||||
})
|
||||
|
||||
// Subscribe to events (async handling)
|
||||
let unsubscribeBlocks: (() => void) | null = null
|
||||
@@ -79,9 +92,10 @@ export default function RealtimeMonitor() {
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopMainnetTether()
|
||||
stopTransactionMirror()
|
||||
if (unsubscribeBlocks) unsubscribeBlocks()
|
||||
isMounted = false
|
||||
stopMainnetTether?.()
|
||||
stopTransactionMirror?.()
|
||||
unsubscribeBlocks?.()
|
||||
}
|
||||
}
|
||||
}, [monitoring, publicClient, chainId, wsUrl])
|
||||
@@ -222,7 +236,7 @@ export default function RealtimeMonitor() {
|
||||
{!monitoring && (
|
||||
<div className="bg-yellow-500/20 border border-yellow-500/50 rounded-lg p-4">
|
||||
<p className="text-yellow-200 text-sm">
|
||||
Click "Start Monitoring" to begin real-time monitoring of contract states and events.
|
||||
Click "Start Monitoring" to begin real-time monitoring of contract states and events.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,12 +4,14 @@ import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from
|
||||
import { CONTRACT_ADDRESSES } from '../../config/contracts'
|
||||
import { GENERIC_STATE_CHANNEL_MANAGER_ABI } from '../../abis/GenericStateChannelManager'
|
||||
import toast from 'react-hot-toast'
|
||||
import { chain138, chain2138Testnet } from '../../config/networks'
|
||||
|
||||
const CHANNEL_STATUS = ['None', 'Open', 'Dispute', 'Closed'] as const
|
||||
|
||||
function getManagerAddress(chainId: number): `0x${string}` | undefined {
|
||||
if (chainId === 1) return CONTRACT_ADDRESSES.mainnet.GENERIC_STATE_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
if (chainId === 138) return CONTRACT_ADDRESSES.chain138.GENERIC_STATE_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
if (chainId === 2138) return CONTRACT_ADDRESSES.chain2138.GENERIC_STATE_CHANNEL_MANAGER as `0x${string}` | undefined
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -105,7 +107,10 @@ export default function StateChannels() {
|
||||
if (managerAddress == null) {
|
||||
return (
|
||||
<div className="rounded-xl bg-white/5 p-6 text-white/80">
|
||||
<p>Generic state channel manager not deployed on this chain. Set GENERIC_STATE_CHANNEL_MANAGER in config for Mainnet (1) or Chain 138.</p>
|
||||
<p>
|
||||
Generic state channel manager not deployed on this chain. Set `GENERIC_STATE_CHANNEL_MANAGER`
|
||||
in config for Ethereum Mainnet, {chain138.name}, or {chain2138Testnet.name}.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAccount } from 'wagmi';
|
||||
import { ethers } from 'ethers';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
interface ChainMetadata {
|
||||
@@ -19,6 +18,10 @@ interface ChainMetadata {
|
||||
avgBlockTime: number;
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : 'Unknown error';
|
||||
}
|
||||
|
||||
export default function ChainManagementDashboard() {
|
||||
const { address, isConnected } = useAccount();
|
||||
const [chains, setChains] = useState<ChainMetadata[]>([]);
|
||||
@@ -63,20 +66,20 @@ export default function ChainManagementDashboard() {
|
||||
avgBlockTime: 2
|
||||
}
|
||||
]);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load chains: ${error.message}`);
|
||||
} catch (error: unknown) {
|
||||
toast.error(`Failed to load chains: ${getErrorMessage(error)}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleChain = async (chainId: number, chainIdentifier: string, currentStatus: boolean) => {
|
||||
const toggleChain = async (currentStatus: boolean) => {
|
||||
try {
|
||||
// TODO: Call ChainRegistry.setChainActive()
|
||||
toast.success(`Chain ${currentStatus ? 'disabled' : 'enabled'}`);
|
||||
loadChains();
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to toggle chain: ${error.message}`);
|
||||
} catch (error: unknown) {
|
||||
toast.error(`Failed to toggle chain: ${getErrorMessage(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,7 +125,7 @@ export default function ChainManagementDashboard() {
|
||||
{chain.isActive ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleChain(chain.chainId, chain.chainIdentifier, chain.isActive)}
|
||||
onClick={() => toggleChain(chain.isActive)}
|
||||
className={`px-4 py-2 rounded-lg font-semibold transition-colors ${
|
||||
chain.isActive
|
||||
? 'bg-red-600 hover:bg-red-700 text-white'
|
||||
@@ -156,7 +159,7 @@ export default function ChainManagementDashboard() {
|
||||
<option value="ethereum">Ethereum Mainnet</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={() => toast.info('Deployment feature coming soon')}
|
||||
onClick={() => toast('Deployment feature coming soon', { icon: 'ℹ️' })}
|
||||
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-semibold"
|
||||
>
|
||||
Deploy Chain Adapter
|
||||
|
||||
@@ -33,6 +33,16 @@ interface BridgeButtonsProps {
|
||||
recipientAddress?: string; // Defaults to connected wallet
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
const maybeMessage = 'message' in error ? error.message : undefined
|
||||
const maybeReason = 'reason' in error ? error.reason : undefined
|
||||
if (typeof maybeMessage === 'string' && maybeMessage.length > 0) return maybeMessage
|
||||
if (typeof maybeReason === 'string' && maybeReason.length > 0) return maybeReason
|
||||
}
|
||||
return 'Unknown error'
|
||||
}
|
||||
|
||||
/** Fetches CCIP fee only when amount > 0 (bridge reverts on zero). Reports result to parent via onFee. */
|
||||
function CalculateFeeFetcher({
|
||||
bridgeContract,
|
||||
@@ -59,7 +69,7 @@ function CalculateFeeFetcher({
|
||||
/** Inner bridge form: only mounted when address is set so balance/allowance are never called with zero address. */
|
||||
function BridgeButtonsConnected({
|
||||
address,
|
||||
destinationChainSelector,
|
||||
destinationChainSelector = CHAIN_SELECTORS.ETHEREUM_MAINNET,
|
||||
recipientAddress,
|
||||
}: BridgeButtonsProps & { address: string }) {
|
||||
const [amount, setAmount] = useState<string>('');
|
||||
@@ -190,10 +200,9 @@ function BridgeButtonsConnected({
|
||||
toast.success('ETH wrapped successfully!', { id: toastId });
|
||||
setAmount('');
|
||||
await handleRefreshBalances();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Wrap error:', error);
|
||||
const errorMessage = error?.message || error?.reason || 'Unknown error';
|
||||
toast.error(`Wrap failed: ${errorMessage}`, { id: toastId });
|
||||
toast.error(`Wrap failed: ${getErrorMessage(error)}`, { id: toastId });
|
||||
} finally {
|
||||
setIsWrapping(false);
|
||||
}
|
||||
@@ -246,10 +255,9 @@ function BridgeButtonsConnected({
|
||||
|
||||
toast.success('All approvals successful!', { id: toastId });
|
||||
await handleRefreshBalances();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Approve error:', error);
|
||||
const errorMessage = error?.message || error?.reason || 'Unknown error';
|
||||
toast.error(`Approval failed: ${errorMessage}`, { id: toastId });
|
||||
toast.error(`Approval failed: ${getErrorMessage(error)}`, { id: toastId });
|
||||
} finally {
|
||||
setIsApproving(false);
|
||||
}
|
||||
@@ -328,10 +336,9 @@ function BridgeButtonsConnected({
|
||||
);
|
||||
setAmount('');
|
||||
await handleRefreshBalances();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Bridge error:', error);
|
||||
const errorMessage = error?.message || error?.reason || 'Unknown error';
|
||||
toast.error(`Bridge failed: ${errorMessage}`, { id: toastId });
|
||||
toast.error(`Bridge failed: ${getErrorMessage(error)}`, { id: toastId });
|
||||
} finally {
|
||||
setIsBridging(false);
|
||||
}
|
||||
@@ -364,7 +371,7 @@ function BridgeButtonsConnected({
|
||||
recipient &&
|
||||
ethers.utils.isAddress(recipient);
|
||||
|
||||
const destination = CCIP_DESTINATIONS.find((d) => d.selector === destinationChainSelector);
|
||||
const destination = CCIP_DESTINATIONS.find((d) => d.selector === destinationChainSelector) ?? null;
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { BRIDGE_QUOTE_URL } from '../../config/bridge';
|
||||
import { defaultFrontendChainId } from '../../config/networks';
|
||||
|
||||
interface QuoteResult {
|
||||
transferId?: string;
|
||||
@@ -19,7 +20,7 @@ interface QuoteResult {
|
||||
export default function SwapBridgeSwapQuoteForm() {
|
||||
const [sourceToken, setSourceToken] = useState('');
|
||||
const [destinationToken, setDestinationToken] = useState('');
|
||||
const [sourceChainId, setSourceChainId] = useState('138');
|
||||
const [sourceChainId, setSourceChainId] = useState(String(defaultFrontendChainId));
|
||||
const [destinationChainId, setDestinationChainId] = useState('137');
|
||||
const [amount, setAmount] = useState('');
|
||||
const [quote, setQuote] = useState<QuoteResult | null>(null);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { defaultFrontendChain, chain2138TestnetEnabled } from '../../config/networks';
|
||||
|
||||
interface ThirdwebBridgeWidgetProps {
|
||||
clientId: string;
|
||||
@@ -15,7 +16,7 @@ interface ThirdwebBridgeWidgetProps {
|
||||
|
||||
export default function ThirdwebBridgeWidget({
|
||||
clientId,
|
||||
fromChain = 138,
|
||||
fromChain = defaultFrontendChain.id,
|
||||
toChain,
|
||||
fromToken,
|
||||
toToken
|
||||
@@ -42,10 +43,16 @@ export default function ThirdwebBridgeWidget({
|
||||
Bridge to EVM Chain
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
Bridge or swap tokens from Chain 138 to supported EVM destinations using ThirdWeb
|
||||
Bridge or swap tokens from {defaultFrontendChain.name} to supported EVM destinations using ThirdWeb
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{chain2138TestnetEnabled && (
|
||||
<p className="mb-4 text-sm text-blue-700 bg-blue-50 border border-blue-200 rounded-xl px-4 py-3">
|
||||
Chain 2138 is enabled for frontend wallet flows. The Thirdweb embed will default to the configured frontend source chain.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-semibold mb-3 text-gray-700">
|
||||
Destination Chain
|
||||
|
||||
@@ -14,11 +14,12 @@ import {
|
||||
CHALLENGE_MANAGER_ABI,
|
||||
ERC20_ABI,
|
||||
} from '../../config/bridge';
|
||||
import { chain138 } from '../../config/networks';
|
||||
import { getTrustlessL2LockboxConfig, TRUSTLESS_L2_CHAIN_ID } from '../../config/trustlessL2';
|
||||
|
||||
const MAINNET_CHAIN_ID = 1;
|
||||
const OUTPUT_ASSET_ETH = 0;
|
||||
const OUTPUT_ASSET_WETH = 1;
|
||||
const ZERO = '0x0000000000000000000000000000000000000000' as const;
|
||||
|
||||
const STABLECOIN_OPTIONS = [
|
||||
{ value: 'USDT', label: 'USDT', address: TRUSTLESS.mainnet.USDT },
|
||||
@@ -26,13 +27,16 @@ const STABLECOIN_OPTIONS = [
|
||||
{ value: 'DAI', label: 'DAI', address: TRUSTLESS.mainnet.DAI },
|
||||
] as const;
|
||||
|
||||
const LOCKBOX_ERC20_OPTIONS = [
|
||||
{ value: 'WETH', label: 'WETH', address: TRUSTLESS.chain138.WETH, decimals: 18 },
|
||||
{ value: 'cUSDT', label: 'cUSDT', address: TRUSTLESS.chain138.CUSDT, decimals: 6 },
|
||||
{ value: 'cUSDC', label: 'cUSDC', address: TRUSTLESS.chain138.CUSDC, decimals: 6 },
|
||||
].filter((o) => o.address !== '0x0000000000000000000000000000000000000000') as Array<{ value: string; label: string; address: `0x${string}`; decimals: number }>;
|
||||
|
||||
export default function TrustlessBridgeForm() {
|
||||
const l2 = getTrustlessL2LockboxConfig();
|
||||
const nativeSymbol = l2.viemChain.nativeCurrency.symbol;
|
||||
const l2Label = l2.viemChain.name;
|
||||
|
||||
const LOCKBOX_ERC20_OPTIONS = [
|
||||
{ value: 'WETH', label: 'WETH', address: l2.weth, decimals: 18 },
|
||||
{ value: 'cUSDT', label: 'cUSDT', address: l2.cusdt, decimals: 6 },
|
||||
{ value: 'cUSDC', label: 'cUSDC', address: l2.cusdc, decimals: 6 },
|
||||
].filter((o) => o.address !== ZERO) as Array<{ value: string; label: string; address: `0x${string}`; decimals: number }>;
|
||||
const { address } = useAccount();
|
||||
const chainId = useChainId();
|
||||
const switchChain = useSwitchChain();
|
||||
@@ -49,17 +53,17 @@ export default function TrustlessBridgeForm() {
|
||||
const [checkDepositId, setCheckDepositId] = useState('');
|
||||
const [finalizeDepositId, setFinalizeDepositId] = useState('');
|
||||
|
||||
const lockboxAddress = TRUSTLESS.chain138.LOCKBOX_138;
|
||||
const lockboxAddress = l2.lockbox;
|
||||
const coordinatorAddress = TRUSTLESS.mainnet.DUAL_ROUTER_BRIDGE_SWAP_COORDINATOR;
|
||||
const challengeManagerAddress = TRUSTLESS.mainnet.CHALLENGE_MANAGER;
|
||||
const erc20TokenAddress = LOCKBOX_ERC20_OPTIONS.find((o) => o.value === erc20Token)?.address ?? TRUSTLESS.chain138.WETH;
|
||||
const erc20TokenAddress = LOCKBOX_ERC20_OPTIONS.find((o) => o.value === erc20Token)?.address ?? l2.weth;
|
||||
|
||||
const { data: nonceData } = useReadContract({
|
||||
address: address ? lockboxAddress : undefined,
|
||||
abi: LOCKBOX_138_ABI,
|
||||
functionName: 'getNonce',
|
||||
args: address ? [address] : undefined,
|
||||
chainId: chain138.id,
|
||||
chainId: l2.chainId,
|
||||
});
|
||||
|
||||
const { data: canSwapResult, refetch: refetchCanSwap } = useReadContract({
|
||||
@@ -83,14 +87,16 @@ export default function TrustlessBridgeForm() {
|
||||
abi: ERC20_ABI,
|
||||
functionName: 'allowance',
|
||||
args: address ? [address, lockboxAddress] : undefined,
|
||||
chainId: chain138.id,
|
||||
chainId: l2.chainId,
|
||||
});
|
||||
|
||||
const { writeContract, data: hash, isPending, reset } = useWriteContract();
|
||||
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash });
|
||||
|
||||
const onChain138 = chainId === chain138.id;
|
||||
const onL2 = chainId === l2.chainId;
|
||||
const onMainnet = chainId === MAINNET_CHAIN_ID;
|
||||
const l2Misconfigured =
|
||||
TRUSTLESS_L2_CHAIN_ID === 2138 && l2.chainId === 138;
|
||||
|
||||
const stablecoinAddress = STABLECOIN_OPTIONS.find((o) => o.value === stablecoin)?.address ?? TRUSTLESS.mainnet.USDT;
|
||||
|
||||
@@ -105,8 +111,8 @@ export default function TrustlessBridgeForm() {
|
||||
return;
|
||||
}
|
||||
const nonceBytes = padHex(toHex(nonceData ?? 0n), { size: 32 }) as `0x${string}`;
|
||||
if (!onChain138) {
|
||||
switchChain?.switchChain({ chainId: chain138.id });
|
||||
if (!onL2) {
|
||||
switchChain?.switchChain({ chainId: l2.chainId });
|
||||
return;
|
||||
}
|
||||
writeContract(
|
||||
@@ -139,8 +145,8 @@ export default function TrustlessBridgeForm() {
|
||||
toast.error('Amount must be > 0');
|
||||
return;
|
||||
}
|
||||
if (!onChain138) {
|
||||
switchChain?.switchChain({ chainId: chain138.id });
|
||||
if (!onL2) {
|
||||
switchChain?.switchChain({ chainId: l2.chainId });
|
||||
return;
|
||||
}
|
||||
writeContract(
|
||||
@@ -167,8 +173,8 @@ export default function TrustlessBridgeForm() {
|
||||
return;
|
||||
}
|
||||
const nonceBytes = padHex(toHex(nonceData ?? 0n), { size: 32 }) as `0x${string}`;
|
||||
if (!onChain138) {
|
||||
switchChain?.switchChain({ chainId: chain138.id });
|
||||
if (!onL2) {
|
||||
switchChain?.switchChain({ chainId: l2.chainId });
|
||||
return;
|
||||
}
|
||||
writeContract(
|
||||
@@ -245,19 +251,31 @@ export default function TrustlessBridgeForm() {
|
||||
|
||||
return (
|
||||
<div className="space-y-8 rounded-2xl bg-white/5 backdrop-blur border border-cyan-400/20 p-6">
|
||||
<h2 className="text-2xl font-bold text-white">Trustless Bridge (Chain 138 ↔ Mainnet)</h2>
|
||||
<h2 className="text-2xl font-bold text-white">Trustless Bridge ({l2Label} ↔ Mainnet)</h2>
|
||||
{l2Misconfigured && (
|
||||
<p className="text-amber-200/90 text-sm mb-2 rounded-lg border border-amber-400/40 bg-amber-500/10 p-3">
|
||||
VITE_TRUSTLESS_L2_CHAIN_ID is 2138 but VITE_ENABLE_CHAIN2138 is off — L2 fell back to {l2Label}. Enable the testnet chain or set L2 to 138.
|
||||
</p>
|
||||
)}
|
||||
{l2.chainId === 2138 && lockboxAddress === ZERO && (
|
||||
<p className="text-amber-200/90 text-sm mb-2 rounded-lg border border-amber-400/40 bg-amber-500/10 p-3">
|
||||
Set VITE_LOCKBOX_2138 (and token addresses) after deploying the testnet lockbox.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Lockbox 138 deposit */}
|
||||
{/* Lockbox L2 deposit */}
|
||||
<section>
|
||||
<h3 className="text-lg font-semibold text-cyan-300 mb-3">1. Deposit on Chain 138 (Lockbox)</h3>
|
||||
<p className="text-white/70 text-sm mb-3">Deposit native ETH or ERC-20 (WETH, cUSDT, cUSDC) on Chain 138. Then relay and finalize claim on Mainnet before Bridge & Swap.</p>
|
||||
{!onChain138 && (
|
||||
<h3 className="text-lg font-semibold text-cyan-300 mb-3">1. Deposit on {l2Label} (Lockbox)</h3>
|
||||
<p className="text-white/70 text-sm mb-3">
|
||||
Deposit native {nativeSymbol} or ERC-20 (WETH, cUSDT, cUSDC) on {l2Label}. Then relay and finalize claim on Mainnet before Bridge & Swap.
|
||||
</p>
|
||||
{!onL2 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => switchChain?.switchChain({ chainId: chain138.id })}
|
||||
onClick={() => switchChain?.switchChain({ chainId: l2.chainId })}
|
||||
className="mb-3 px-4 py-2 rounded-lg bg-cyan-500/20 text-cyan-300 border border-cyan-400/40"
|
||||
>
|
||||
Switch to Chain 138
|
||||
Switch to {l2Label}
|
||||
</button>
|
||||
)}
|
||||
<div className="grid gap-3 max-w-md">
|
||||
@@ -265,13 +283,16 @@ export default function TrustlessBridgeForm() {
|
||||
<span className="text-white/80">Deposit type:</span>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" checked={depositType === 'native'} onChange={() => setDepositType('native')} className="rounded" />
|
||||
<span>Native ETH</span>
|
||||
<span>Native {nativeSymbol}</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" checked={depositType === 'erc20'} onChange={() => setDepositType('erc20')} className="rounded" />
|
||||
<span>ERC-20</span>
|
||||
</label>
|
||||
</div>
|
||||
{depositType === 'erc20' && LOCKBOX_ERC20_OPTIONS.length === 0 && (
|
||||
<p className="text-amber-200/90 text-sm">No ERC-20 tokens configured for this L2 (set non-zero addresses in env).</p>
|
||||
)}
|
||||
{depositType === 'erc20' && LOCKBOX_ERC20_OPTIONS.length > 0 && (
|
||||
<div className="flex gap-4 items-center">
|
||||
<label htmlFor="trustless-bridge-token" className="text-white/80">Token:</label>
|
||||
@@ -303,7 +324,7 @@ export default function TrustlessBridgeForm() {
|
||||
id="trustless-bridge-amount"
|
||||
name="amount"
|
||||
type="text"
|
||||
placeholder={depositType === 'native' ? 'Amount (ETH)' : 'Amount (token units)'}
|
||||
placeholder={depositType === 'native' ? `Amount (${nativeSymbol})` : 'Amount (token units)'}
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-cyan-400/30"
|
||||
@@ -311,11 +332,11 @@ export default function TrustlessBridgeForm() {
|
||||
{depositType === 'native' && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={isPending || isConfirming || !address || !amount || !recipient || !onChain138}
|
||||
disabled={isPending || isConfirming || !address || !amount || !recipient || !onL2 || lockboxAddress === ZERO}
|
||||
onClick={handleDepositNative}
|
||||
className="px-4 py-2 rounded-lg bg-cyan-500 text-white font-medium disabled:opacity-50"
|
||||
>
|
||||
{isPending || isConfirming ? 'Confirming...' : 'Deposit native ETH'}
|
||||
{isPending || isConfirming ? 'Confirming...' : `Deposit native ${nativeSymbol}`}
|
||||
</button>
|
||||
)}
|
||||
{depositType === 'erc20' && (
|
||||
@@ -323,7 +344,7 @@ export default function TrustlessBridgeForm() {
|
||||
{tokenAllowance !== undefined && BigInt(amount || 0) * 10n ** 18n > (tokenAllowance ?? 0n) && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={isPending || isConfirming || !address || !amount || !onChain138}
|
||||
disabled={isPending || isConfirming || !address || !amount || !onL2 || lockboxAddress === ZERO}
|
||||
onClick={handleApprove}
|
||||
className="px-4 py-2 rounded-lg bg-cyan-500/80 text-white font-medium disabled:opacity-50"
|
||||
>
|
||||
@@ -332,7 +353,7 @@ export default function TrustlessBridgeForm() {
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
disabled={isPending || isConfirming || !address || !amount || !recipient || !onChain138 || (tokenAllowance !== undefined && erc20AmountWei > (tokenAllowance ?? 0n))}
|
||||
disabled={isPending || isConfirming || !address || !amount || !recipient || !onL2 || lockboxAddress === ZERO || (tokenAllowance !== undefined && erc20AmountWei > (tokenAllowance ?? 0n))}
|
||||
onClick={handleDepositERC20}
|
||||
className="px-4 py-2 rounded-lg bg-cyan-500 text-white font-medium disabled:opacity-50"
|
||||
>
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Link, useLocation } from 'react-router-dom'
|
||||
import { useRef } from 'react'
|
||||
import WalletConnect from '../wallet/WalletConnect'
|
||||
import WalletDisconnectNotice from '../wallet/WalletDisconnectNotice'
|
||||
|
||||
const EXPLORER_URL = 'https://explorer.d-bis.org'
|
||||
import { defaultFrontendExplorerUrl } from '../../config/networks'
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode
|
||||
@@ -59,7 +58,7 @@ export default function Layout({ children }: LayoutProps) {
|
||||
Wallets
|
||||
</Link>
|
||||
<a
|
||||
href={EXPLORER_URL}
|
||||
href={defaultFrontendExplorerUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium text-[#A0A0A0] hover:text-white hover:bg-white/5 transition-colors"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { useAccount, useConnect, useDisconnect, useChainId, useSwitchChain } from 'wagmi'
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
|
||||
const CHAIN_138_ID = 138
|
||||
const EXPLORER_URL = 'https://explorer.d-bis.org'
|
||||
import {
|
||||
defaultFrontendChainId,
|
||||
defaultFrontendChainName,
|
||||
defaultFrontendExplorerUrl,
|
||||
frontendSourceChainIds,
|
||||
} from '../../config/networks'
|
||||
|
||||
interface WalletConnectProps {
|
||||
/** Callback before disconnect so we can treat it as user-initiated (no "disconnected" toast). */
|
||||
@@ -21,7 +24,7 @@ export default function WalletConnect({ onBeforeDisconnect }: WalletConnectProps
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && chainId !== CHAIN_138_ID) {
|
||||
if (isConnected && !frontendSourceChainIds.includes(chainId)) {
|
||||
setShowChainWarning(true)
|
||||
} else {
|
||||
setShowChainWarning(false)
|
||||
@@ -40,7 +43,7 @@ export default function WalletConnect({ onBeforeDisconnect }: WalletConnectProps
|
||||
|
||||
const handleSwitchChain = async () => {
|
||||
try {
|
||||
await switchChain({ chainId: CHAIN_138_ID })
|
||||
await switchChain({ chainId: defaultFrontendChainId })
|
||||
} catch (error) {
|
||||
console.error('Failed to switch chain:', error)
|
||||
}
|
||||
@@ -59,7 +62,7 @@ export default function WalletConnect({ onBeforeDisconnect }: WalletConnectProps
|
||||
}
|
||||
|
||||
const viewOnExplorer = () => {
|
||||
if (address) window.open(`${EXPLORER_URL}/address/${address}`, '_blank', 'noopener')
|
||||
if (address) window.open(`${defaultFrontendExplorerUrl}/address/${address}`, '_blank', 'noopener')
|
||||
setShowDropdown(false)
|
||||
}
|
||||
|
||||
@@ -81,7 +84,7 @@ export default function WalletConnect({ onBeforeDisconnect }: WalletConnectProps
|
||||
Switching...
|
||||
</>
|
||||
) : (
|
||||
'Switch to Chain 138'
|
||||
`Switch to ${defaultFrontendChainName}`
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
32
frontend-dapp/src/config/__tests__/networks.test.ts
Normal file
32
frontend-dapp/src/config/__tests__/networks.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const loadNetworksModule = async () => import('../networks')
|
||||
|
||||
describe('frontend network config', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('keeps Chain 2138 disabled by default', async () => {
|
||||
const networks = await loadNetworksModule()
|
||||
|
||||
expect(networks.chain2138TestnetEnabled).toBe(false)
|
||||
expect(networks.frontendSourceChainIds).toEqual([138])
|
||||
expect(networks.defaultFrontendChainId).toBe(138)
|
||||
})
|
||||
|
||||
it('enables Chain 2138 and promotes it to the default frontend chain when configured', async () => {
|
||||
vi.stubEnv('VITE_ENABLE_CHAIN2138', 'true')
|
||||
vi.stubEnv('VITE_DEFAULT_FRONTEND_CHAIN_ID', '2138')
|
||||
vi.stubEnv('VITE_RPC_URL_2138', 'https://rpc.public-2138.defi-oracle.io')
|
||||
vi.stubEnv('VITE_EXPLORER_URL_2138', 'https://public-2138.defi-oracle.io')
|
||||
|
||||
const networks = await loadNetworksModule()
|
||||
|
||||
expect(networks.chain2138TestnetEnabled).toBe(true)
|
||||
expect(networks.frontendSourceChainIds).toEqual([138, 2138])
|
||||
expect(networks.defaultFrontendChainId).toBe(2138)
|
||||
expect(networks.defaultFrontendChainName).toBe('Defi Oracle Meta Testnet')
|
||||
})
|
||||
})
|
||||
@@ -52,6 +52,13 @@ export const TRUSTLESS = {
|
||||
CUSDT: (import.meta.env.VITE_CUSDT_CHAIN138 || zero) as `0x${string}`,
|
||||
CUSDC: (import.meta.env.VITE_CUSDC_CHAIN138 || zero) as `0x${string}`,
|
||||
},
|
||||
/** Testnet 2138 — set addresses after deploy; used when VITE_TRUSTLESS_L2_CHAIN_ID=2138 */
|
||||
chain2138: {
|
||||
LOCKBOX_2138: (import.meta.env.VITE_LOCKBOX_2138 || zero) as `0x${string}`,
|
||||
WETH: (import.meta.env.VITE_WETH_CHAIN2138 || zero) as `0x${string}`,
|
||||
CUSDT: (import.meta.env.VITE_CUSDT_CHAIN2138 || zero) as `0x${string}`,
|
||||
CUSDC: (import.meta.env.VITE_CUSDC_CHAIN2138 || zero) as `0x${string}`,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mainnet } from 'wagmi/chains'
|
||||
import type { Address } from 'viem'
|
||||
import { TRUSTLESS } from './bridge'
|
||||
import { chain138 as chain138Network, chain2138Testnet } from './networks'
|
||||
|
||||
// Contract addresses on Ethereum Mainnet and Chain 138
|
||||
// Trustless bridge (Lockbox, Inbox, LP, Coordinators, ChallengeManager) — use TRUSTLESS from bridge.ts as single source of truth
|
||||
@@ -24,24 +25,28 @@ export const CONTRACT_ADDRESSES = {
|
||||
TWOWAY_BRIDGE_L2: undefined as Address | undefined,
|
||||
LOCKBOX_138: TRUSTLESS.chain138.LOCKBOX_138 as Address,
|
||||
},
|
||||
chain2138: {
|
||||
TRANSACTION_MIRROR: (import.meta.env.VITE_TRANSACTION_MIRROR_CHAIN2138 || undefined) as Address | undefined,
|
||||
PAYMENT_CHANNEL_MANAGER: undefined as Address | undefined,
|
||||
GENERIC_STATE_CHANNEL_MANAGER: undefined as Address | undefined,
|
||||
TWOWAY_BRIDGE_L2: undefined as Address | undefined,
|
||||
LOCKBOX_2138: TRUSTLESS.chain2138.LOCKBOX_2138 as Address,
|
||||
},
|
||||
} as const
|
||||
|
||||
export { TRUSTLESS } from './bridge'
|
||||
|
||||
// Chain 138 for wagmi (custom chain)
|
||||
export const chain138 = {
|
||||
id: 138,
|
||||
name: 'Chain 138',
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: ['https://rpc.chain138.example'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: 'Explorer', url: 'https://explorer.chain138.example' },
|
||||
},
|
||||
...chain138Network,
|
||||
} as const
|
||||
|
||||
export const chain2138 = {
|
||||
...chain2138Testnet,
|
||||
} as const
|
||||
|
||||
export const SUPPORTED_CHAINS = {
|
||||
mainnet,
|
||||
chain138,
|
||||
chain2138,
|
||||
} as const
|
||||
|
||||
@@ -26,6 +26,9 @@ const rpcUrl2138 =
|
||||
import.meta.env.VITE_RPC_URL_2138 || 'https://rpc.public-2138.defi-oracle.io'
|
||||
const explorerUrl2138 =
|
||||
import.meta.env.VITE_EXPLORER_URL_2138 || 'https://public-2138.defi-oracle.io'
|
||||
const configuredDefaultFrontendChainId = Number(
|
||||
import.meta.env.VITE_DEFAULT_FRONTEND_CHAIN_ID || 138
|
||||
)
|
||||
|
||||
/** Chain 2138 - Defi Oracle Meta Testnet (optional; enable with VITE_ENABLE_CHAIN2138) */
|
||||
export const chain2138Testnet = defineChain({
|
||||
@@ -186,6 +189,25 @@ export const customChains = chain2138TestnetEnabled
|
||||
? customChainsWith2138
|
||||
: customChainsWithout2138
|
||||
|
||||
/** Source chains the frontend can intentionally target for wallet UX. */
|
||||
export const frontendSourceChains = chain2138TestnetEnabled
|
||||
? [chain138, chain2138Testnet] as const
|
||||
: [chain138] as const
|
||||
|
||||
/** Shared source-chain IDs for connect/switch prompts. */
|
||||
export const frontendSourceChainIds: number[] = frontendSourceChains.map((chain) => chain.id)
|
||||
|
||||
const configuredFrontendChain =
|
||||
chain2138TestnetEnabled && configuredDefaultFrontendChainId === chain2138Testnet.id
|
||||
? chain2138Testnet
|
||||
: chain138
|
||||
|
||||
/** Default chain the wallet UX should prompt for. */
|
||||
export const defaultFrontendChain = configuredFrontendChain
|
||||
export const defaultFrontendChainId = defaultFrontendChain.id
|
||||
export const defaultFrontendChainName = defaultFrontendChain.name
|
||||
export const defaultFrontendExplorerUrl = defaultFrontendChain.blockExplorers.default.url
|
||||
|
||||
/** Standard L2s from wagmi/chains (Ethereum, Base, Arbitrum, Polygon, Optimism) */
|
||||
export const standardChains = [mainnet, base, arbitrum, polygon, optimism] as const
|
||||
|
||||
|
||||
55
frontend-dapp/src/config/trustlessL2.ts
Normal file
55
frontend-dapp/src/config/trustlessL2.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Trustless bridge “L2” side: Chain 138 (mainnet) or Chain 2138 (testnet).
|
||||
* Set VITE_TRUSTLESS_L2_CHAIN_ID=2138 and deploy addresses via VITE_LOCKBOX_2138, etc.
|
||||
* When using 2138, enable VITE_ENABLE_CHAIN2138 so Wagmi lists the chain.
|
||||
*/
|
||||
|
||||
import type { Chain } from 'viem'
|
||||
import { TRUSTLESS } from './bridge'
|
||||
import { chain138, chain2138Testnet, chain2138TestnetEnabled } from './networks'
|
||||
|
||||
const raw = import.meta.env.VITE_TRUSTLESS_L2_CHAIN_ID
|
||||
const parsed = raw === undefined || raw === '' ? 138 : Number(raw)
|
||||
export const TRUSTLESS_L2_CHAIN_ID: 138 | 2138 = parsed === 2138 ? 2138 : 138
|
||||
|
||||
export type TrustlessL2LockboxConfig = {
|
||||
chainId: 138 | 2138
|
||||
viemChain: Chain
|
||||
lockbox: `0x${string}`
|
||||
weth: `0x${string}`
|
||||
cusdt: `0x${string}`
|
||||
cusdc: `0x${string}`
|
||||
}
|
||||
|
||||
/** Resolves L2 chain + lockbox token addresses for TrustlessBridgeForm. */
|
||||
export function getTrustlessL2LockboxConfig(): TrustlessL2LockboxConfig {
|
||||
if (TRUSTLESS_L2_CHAIN_ID === 2138) {
|
||||
if (!chain2138TestnetEnabled) {
|
||||
return {
|
||||
chainId: 138,
|
||||
viemChain: chain138,
|
||||
lockbox: TRUSTLESS.chain138.LOCKBOX_138,
|
||||
weth: TRUSTLESS.chain138.WETH,
|
||||
cusdt: TRUSTLESS.chain138.CUSDT,
|
||||
cusdc: TRUSTLESS.chain138.CUSDC,
|
||||
}
|
||||
}
|
||||
const c = TRUSTLESS.chain2138
|
||||
return {
|
||||
chainId: 2138,
|
||||
viemChain: chain2138Testnet,
|
||||
lockbox: c.LOCKBOX_2138,
|
||||
weth: c.WETH,
|
||||
cusdt: c.CUSDT,
|
||||
cusdc: c.CUSDC,
|
||||
}
|
||||
}
|
||||
return {
|
||||
chainId: 138,
|
||||
viemChain: chain138,
|
||||
lockbox: TRUSTLESS.chain138.LOCKBOX_138,
|
||||
weth: TRUSTLESS.chain138.WETH,
|
||||
cusdt: TRUSTLESS.chain138.CUSDT,
|
||||
cusdc: TRUSTLESS.chain138.CUSDC,
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,17 @@ import { metaMask, walletConnect, coinbaseWallet } from 'wagmi/connectors'
|
||||
import {
|
||||
allNetworks,
|
||||
chainRpcUrls,
|
||||
chain138,
|
||||
chain2138Testnet,
|
||||
chain2138TestnetEnabled,
|
||||
allMainnet,
|
||||
etherlink,
|
||||
bsc,
|
||||
avalanche,
|
||||
cronos,
|
||||
gnosis,
|
||||
} from './networks'
|
||||
|
||||
const projectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID || ''
|
||||
|
||||
const transports = Object.fromEntries(
|
||||
allNetworks.map((chain) => {
|
||||
const rpcUrl = chainRpcUrls[chain.id]
|
||||
return [chain.id, rpcUrl ? http(rpcUrl) : http()]
|
||||
})
|
||||
)
|
||||
|
||||
export const config = createConfig({
|
||||
chains: allNetworks,
|
||||
connectors: [
|
||||
@@ -23,22 +21,5 @@ export const config = createConfig({
|
||||
walletConnect({ projectId }),
|
||||
coinbaseWallet({ appName: 'Bridge DApp' }),
|
||||
],
|
||||
transports: {
|
||||
[chain138.id]: http(chainRpcUrls[chain138.id]),
|
||||
...(chain2138TestnetEnabled
|
||||
? { [chain2138Testnet.id]: http(chainRpcUrls[chain2138Testnet.id]) }
|
||||
: {}),
|
||||
[allMainnet.id]: http(chainRpcUrls[allMainnet.id]),
|
||||
[etherlink.id]: http(chainRpcUrls[etherlink.id]),
|
||||
[bsc.id]: http(chainRpcUrls[bsc.id]),
|
||||
[avalanche.id]: http(chainRpcUrls[avalanche.id]),
|
||||
[cronos.id]: http(chainRpcUrls[cronos.id]),
|
||||
[gnosis.id]: http(chainRpcUrls[gnosis.id]),
|
||||
// Standard chains use default public RPC when no override
|
||||
...Object.fromEntries(
|
||||
allNetworks
|
||||
.filter((c) => !(c.id in chainRpcUrls))
|
||||
.map((c) => [c.id, http()])
|
||||
),
|
||||
},
|
||||
transports: transports as Record<number, ReturnType<typeof http>>,
|
||||
})
|
||||
|
||||
@@ -32,6 +32,7 @@ import RealtimeMonitor from '../components/admin/RealtimeMonitor'
|
||||
import PaymentChannels from '../components/admin/PaymentChannels'
|
||||
import PaymentChannelAdmin from '../components/admin/PaymentChannelAdmin'
|
||||
import StateChannels from '../components/admin/StateChannels'
|
||||
import { chain138 } from '../config/networks'
|
||||
|
||||
type TabType = 'dashboard' | 'mainnet-tether' | 'transaction-mirror' | 'two-way-bridge' | 'channels' | 'state-channels' | 'channel-admin' | 'multisig' | 'queue' | 'impersonation' | 'emergency' | 'audit' | 'gas' | 'batch' | 'templates' | 'retry' | 'services' | 'preview' | 'roles' | 'timelock' | 'wallet' | 'multichain' | 'schedule' | 'balance' | 'owners' | 'backup' | 'priority' | 'hardware' | 'permissions' | 'realtime'
|
||||
|
||||
@@ -40,7 +41,7 @@ export default function AdminPanel() {
|
||||
const chainId = useChainId()
|
||||
const [activeTab, setActiveTab] = useState<TabType>('dashboard')
|
||||
|
||||
const isSupportedChain = chainId === 1 || chainId === 138
|
||||
const isSupportedChain = chainId === 1 || chainId === chain138.id
|
||||
|
||||
const tabs = [
|
||||
{ id: 'dashboard' as TabType, label: 'Dashboard', icon: '📊' },
|
||||
@@ -97,11 +98,11 @@ export default function AdminPanel() {
|
||||
<div className="bg-white/10 backdrop-blur-xl rounded-2xl shadow-2xl border border-white/20 p-8 text-center">
|
||||
<h1 className="text-3xl font-bold text-white mb-4">Admin Panel</h1>
|
||||
<p className="text-white/80 mb-6">
|
||||
Please switch to Ethereum Mainnet (1) or Chain 138 to access admin functions.
|
||||
Please switch to Ethereum Mainnet (1) or {chain138.name} to access admin functions.
|
||||
</p>
|
||||
<div className="inline-block bg-red-500/20 border border-red-500/50 rounded-lg p-4">
|
||||
<p className="text-red-200 text-sm">
|
||||
Current network: Chain ID {chainId}. Use Mainnet (1) or Chain 138.
|
||||
Current network: Chain ID {chainId}. Use Mainnet (1) or {chain138.name}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,15 +7,14 @@ import SwapBridgeSwapQuoteForm from '../components/bridge/SwapBridgeSwapQuoteFor
|
||||
import TrustlessBridgeForm from '../components/bridge/TrustlessBridgeForm';
|
||||
import ChainIcon from '../components/ui/ChainIcon';
|
||||
import { CCIP_DESTINATIONS, CHAIN_SELECTORS } from '../config/bridge';
|
||||
|
||||
const CHAIN_138_ID = 138;
|
||||
import { chain138 } from '../config/networks';
|
||||
|
||||
const THIRDWEB_CLIENT_ID = import.meta.env.VITE_THIRDWEB_CLIENT_ID || '542981292d51ec610388ba8985f027d7';
|
||||
|
||||
export default function BridgePage() {
|
||||
const [activeTab, setActiveTab] = useState<'custom' | 'trustless' | 'evm' | 'xrpl' | 'track'>('custom');
|
||||
const [transferId, setTransferId] = useState<string | undefined>();
|
||||
const [ccipDestinationSelector, setCcipDestinationSelector] = useState(CHAIN_SELECTORS.ETHEREUM_MAINNET);
|
||||
const [ccipDestinationSelector, setCcipDestinationSelector] = useState<string>(CHAIN_SELECTORS.ETHEREUM_MAINNET);
|
||||
|
||||
const handleXRPLBridge = async (data: XRPLBridgeData) => {
|
||||
try {
|
||||
@@ -32,8 +31,8 @@ export default function BridgePage() {
|
||||
const result = await response.json();
|
||||
setTransferId(result.transferId);
|
||||
setActiveTab('track');
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message || 'Bridge initiation failed');
|
||||
} catch (error: unknown) {
|
||||
throw new Error(error instanceof Error ? error.message : 'Bridge initiation failed');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -138,8 +137,8 @@ export default function BridgePage() {
|
||||
<div className="mb-4 flex flex-wrap items-center gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[#A0A0A0] font-medium">From</span>
|
||||
<ChainIcon chainId={CHAIN_138_ID} name="Chain 138" size={28} />
|
||||
<span className="text-white font-medium">Chain 138</span>
|
||||
<ChainIcon chainId={chain138.id} name={chain138.name} size={28} />
|
||||
<span className="text-white font-medium">{chain138.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[#A0A0A0] font-medium">To</span>
|
||||
|
||||
@@ -6,41 +6,26 @@
|
||||
import { createThirdwebClient, defineChain } from 'thirdweb'
|
||||
import { ThirdwebProvider, ConnectButton, useActiveAccount, useWalletBalance } from 'thirdweb/react'
|
||||
import { inAppWallet } from 'thirdweb/wallets'
|
||||
import {
|
||||
defaultFrontendChain,
|
||||
defaultFrontendChainId,
|
||||
defaultFrontendExplorerUrl,
|
||||
chainRpcUrls,
|
||||
} from '../config/networks'
|
||||
|
||||
const clientId = import.meta.env.VITE_THIRDWEB_CLIENT_ID || '542981292d51ec610388ba8985f027d7'
|
||||
const client = createThirdwebClient({ clientId })
|
||||
|
||||
const rpcUrl138 = import.meta.env.VITE_RPC_URL_138 || 'https://rpc-http-pub.d-bis.org'
|
||||
const rpcUrl651940 = import.meta.env.VITE_CHAIN_651940_RPC || import.meta.env.VITE_RPC_URL_651940 || 'https://mainnet-rpc.alltra.global'
|
||||
|
||||
/** Chain 138 — hub (DeFi Oracle Meta Mainnet) */
|
||||
const chain138 = defineChain({
|
||||
id: 138,
|
||||
name: 'DeFi Oracle Meta Mainnet',
|
||||
rpc: rpcUrl138,
|
||||
const defaultThirdwebChain = defineChain({
|
||||
id: defaultFrontendChain.id,
|
||||
name: defaultFrontendChain.name,
|
||||
rpc: chainRpcUrls[defaultFrontendChainId] || defaultFrontendExplorerUrl,
|
||||
nativeCurrency: {
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
name: defaultFrontendChain.nativeCurrency.name,
|
||||
symbol: defaultFrontendChain.nativeCurrency.symbol,
|
||||
decimals: defaultFrontendChain.nativeCurrency.decimals,
|
||||
},
|
||||
})
|
||||
|
||||
/** Chain 651940 — ALL Mainnet (Alltra); Alltra-native services + payments */
|
||||
const chain651940 = defineChain({
|
||||
id: 651940,
|
||||
name: 'ALL Mainnet',
|
||||
rpc: rpcUrl651940,
|
||||
nativeCurrency: {
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
},
|
||||
blockExplorers: [{ name: 'Alltra', url: 'https://alltra.global' }],
|
||||
})
|
||||
|
||||
/** Default chain for this page; can be switched to 651940 for Alltra flows */
|
||||
const defaultChain = chain138
|
||||
|
||||
const wallets = [
|
||||
inAppWallet({
|
||||
auth: {
|
||||
@@ -49,7 +34,7 @@ const wallets = [
|
||||
metadata: {
|
||||
name: 'DBIS Bridge',
|
||||
image: {
|
||||
src: 'https://explorer.d-bis.org/favicon.ico',
|
||||
src: `${defaultFrontendExplorerUrl}/favicon.ico`,
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
@@ -61,7 +46,7 @@ function WalletsDemoContent() {
|
||||
const account = useActiveAccount()
|
||||
const { data: balance, isLoading: balanceLoading } = useWalletBalance({
|
||||
client,
|
||||
chain: defaultChain,
|
||||
chain: defaultThirdwebChain,
|
||||
address: account?.address,
|
||||
})
|
||||
|
||||
@@ -70,14 +55,14 @@ function WalletsDemoContent() {
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-2">Thirdweb Wallets</h1>
|
||||
<p className="text-[#A0A0A0] text-sm">
|
||||
Connect with email, Google, Apple, passkey, or an external wallet (MetaMask, WalletConnect, etc.).
|
||||
Connect with email, Google, Apple, passkey, or an external wallet (MetaMask, WalletConnect, etc.) on {defaultFrontendChain.name}.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-6 p-6 bg-[#252830] rounded-xl border border-white/10">
|
||||
<ConnectButton
|
||||
client={client}
|
||||
chain={defaultChain}
|
||||
chain={defaultThirdwebChain}
|
||||
wallets={wallets}
|
||||
theme="dark"
|
||||
connectButton={{
|
||||
@@ -99,7 +84,7 @@ function WalletsDemoContent() {
|
||||
<p className="text-[#A0A0A0]">Loading balance…</p>
|
||||
) : balance ? (
|
||||
<p className="text-[#A0A0A0]">
|
||||
Balance ({defaultChain.name}):{' '}
|
||||
Balance ({defaultFrontendChain.name}):{' '}
|
||||
<span className="text-white">
|
||||
{balance.displayValue} {balance.symbol}
|
||||
</span>
|
||||
|
||||
@@ -7,6 +7,11 @@ import { getPublicClient } from '@wagmi/core'
|
||||
import { config } from '../config/wagmi'
|
||||
import { mainnet } from 'wagmi/chains'
|
||||
|
||||
type EnsPublicClient = {
|
||||
getEnsName: (args: { address: `0x${string}` }) => Promise<string | null>
|
||||
getEnsAddress: (args: { name: string }) => Promise<string | null>
|
||||
}
|
||||
|
||||
const ENS_CACHE: Record<string, { name: string | null; timestamp: number }> = {}
|
||||
const ADDRESS_CACHE: Record<string, { address: string | null; timestamp: number }> = {}
|
||||
const CACHE_TTL = 3600000 // 1 hour
|
||||
@@ -22,7 +27,10 @@ export async function resolveENS(address: string): Promise<string | null> {
|
||||
|
||||
try {
|
||||
// Only resolve on mainnet
|
||||
const publicClient = getPublicClient(config, { chainId: mainnet.id }) as any
|
||||
const publicClient = getPublicClient(
|
||||
config as never,
|
||||
{ chainId: mainnet.id } as never
|
||||
) as EnsPublicClient | undefined
|
||||
if (!publicClient) {
|
||||
return null
|
||||
}
|
||||
@@ -57,7 +65,10 @@ export async function resolveAddress(name: string): Promise<string | null> {
|
||||
|
||||
try {
|
||||
// Only resolve on mainnet
|
||||
const publicClient = getPublicClient(config, { chainId: mainnet.id }) as any
|
||||
const publicClient = getPublicClient(
|
||||
config as never,
|
||||
{ chainId: mainnet.id } as never
|
||||
) as EnsPublicClient | undefined
|
||||
if (!publicClient) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -22,9 +22,13 @@ export const RATE_LIMITS: Record<string, RateLimitConfig> = {
|
||||
|
||||
export function checkRateLimit(
|
||||
identifier: string,
|
||||
action: string = 'default'
|
||||
actionOrMaxRequests: string | number = 'default',
|
||||
windowMs?: number
|
||||
): { allowed: boolean; remaining?: number; resetAt?: number } {
|
||||
const config = RATE_LIMITS[action] || RATE_LIMITS.default
|
||||
const config =
|
||||
typeof actionOrMaxRequests === 'number'
|
||||
? { maxRequests: actionOrMaxRequests, windowMs: windowMs ?? RATE_LIMITS.default.windowMs }
|
||||
: RATE_LIMITS[actionOrMaxRequests] || RATE_LIMITS.default
|
||||
const allowed = rateLimiter.checkLimit(identifier, config.maxRequests)
|
||||
|
||||
if (!allowed) {
|
||||
|
||||
7
frontend-dapp/src/vite-env.d.ts
vendored
7
frontend-dapp/src/vite-env.d.ts
vendored
@@ -4,6 +4,13 @@ interface ImportMetaEnv {
|
||||
readonly VITE_ENABLE_CHAIN2138?: string
|
||||
readonly VITE_RPC_URL_2138?: string
|
||||
readonly VITE_EXPLORER_URL_2138?: string
|
||||
readonly VITE_DEFAULT_FRONTEND_CHAIN_ID?: string
|
||||
readonly VITE_TRUSTLESS_L2_CHAIN_ID?: string
|
||||
readonly VITE_LOCKBOX_2138?: string
|
||||
readonly VITE_WETH_CHAIN2138?: string
|
||||
readonly VITE_CUSDT_CHAIN2138?: string
|
||||
readonly VITE_CUSDC_CHAIN2138?: string
|
||||
readonly VITE_TRANSACTION_MIRROR_CHAIN2138?: string
|
||||
}
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
"@/*": ["./src/*"],
|
||||
"react": ["./node_modules/@types/react"],
|
||||
"react/jsx-runtime": ["./node_modules/@types/react/jsx-runtime"],
|
||||
"react/jsx-dev-runtime": ["./node_modules/@types/react/jsx-dev-runtime"],
|
||||
"react-dom": ["./node_modules/@types/react-dom"]
|
||||
},
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ CHAIN2138_ETHERSCAN_API_KEY=<explorer-api-key-if-required>
|
||||
# Fee token on 2138 (often native or LINK once deployed)
|
||||
FEE_TOKEN=<native-or-link-address>
|
||||
|
||||
# Vite DApp (smom-dbis-138/frontend-dapp/.env*) — not read by Terraform; copy as needed
|
||||
# VITE_ENABLE_CHAIN2138=true
|
||||
# VITE_TRUSTLESS_L2_CHAIN_ID=2138
|
||||
# VITE_LOCKBOX_2138=<lockbox-after-deploy>
|
||||
# VITE_WETH_CHAIN2138=... VITE_CUSDT_CHAIN2138=... VITE_CUSDC_CHAIN2138=...
|
||||
|
||||
# thirdweb (optional)
|
||||
THIRDWEB_PROJECT_NAME="DBIS ChainID 2138 Testnet"
|
||||
THIRDWEB_CLIENT_ID=<your-thirdweb-client-id>
|
||||
|
||||
Reference in New Issue
Block a user