Skip to content

Commit 0f51b7d

Browse files
akushniruknksazonov
authored andcommitted
final-p2p-transfer
1 parent f3939f6 commit 0f51b7d

File tree

7 files changed

+517
-38
lines changed

7 files changed

+517
-38
lines changed

.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Nitrolite WebSocket URL for real-time communication
2+
# Replace with your actual Nitrolite WebSocket endpoint
3+
VITE_NITROLITE_WS_URL=wss://your-nitrolite-websocket-url
4+
5+
# Example for development:
6+
# VITE_NITROLITE_WS_URL=wss://dev-nitrolite.example.com/ws
7+
8+
# Example for production:
9+
# VITE_NITROLITE_WS_URL=wss://nitrolite.example.com/ws

docs/final-p2p-transfer.md

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
# Final Chapter: Peer-to-Peer Transfers
2+
3+
## Goal
4+
5+
In this final chapter, we'll add peer-to-peer transfer functionality to our Web3 application. Users will be able to send USDC support using session keys, creating a seamless Web2-like experience for Web3 transactions with just one button click.
6+
7+
## Why This Matters
8+
9+
This demonstrates the full power of session keys:
10+
11+
- **Instant Transfers**: No wallet popups after initial authentication
12+
- **Real-time Updates**: Balance updates immediately after transfers
13+
- **User-Friendly UX**: Simple forms instead of complex transaction flows
14+
15+
## Interaction Flow
16+
17+
```mermaid
18+
sequenceDiagram
19+
participant User as "User"
20+
participant App as "Our dApp"
21+
participant WSS as "WebSocketService"
22+
participant ClearNode as "ClearNode"
23+
24+
User->>App: 1. Clicks support button
25+
App->>App: 2. Uses predefined recipient and amount
26+
App->>WSS: 3. Sends signed transfer request
27+
WSS->>ClearNode: 4. Forwards transfer request
28+
ClearNode->>ClearNode: 5. Processes transfer & updates balances
29+
ClearNode-->>WSS: 6. Confirms transfer completion
30+
WSS-->>App: 7. Receives transfer confirmation
31+
App->>App: 8. Updates UI with success message
32+
ClearNode-->>WSS: 9. Sends balance update to sender
33+
WSS-->>App: 10. Real-time balance update
34+
App->>App: 11. Updates balance display
35+
```
36+
37+
## Implementation Steps
38+
39+
### 1. Import User Data
40+
41+
We'll use a predefined support address from our users data. The users are already defined in `src/data/users.ts`.
42+
43+
### 2. Create useTransfer Hook
44+
45+
Create `src/hooks/useTransfer.ts` for clean transfer logic separation:
46+
47+
```tsx
48+
// FINAL: Custom hook for handling transfers
49+
import { useCallback } from 'preact/hooks';
50+
import { createTransferMessage, createECDSAMessageSigner } from '@erc7824/nitrolite';
51+
import type { Address } from 'viem';
52+
import { webSocketService } from '../lib/websocket';
53+
import type { SessionKey } from '../lib/utils';
54+
55+
export interface TransferResult {
56+
success: boolean;
57+
error?: string;
58+
}
59+
60+
export const useTransfer = (sessionKey: SessionKey | null, isAuthenticated: boolean) => {
61+
const handleTransfer = useCallback(
62+
async (recipient: Address, amount: string, asset: string = 'usdc'): Promise<TransferResult> => {
63+
if (!isAuthenticated || !sessionKey) {
64+
return { success: false, error: 'Please authenticate first' };
65+
}
66+
67+
try {
68+
const sessionSigner = createECDSAMessageSigner(sessionKey.privateKey);
69+
70+
const transferPayload = await createTransferMessage(sessionSigner, {
71+
destination: recipient,
72+
allocations: [
73+
{
74+
asset: asset.toLowerCase(),
75+
amount: amount,
76+
}
77+
],
78+
});
79+
80+
console.log('Sending transfer request...');
81+
webSocketService.send(transferPayload);
82+
83+
return { success: true };
84+
} catch (error) {
85+
console.error('Failed to create transfer:', error);
86+
const errorMsg = error instanceof Error ? error.message : 'Failed to create transfer';
87+
return { success: false, error: errorMsg };
88+
}
89+
},
90+
[sessionKey, isAuthenticated]
91+
);
92+
93+
return { handleTransfer };
94+
};
95+
```
96+
97+
### 3. Update App.tsx - Add Transfer Imports
98+
99+
Add these imports to your existing App.tsx:
100+
101+
```tsx
102+
// FINAL: Add transfer response import to existing imports
103+
import {
104+
// ... existing imports ...
105+
type TransferResponse,
106+
} from '@erc7824/nitrolite';
107+
108+
// FINAL: Import useTransfer hook
109+
import { useTransfer } from './hooks/useTransfer';
110+
// FINAL: Import users for support address
111+
import { users } from './data/users';
112+
```
113+
114+
### 4. Update App.tsx - Add Transfer State and Hook
115+
116+
Add transfer state variables and the useTransfer hook to your App component:
117+
118+
```tsx
119+
// FINAL: Add transfer state after existing state
120+
const [isTransferring, setIsTransferring] = useState(false);
121+
const [transferStatus, setTransferStatus] = useState<string | null>(null);
122+
123+
// FINAL: Use transfer hook
124+
const { handleTransfer: transferFn } = useTransfer(sessionKey, isAuthenticated);
125+
```
126+
127+
### 5. Update App.tsx - Add Support Function
128+
129+
Add this support function before your useEffect hooks:
130+
131+
```tsx
132+
// FINAL: Handle support function for PostList
133+
const handleSupport = async (recipient: string, amount: string) => {
134+
setIsTransferring(true);
135+
setTransferStatus('Sending support...');
136+
137+
const result = await transferFn(recipient as Address, amount);
138+
139+
if (result.success) {
140+
setTransferStatus('Support sent!');
141+
} else {
142+
setIsTransferring(false);
143+
setTransferStatus(null);
144+
if (result.error) {
145+
alert(result.error);
146+
}
147+
}
148+
};
149+
```
150+
151+
### 6. Update App.tsx - Handle Transfer Responses
152+
153+
Add transfer response handling to your existing message handler useEffect:
154+
155+
```tsx
156+
// FINAL: Add this to your existing handleMessage function, after balance handling
157+
if (response.method === RPCMethod.Transfer) {
158+
const transferResponse = response as TransferResponse;
159+
console.log('Transfer completed:', transferResponse.params);
160+
161+
setIsTransferring(false);
162+
setTransferStatus(null);
163+
164+
alert(`Transfer completed successfully!`);
165+
}
166+
167+
// FINAL: Update error handling to include transfers
168+
if (response.method === RPCMethod.Error) {
169+
console.error('RPC Error:', response.params);
170+
171+
if (isTransferring) {
172+
setIsTransferring(false);
173+
setTransferStatus(null);
174+
alert(`Transfer failed: ${response.params.error}`);
175+
} else {
176+
// Other errors (like auth failures)
177+
removeJWT();
178+
removeSessionKey();
179+
alert(`Error: ${response.params.error}`);
180+
setIsAuthAttempted(false);
181+
}
182+
}
183+
```
184+
185+
### 7. Update PostList Component
186+
187+
Update `src/components/PostList/PostList.tsx` to handle transfers:
188+
189+
```tsx
190+
// Add users import and transfer props
191+
import { users } from '../../data/users';
192+
193+
interface PostListProps {
194+
posts: Post[];
195+
isWalletConnected: boolean;
196+
isAuthenticated: boolean;
197+
onTransfer?: (recipient: string, amount: string) => Promise<void>;
198+
isTransferring?: boolean;
199+
}
200+
201+
export function PostList({ posts, isWalletConnected, isAuthenticated, onTransfer, isTransferring }: PostListProps) {
202+
const handleTip = async (post: Post) => {
203+
if (!onTransfer) {
204+
console.log('Transfer function not available');
205+
return;
206+
}
207+
208+
// Find the author's wallet address from users data
209+
const author = users.find(user => user.id === post.authorId);
210+
if (!author) {
211+
console.error('Author wallet address not found');
212+
return;
213+
}
214+
215+
console.log(`Supporting ${post.authorName} with 0.01 USDC`);
216+
await onTransfer(author.walletAddress, '0.01');
217+
};
218+
219+
// Update the support button to show transfer status
220+
<button
221+
className={styles.supportButton}
222+
disabled={!isWalletConnected || !isAuthenticated || isTransferring}
223+
onClick={(e) => {
224+
e.preventDefault();
225+
handleTip(post);
226+
}}
227+
>
228+
{!isWalletConnected
229+
? 'Connect Wallet'
230+
: !isAuthenticated
231+
? 'Authenticating...'
232+
: isTransferring
233+
? 'Supporting...'
234+
: 'Support 0.01 USDC'}
235+
</button>
236+
}
237+
```
238+
239+
### 8. Update App.tsx - Pass Transfer Props to PostList
240+
241+
Update your PostList usage to pass the transfer function:
242+
243+
```tsx
244+
<main className="main-content">
245+
{/* FINAL: Status message for transfers */}
246+
{transferStatus && (
247+
<div className="transfer-status">
248+
{transferStatus}
249+
</div>
250+
)}
251+
252+
<PostList
253+
posts={posts}
254+
isWalletConnected={!!account}
255+
isAuthenticated={isAuthenticated}
256+
onTransfer={handleSupport}
257+
isTransferring={isTransferring}
258+
/>
259+
</main>
260+
```
261+
262+
### 9. Add Basic Transfer Status CSS
263+
264+
Add this to your `src/index.css`:
265+
266+
```css
267+
/* FINAL: Transfer status styles */
268+
.transfer-status {
269+
text-align: center;
270+
padding: 1rem;
271+
margin: 1rem auto;
272+
max-width: 400px;
273+
background-color: var(--accent);
274+
color: white;
275+
border-radius: 6px;
276+
font-weight: 500;
277+
}
278+
```
279+
280+
## Expected Outcome
281+
282+
Your completed application now provides:
283+
284+
### What Users Experience
285+
286+
1. **One-Click Support**: Support buttons on each post to send 0.01 USDC to authors
287+
2. **Popup-Free Transfers**: Send USDC without wallet confirmations
288+
3. **Real-time Updates**: Balance updates immediately after successful transfers
289+
4. **Clear Feedback**: Button states show "Supporting..." during transfers
290+
5. **Dynamic Recipients**: Each post's author receives the support automatically
291+
292+
### Technical Achievement
293+
294+
- **Session Key Transfers**: Cryptographically signed transfers without user popups
295+
- **Real-time State**: WebSocket updates keep UI synchronized with blockchain
296+
- **Professional UX**: Loading states, validation, and error handling
297+
- **Type Safety**: Full TypeScript integration with the SDK
298+
299+
## What You've Learned
300+
301+
You've built a complete Web3 application demonstrating:
302+
303+
- **Session Authentication**: Temporary keys for seamless user experience
304+
- **Peer-to-Peer Transfers**: Direct asset movement between participants
305+
- **Real-time Updates**: Live blockchain state synchronization
306+
- **Professional UX**: Modern interface patterns for Web3 applications
307+
308+
This represents the future of Web3 user experience - where blockchain complexity is abstracted away, leaving users with familiar, intuitive interfaces.
309+
310+
## Key Benefits
311+
312+
This simplified approach offers several advantages for workshop participants:
313+
314+
- **Easy to understand**: One button, clear purpose
315+
- **No form validation**: Removes complexity around input handling
316+
- **Predefined values**: No need to think about amounts or addresses
317+
- **Focus on core concept**: Emphasizes session key functionality over UI details

0 commit comments

Comments
 (0)