agentic-team-templates 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# Web3 Frontend Integration
|
|
2
|
+
|
|
3
|
+
Patterns for building secure, user-friendly Web3 frontends using modern tooling.
|
|
4
|
+
|
|
5
|
+
## Technology Stack
|
|
6
|
+
|
|
7
|
+
### Recommended Stack (2025)
|
|
8
|
+
|
|
9
|
+
| Layer | Technology | Purpose |
|
|
10
|
+
|-------|------------|---------|
|
|
11
|
+
| Ethereum Interface | Viem | Low-level, type-safe RPC client |
|
|
12
|
+
| React Hooks | Wagmi | React hooks for wallet/chain interaction |
|
|
13
|
+
| Wallet Connection | ConnectKit/RainbowKit | Pre-built wallet UIs |
|
|
14
|
+
| State Management | React Query (via Wagmi) | Caching and synchronization |
|
|
15
|
+
| Type Generation | wagmi cli | Generate types from ABIs |
|
|
16
|
+
|
|
17
|
+
### Project Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install wagmi viem @tanstack/react-query
|
|
21
|
+
npm install connectkit # or rainbowkit
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
### Wagmi Config
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// lib/wagmi.ts
|
|
30
|
+
import { createConfig, http } from 'wagmi';
|
|
31
|
+
import { mainnet, sepolia, arbitrum } from 'wagmi/chains';
|
|
32
|
+
import { coinbaseWallet, injected, walletConnect } from 'wagmi/connectors';
|
|
33
|
+
|
|
34
|
+
export const config = createConfig({
|
|
35
|
+
chains: [mainnet, arbitrum, sepolia],
|
|
36
|
+
connectors: [
|
|
37
|
+
injected(),
|
|
38
|
+
coinbaseWallet({ appName: 'My App' }),
|
|
39
|
+
walletConnect({ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID! }),
|
|
40
|
+
],
|
|
41
|
+
transports: {
|
|
42
|
+
[mainnet.id]: http(process.env.NEXT_PUBLIC_MAINNET_RPC),
|
|
43
|
+
[arbitrum.id]: http(process.env.NEXT_PUBLIC_ARBITRUM_RPC),
|
|
44
|
+
[sepolia.id]: http(process.env.NEXT_PUBLIC_SEPOLIA_RPC),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Provider Setup
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// app/providers.tsx
|
|
53
|
+
'use client';
|
|
54
|
+
|
|
55
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
56
|
+
import { WagmiProvider } from 'wagmi';
|
|
57
|
+
import { ConnectKitProvider } from 'connectkit';
|
|
58
|
+
import { config } from '@/lib/wagmi';
|
|
59
|
+
|
|
60
|
+
const queryClient = new QueryClient();
|
|
61
|
+
|
|
62
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
63
|
+
return (
|
|
64
|
+
<WagmiProvider config={config}>
|
|
65
|
+
<QueryClientProvider client={queryClient}>
|
|
66
|
+
<ConnectKitProvider>
|
|
67
|
+
{children}
|
|
68
|
+
</ConnectKitProvider>
|
|
69
|
+
</QueryClientProvider>
|
|
70
|
+
</WagmiProvider>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Wallet Connection
|
|
76
|
+
|
|
77
|
+
### Basic Connection
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// components/ConnectButton.tsx
|
|
81
|
+
import { useAccount, useConnect, useDisconnect } from 'wagmi';
|
|
82
|
+
|
|
83
|
+
export function ConnectButton() {
|
|
84
|
+
const { address, isConnected } = useAccount();
|
|
85
|
+
const { connect, connectors, isPending } = useConnect();
|
|
86
|
+
const { disconnect } = useDisconnect();
|
|
87
|
+
|
|
88
|
+
if (isConnected) {
|
|
89
|
+
return (
|
|
90
|
+
<div>
|
|
91
|
+
<span>{address?.slice(0, 6)}...{address?.slice(-4)}</span>
|
|
92
|
+
<button onClick={() => disconnect()}>Disconnect</button>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div>
|
|
99
|
+
{connectors.map((connector) => (
|
|
100
|
+
<button
|
|
101
|
+
key={connector.id}
|
|
102
|
+
onClick={() => connect({ connector })}
|
|
103
|
+
disabled={isPending}
|
|
104
|
+
>
|
|
105
|
+
{connector.name}
|
|
106
|
+
</button>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Using ConnectKit (Recommended)
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { ConnectKitButton } from 'connectkit';
|
|
117
|
+
|
|
118
|
+
export function Header() {
|
|
119
|
+
return (
|
|
120
|
+
<header>
|
|
121
|
+
<ConnectKitButton />
|
|
122
|
+
</header>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Contract Interactions
|
|
128
|
+
|
|
129
|
+
### Type-Safe Contract Hooks
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// hooks/useVault.ts
|
|
133
|
+
import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
|
|
134
|
+
import { parseEther, formatEther } from 'viem';
|
|
135
|
+
import { vaultAbi } from '@/contracts/abi';
|
|
136
|
+
|
|
137
|
+
const VAULT_ADDRESS = '0x...' as const;
|
|
138
|
+
|
|
139
|
+
export function useVaultBalance(address: `0x${string}` | undefined) {
|
|
140
|
+
return useReadContract({
|
|
141
|
+
address: VAULT_ADDRESS,
|
|
142
|
+
abi: vaultAbi,
|
|
143
|
+
functionName: 'balanceOf',
|
|
144
|
+
args: address ? [address] : undefined,
|
|
145
|
+
query: {
|
|
146
|
+
enabled: !!address,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function useVaultDeposit() {
|
|
152
|
+
const { data: hash, writeContract, isPending, error } = useWriteContract();
|
|
153
|
+
|
|
154
|
+
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
|
|
155
|
+
hash,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const deposit = async (amount: string) => {
|
|
159
|
+
writeContract({
|
|
160
|
+
address: VAULT_ADDRESS,
|
|
161
|
+
abi: vaultAbi,
|
|
162
|
+
functionName: 'deposit',
|
|
163
|
+
args: [parseEther(amount)],
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
deposit,
|
|
169
|
+
hash,
|
|
170
|
+
isPending,
|
|
171
|
+
isConfirming,
|
|
172
|
+
isSuccess,
|
|
173
|
+
error,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Using the Hook
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// components/DepositForm.tsx
|
|
182
|
+
import { useState } from 'react';
|
|
183
|
+
import { useAccount } from 'wagmi';
|
|
184
|
+
import { useVaultDeposit, useVaultBalance } from '@/hooks/useVault';
|
|
185
|
+
|
|
186
|
+
export function DepositForm() {
|
|
187
|
+
const [amount, setAmount] = useState('');
|
|
188
|
+
const { address } = useAccount();
|
|
189
|
+
const { data: balance } = useVaultBalance(address);
|
|
190
|
+
const { deposit, isPending, isConfirming, isSuccess, error } = useVaultDeposit();
|
|
191
|
+
|
|
192
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
deposit(amount);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<form onSubmit={handleSubmit}>
|
|
199
|
+
<input
|
|
200
|
+
type="text"
|
|
201
|
+
value={amount}
|
|
202
|
+
onChange={(e) => setAmount(e.target.value)}
|
|
203
|
+
placeholder="Amount in ETH"
|
|
204
|
+
disabled={isPending || isConfirming}
|
|
205
|
+
/>
|
|
206
|
+
|
|
207
|
+
<button type="submit" disabled={isPending || isConfirming}>
|
|
208
|
+
{isPending ? 'Confirm in wallet...' : isConfirming ? 'Confirming...' : 'Deposit'}
|
|
209
|
+
</button>
|
|
210
|
+
|
|
211
|
+
{isSuccess && <p>Deposit successful!</p>}
|
|
212
|
+
{error && <p>Error: {error.message}</p>}
|
|
213
|
+
</form>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Transaction Handling
|
|
219
|
+
|
|
220
|
+
### Transaction States
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
type TransactionState =
|
|
224
|
+
| 'idle' // No transaction
|
|
225
|
+
| 'pending' // Waiting for wallet signature
|
|
226
|
+
| 'submitted' // Signed, waiting for confirmation
|
|
227
|
+
| 'confirming' // In mempool, waiting for block
|
|
228
|
+
| 'confirmed' // Included in block
|
|
229
|
+
| 'failed'; // Reverted or rejected
|
|
230
|
+
|
|
231
|
+
// Visual feedback for each state
|
|
232
|
+
const transactionMessages: Record<TransactionState, string> = {
|
|
233
|
+
idle: '',
|
|
234
|
+
pending: 'Please confirm in your wallet...',
|
|
235
|
+
submitted: 'Transaction submitted, waiting for confirmation...',
|
|
236
|
+
confirming: 'Transaction is being confirmed...',
|
|
237
|
+
confirmed: 'Transaction confirmed!',
|
|
238
|
+
failed: 'Transaction failed',
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Transaction Tracking Component
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// components/TransactionStatus.tsx
|
|
246
|
+
import { useWaitForTransactionReceipt } from 'wagmi';
|
|
247
|
+
|
|
248
|
+
interface TransactionStatusProps {
|
|
249
|
+
hash: `0x${string}` | undefined;
|
|
250
|
+
onSuccess?: () => void;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function TransactionStatus({ hash, onSuccess }: TransactionStatusProps) {
|
|
254
|
+
const { isLoading, isSuccess, isError, error } = useWaitForTransactionReceipt({
|
|
255
|
+
hash,
|
|
256
|
+
onSuccess,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (!hash) return null;
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div className="transaction-status">
|
|
263
|
+
{isLoading && (
|
|
264
|
+
<div className="flex items-center gap-2">
|
|
265
|
+
<Spinner />
|
|
266
|
+
<span>Confirming transaction...</span>
|
|
267
|
+
<a href={`https://etherscan.io/tx/${hash}`} target="_blank">
|
|
268
|
+
View on Etherscan
|
|
269
|
+
</a>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{isSuccess && (
|
|
274
|
+
<div className="text-green-600">
|
|
275
|
+
Transaction confirmed!
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
|
|
279
|
+
{isError && (
|
|
280
|
+
<div className="text-red-600">
|
|
281
|
+
Transaction failed: {error?.message}
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Error Handling
|
|
290
|
+
|
|
291
|
+
### Contract Error Parsing
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// utils/errors.ts
|
|
295
|
+
import { BaseError, ContractFunctionRevertedError } from 'viem';
|
|
296
|
+
|
|
297
|
+
export function parseContractError(error: unknown): string {
|
|
298
|
+
if (error instanceof BaseError) {
|
|
299
|
+
// Check for contract revert
|
|
300
|
+
const revertError = error.walk(
|
|
301
|
+
(err) => err instanceof ContractFunctionRevertedError
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
305
|
+
const errorName = revertError.data?.errorName ?? 'Unknown error';
|
|
306
|
+
|
|
307
|
+
// Map custom errors to user-friendly messages
|
|
308
|
+
const errorMessages: Record<string, string> = {
|
|
309
|
+
InsufficientBalance: 'You don\'t have enough balance',
|
|
310
|
+
Unauthorized: 'You\'re not authorized to perform this action',
|
|
311
|
+
SlippageExceeded: 'Price moved too much, try again',
|
|
312
|
+
Expired: 'Transaction expired, please try again',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return errorMessages[errorName] ?? errorName;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check for user rejection
|
|
319
|
+
if (error.message.includes('User rejected')) {
|
|
320
|
+
return 'Transaction cancelled';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return 'An unexpected error occurred';
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Error Boundary
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// components/Web3ErrorBoundary.tsx
|
|
332
|
+
import { Component, ReactNode } from 'react';
|
|
333
|
+
|
|
334
|
+
interface Props {
|
|
335
|
+
children: ReactNode;
|
|
336
|
+
fallback?: ReactNode;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface State {
|
|
340
|
+
hasError: boolean;
|
|
341
|
+
error?: Error;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export class Web3ErrorBoundary extends Component<Props, State> {
|
|
345
|
+
state: State = { hasError: false };
|
|
346
|
+
|
|
347
|
+
static getDerivedStateFromError(error: Error): State {
|
|
348
|
+
return { hasError: true, error };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
render() {
|
|
352
|
+
if (this.state.hasError) {
|
|
353
|
+
return this.props.fallback ?? (
|
|
354
|
+
<div className="error-container">
|
|
355
|
+
<h2>Something went wrong</h2>
|
|
356
|
+
<p>{this.state.error?.message}</p>
|
|
357
|
+
<button onClick={() => this.setState({ hasError: false })}>
|
|
358
|
+
Try again
|
|
359
|
+
</button>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return this.props.children;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Network Handling
|
|
370
|
+
|
|
371
|
+
### Chain Switching
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { useSwitchChain, useChainId } from 'wagmi';
|
|
375
|
+
import { mainnet, arbitrum } from 'wagmi/chains';
|
|
376
|
+
|
|
377
|
+
export function NetworkSwitcher() {
|
|
378
|
+
const chainId = useChainId();
|
|
379
|
+
const { switchChain, isPending } = useSwitchChain();
|
|
380
|
+
|
|
381
|
+
const supportedChains = [mainnet, arbitrum];
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<select
|
|
385
|
+
value={chainId}
|
|
386
|
+
onChange={(e) => switchChain({ chainId: Number(e.target.value) })}
|
|
387
|
+
disabled={isPending}
|
|
388
|
+
>
|
|
389
|
+
{supportedChains.map((chain) => (
|
|
390
|
+
<option key={chain.id} value={chain.id}>
|
|
391
|
+
{chain.name}
|
|
392
|
+
</option>
|
|
393
|
+
))}
|
|
394
|
+
</select>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Wrong Network Warning
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { useChainId, useAccount } from 'wagmi';
|
|
403
|
+
|
|
404
|
+
const SUPPORTED_CHAIN_IDS = [1, 42161]; // Mainnet, Arbitrum
|
|
405
|
+
|
|
406
|
+
export function NetworkGuard({ children }: { children: React.ReactNode }) {
|
|
407
|
+
const chainId = useChainId();
|
|
408
|
+
const { isConnected } = useAccount();
|
|
409
|
+
|
|
410
|
+
if (isConnected && !SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
|
411
|
+
return (
|
|
412
|
+
<div className="warning">
|
|
413
|
+
<p>Please switch to a supported network</p>
|
|
414
|
+
<NetworkSwitcher />
|
|
415
|
+
</div>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return <>{children}</>;
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Security Best Practices
|
|
424
|
+
|
|
425
|
+
### 1. Validate Before Signing
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Always show transaction details before signing
|
|
429
|
+
function ConfirmTransaction({
|
|
430
|
+
action,
|
|
431
|
+
amount,
|
|
432
|
+
recipient,
|
|
433
|
+
onConfirm,
|
|
434
|
+
}: ConfirmTransactionProps) {
|
|
435
|
+
return (
|
|
436
|
+
<div className="confirmation-modal">
|
|
437
|
+
<h3>Confirm Transaction</h3>
|
|
438
|
+
<dl>
|
|
439
|
+
<dt>Action</dt>
|
|
440
|
+
<dd>{action}</dd>
|
|
441
|
+
<dt>Amount</dt>
|
|
442
|
+
<dd>{amount} ETH</dd>
|
|
443
|
+
<dt>Recipient</dt>
|
|
444
|
+
<dd>{recipient}</dd>
|
|
445
|
+
</dl>
|
|
446
|
+
<button onClick={onConfirm}>Confirm</button>
|
|
447
|
+
<button onClick={onCancel}>Cancel</button>
|
|
448
|
+
</div>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### 2. Simulate Before Sending
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { useSimulateContract } from 'wagmi';
|
|
457
|
+
|
|
458
|
+
function useDepositWithSimulation(amount: bigint) {
|
|
459
|
+
// Simulate first
|
|
460
|
+
const { data: simulation, error: simulationError } = useSimulateContract({
|
|
461
|
+
address: VAULT_ADDRESS,
|
|
462
|
+
abi: vaultAbi,
|
|
463
|
+
functionName: 'deposit',
|
|
464
|
+
args: [amount],
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const { writeContract } = useWriteContract();
|
|
468
|
+
|
|
469
|
+
const deposit = () => {
|
|
470
|
+
if (simulationError) {
|
|
471
|
+
throw new Error(`Transaction would fail: ${simulationError.message}`);
|
|
472
|
+
}
|
|
473
|
+
writeContract(simulation!.request);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
return { deposit, simulationError };
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 3. Never Trust URL Parameters for Addresses
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
// Bad: Using address from URL
|
|
484
|
+
const address = searchParams.get('recipient');
|
|
485
|
+
writeContract({ args: [address] }); // Phishing risk!
|
|
486
|
+
|
|
487
|
+
// Good: Always use known addresses or user confirmation
|
|
488
|
+
const KNOWN_CONTRACTS = {
|
|
489
|
+
vault: '0x...',
|
|
490
|
+
staking: '0x...',
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Or require explicit user confirmation
|
|
494
|
+
const [confirmed, setConfirmed] = useState(false);
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 4. Rate Limit Sensitive Operations
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
import { useCallback, useRef } from 'react';
|
|
501
|
+
|
|
502
|
+
function useRateLimitedAction(cooldownMs: number = 5000) {
|
|
503
|
+
const lastAction = useRef<number>(0);
|
|
504
|
+
|
|
505
|
+
const canExecute = useCallback(() => {
|
|
506
|
+
const now = Date.now();
|
|
507
|
+
if (now - lastAction.current < cooldownMs) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
lastAction.current = now;
|
|
511
|
+
return true;
|
|
512
|
+
}, [cooldownMs]);
|
|
513
|
+
|
|
514
|
+
return canExecute;
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Performance Tips
|
|
519
|
+
|
|
520
|
+
1. **Use query keys properly** - Wagmi handles caching, don't duplicate
|
|
521
|
+
2. **Batch reads** - Use `useReadContracts` for multiple reads
|
|
522
|
+
3. **Avoid unnecessary re-renders** - Memoize callbacks and values
|
|
523
|
+
4. **Lazy load Web3** - Don't block initial page load
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
// Batch multiple contract reads
|
|
527
|
+
import { useReadContracts } from 'wagmi';
|
|
528
|
+
|
|
529
|
+
function useVaultStats() {
|
|
530
|
+
return useReadContracts({
|
|
531
|
+
contracts: [
|
|
532
|
+
{ address: VAULT, abi, functionName: 'totalAssets' },
|
|
533
|
+
{ address: VAULT, abi, functionName: 'totalSupply' },
|
|
534
|
+
{ address: VAULT, abi, functionName: 'asset' },
|
|
535
|
+
],
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
```
|