spaps-mcp 0.1.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/CHANGELOG.md +7 -0
- package/README.md +76 -0
- package/dist/chunk-GPBTYWDD.js +496 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +9 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +26 -0
- package/dist/resources/SPAPS_SURFACE_CONTRACT.md +125 -0
- package/dist/resources/glossary.md +36 -0
- package/dist/resources/llms.txt +15 -0
- package/dist/wizard/step-01-setup.md +149 -0
- package/dist/wizard/step-02-environment.md +291 -0
- package/dist/wizard/step-03-sdk-init.md +351 -0
- package/dist/wizard/step-04-email-auth.md +311 -0
- package/dist/wizard/step-05-wallet-auth.md +368 -0
- package/dist/wizard/step-06-magic-link.md +560 -0
- package/dist/wizard/step-07-payments.md +529 -0
- package/dist/wizard/step-08-whitelist.md +338 -0
- package/dist/wizard/step-09-admin.md +579 -0
- package/dist/wizard/step-10-errors.md +525 -0
- package/dist/wizard/step-11-ui-polish.md +640 -0
- package/dist/wizard/step-12-testing.md +588 -0
- package/dist/wizard/wizard.lock +67 -0
- package/package.json +66 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# SPAPS Integration Step 5/12: Wallet Authentication
|
|
2
|
+
|
|
3
|
+
## Prerequisites Check
|
|
4
|
+
|
|
5
|
+
Before starting, confirm you have completed:
|
|
6
|
+
- ✅ Step 1-4: Project setup through email authentication
|
|
7
|
+
- ✅ TokenManager working with email auth
|
|
8
|
+
- ✅ Error handling implemented
|
|
9
|
+
|
|
10
|
+
## Required TodoWrite List
|
|
11
|
+
|
|
12
|
+
Create a TodoWrite with EXACTLY these items:
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
TodoWrite({
|
|
16
|
+
todos: [
|
|
17
|
+
{ content: "Add wallet connection UI with shadcn/ui", status: "pending", activeForm: "Adding wallet connection UI" },
|
|
18
|
+
{ content: "Implement authenticateWallet method (NOT signInWithWallet)", status: "pending", activeForm: "Implementing authenticateWallet" },
|
|
19
|
+
{ content: "Create complete signature callback function", status: "pending", activeForm: "Creating signature callback" },
|
|
20
|
+
{ content: "Use WalletUtils.detectChainType for chain detection", status: "pending", activeForm: "Using WalletUtils for chain detection" },
|
|
21
|
+
{ content: "Test with address 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb8", status: "pending", activeForm: "Testing wallet authentication" },
|
|
22
|
+
{ content: "Store tokens and setup auto-refresh", status: "pending", activeForm: "Setting up token storage" },
|
|
23
|
+
{ content: "Handle wallet-specific errors", status: "pending", activeForm: "Handling wallet errors" },
|
|
24
|
+
{ content: "Request step 6 of SPAPS integration wizard", status: "pending", activeForm: "Requesting next step" }
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Critical Method Name Warning
|
|
30
|
+
|
|
31
|
+
⚠️ **EXTREMELY IMPORTANT - READ THIS CAREFULLY** ⚠️
|
|
32
|
+
|
|
33
|
+
The correct method is `authenticateWallet` NOT `signInWithWallet`:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// ✅ CORRECT - This is the ONLY method that exists
|
|
37
|
+
const auth = await sdk.auth.authenticateWallet(
|
|
38
|
+
walletAddress,
|
|
39
|
+
signatureCallback,
|
|
40
|
+
chainType
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// ❌ WRONG - These methods DO NOT EXIST
|
|
44
|
+
sdk.auth.signInWithWallet(...) // NO! This doesn't exist!
|
|
45
|
+
sdk.auth.walletLogin(...) // NO! This doesn't exist!
|
|
46
|
+
sdk.auth.walletSignIn(...) // NO! This doesn't exist!
|
|
47
|
+
sdk.auth.connectWallet(...) // NO! This doesn't exist!
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Implementation Guide
|
|
51
|
+
|
|
52
|
+
### 1. Import Requirements
|
|
53
|
+
```typescript
|
|
54
|
+
import { SweetPotatoSDK, TokenManager, WalletUtils, SweetPotatoAPIError } from 'spaps-sdk'
|
|
55
|
+
import { sdk } from '@/lib/spaps'
|
|
56
|
+
import { ethers } from 'ethers' // For Ethereum wallet interaction
|
|
57
|
+
|
|
58
|
+
// UI Components
|
|
59
|
+
import { Button } from "@/components/ui/button"
|
|
60
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
61
|
+
import { toast } from "sonner"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Wallet Authentication Implementation
|
|
65
|
+
|
|
66
|
+
The `authenticateWallet` method has this EXACT signature:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
authenticateWallet(
|
|
70
|
+
walletAddress: string,
|
|
71
|
+
signatureFunction: (message: string) => Promise<string> | string,
|
|
72
|
+
chainType?: 'ethereum' | 'solana' | 'bitcoin' | 'base',
|
|
73
|
+
username?: string
|
|
74
|
+
): Promise<AuthResponse>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Bitcoin scope note:** the server verifies BIP-137 signed messages
|
|
78
|
+
(Bitcoin Core / Electrum / Trezor style) for mainnet P2PKH, P2WPKH (`bc1q`),
|
|
79
|
+
and P2SH-P2WPKH addresses. Taproot (`bc1p`) addresses require BIP-322 and are
|
|
80
|
+
rejected with a clear error — do not offer Taproot wallets for sign-in.
|
|
81
|
+
Testnet addresses are rejected.
|
|
82
|
+
|
|
83
|
+
### 3. Complete Implementation Example
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
'use client'
|
|
87
|
+
|
|
88
|
+
import { useState } from 'react'
|
|
89
|
+
import { Button } from "@/components/ui/button"
|
|
90
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
91
|
+
import { toast } from "sonner"
|
|
92
|
+
import { sdk } from '@/lib/spaps'
|
|
93
|
+
import { SweetPotatoAPIError, TokenManager, WalletUtils } from 'spaps-sdk'
|
|
94
|
+
import { ethers } from 'ethers'
|
|
95
|
+
|
|
96
|
+
export function WalletAuthForm() {
|
|
97
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
98
|
+
const [walletAddress, setWalletAddress] = useState('')
|
|
99
|
+
|
|
100
|
+
const handleWalletConnect = async () => {
|
|
101
|
+
setIsLoading(true)
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Step 1: Check for MetaMask
|
|
105
|
+
if (!window.ethereum) {
|
|
106
|
+
throw new Error('Please install MetaMask to use wallet authentication')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Step 2: Request account access
|
|
110
|
+
const accounts = await window.ethereum.request({
|
|
111
|
+
method: 'eth_requestAccounts'
|
|
112
|
+
}) as string[]
|
|
113
|
+
|
|
114
|
+
if (!accounts || accounts.length === 0) {
|
|
115
|
+
throw new Error('No accounts found in wallet')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const address = accounts[0]
|
|
119
|
+
setWalletAddress(address)
|
|
120
|
+
|
|
121
|
+
// Step 3: Detect chain type (optional, will auto-detect if not provided)
|
|
122
|
+
const chainType = WalletUtils.detectChainType(address)
|
|
123
|
+
console.log('Detected chain type:', chainType)
|
|
124
|
+
|
|
125
|
+
// Step 4: Create signature callback
|
|
126
|
+
// ⚠️ CRITICAL: This callback receives the message to sign
|
|
127
|
+
const signatureCallback = async (message: string): Promise<string> => {
|
|
128
|
+
console.log('Message to sign:', message)
|
|
129
|
+
|
|
130
|
+
// Create ethers provider and signer
|
|
131
|
+
const provider = new ethers.BrowserProvider(window.ethereum)
|
|
132
|
+
const signer = await provider.getSigner()
|
|
133
|
+
|
|
134
|
+
// Sign the message
|
|
135
|
+
const signature = await signer.signMessage(message)
|
|
136
|
+
console.log('Signature:', signature)
|
|
137
|
+
|
|
138
|
+
return signature
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Step 5: Authenticate with SPAPS
|
|
142
|
+
// ✅ CORRECT METHOD NAME: authenticateWallet
|
|
143
|
+
const authResponse = await sdk.auth.authenticateWallet(
|
|
144
|
+
address,
|
|
145
|
+
signatureCallback,
|
|
146
|
+
'ethereum' // or chainType from detection
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
// Step 6: Store tokens
|
|
150
|
+
TokenManager.storeTokens(authResponse)
|
|
151
|
+
|
|
152
|
+
// Step 7: Setup auto-refresh
|
|
153
|
+
TokenManager.autoRefreshToken(sdk)
|
|
154
|
+
|
|
155
|
+
toast.success('Wallet authenticated successfully!')
|
|
156
|
+
console.log('Auth response:', authResponse)
|
|
157
|
+
|
|
158
|
+
} catch (error) {
|
|
159
|
+
handleWalletError(error)
|
|
160
|
+
} finally {
|
|
161
|
+
setIsLoading(false)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const handleWalletError = (error: any) => {
|
|
166
|
+
if (error instanceof SweetPotatoAPIError) {
|
|
167
|
+
switch (error.code) {
|
|
168
|
+
case 'INVALID_SIGNATURE':
|
|
169
|
+
toast.error('Invalid wallet signature')
|
|
170
|
+
break
|
|
171
|
+
case 'WALLET_NOT_FOUND':
|
|
172
|
+
toast.error('Wallet not registered')
|
|
173
|
+
break
|
|
174
|
+
case 'RATE_LIMITED':
|
|
175
|
+
toast.error('Too many attempts. Please wait.')
|
|
176
|
+
break
|
|
177
|
+
default:
|
|
178
|
+
toast.error(error.message)
|
|
179
|
+
}
|
|
180
|
+
} else if (error?.code === 4001) {
|
|
181
|
+
// User rejected the signature request in MetaMask
|
|
182
|
+
toast.error('Signature request rejected')
|
|
183
|
+
} else if (error?.code === -32002) {
|
|
184
|
+
// Request already pending in MetaMask
|
|
185
|
+
toast.error('Please check MetaMask for pending request')
|
|
186
|
+
} else {
|
|
187
|
+
toast.error(error.message || 'Wallet authentication failed')
|
|
188
|
+
}
|
|
189
|
+
console.error('Wallet auth error:', error)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Test wallet button
|
|
193
|
+
const fillTestWallet = () => {
|
|
194
|
+
setWalletAddress('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb8')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Card className="w-[400px]">
|
|
199
|
+
<CardHeader>
|
|
200
|
+
<CardTitle>Wallet Authentication</CardTitle>
|
|
201
|
+
<CardDescription>
|
|
202
|
+
Connect your Ethereum wallet to authenticate
|
|
203
|
+
</CardDescription>
|
|
204
|
+
</CardHeader>
|
|
205
|
+
<CardContent className="space-y-4">
|
|
206
|
+
<div className="p-4 bg-muted rounded-lg">
|
|
207
|
+
<p className="text-sm font-medium">Current Address:</p>
|
|
208
|
+
<p className="text-xs font-mono mt-1">
|
|
209
|
+
{walletAddress || 'Not connected'}
|
|
210
|
+
</p>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<Button
|
|
214
|
+
onClick={handleWalletConnect}
|
|
215
|
+
className="w-full"
|
|
216
|
+
disabled={isLoading}
|
|
217
|
+
>
|
|
218
|
+
{isLoading ? 'Connecting...' : 'Connect Ethereum Wallet'}
|
|
219
|
+
</Button>
|
|
220
|
+
|
|
221
|
+
<Button
|
|
222
|
+
variant="outline"
|
|
223
|
+
className="w-full"
|
|
224
|
+
onClick={fillTestWallet}
|
|
225
|
+
>
|
|
226
|
+
Use Test Wallet Address
|
|
227
|
+
</Button>
|
|
228
|
+
|
|
229
|
+
<p className="text-xs text-muted-foreground">
|
|
230
|
+
Test wallet: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb8
|
|
231
|
+
</p>
|
|
232
|
+
</CardContent>
|
|
233
|
+
</Card>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// TypeScript declarations for MetaMask
|
|
238
|
+
declare global {
|
|
239
|
+
interface Window {
|
|
240
|
+
ethereum?: {
|
|
241
|
+
request: (args: { method: string; params?: any[] }) => Promise<any>
|
|
242
|
+
on: (event: string, handler: (...args: any[]) => void) => void
|
|
243
|
+
removeListener: (event: string, handler: (...args: any[]) => void) => void
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## How authenticateWallet Works
|
|
250
|
+
|
|
251
|
+
The SDK handles these steps automatically:
|
|
252
|
+
1. **Gets nonce** from server for the wallet address
|
|
253
|
+
2. **Creates message** to sign with nonce
|
|
254
|
+
3. **Calls your callback** with the message
|
|
255
|
+
4. **Receives signature** from your callback
|
|
256
|
+
5. **Verifies signature** on server
|
|
257
|
+
6. **Returns auth tokens** if valid
|
|
258
|
+
|
|
259
|
+
You only need to provide the signature callback!
|
|
260
|
+
|
|
261
|
+
## Testing Without Real Wallet
|
|
262
|
+
|
|
263
|
+
For testing without MetaMask:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const mockSignatureCallback = async (message: string): Promise<string> => {
|
|
267
|
+
console.log('Would sign message:', message)
|
|
268
|
+
// Return mock signature for testing
|
|
269
|
+
return '0xmocked_signature_for_testing_purposes_only'
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Use in test mode
|
|
273
|
+
const auth = await sdk.auth.authenticateWallet(
|
|
274
|
+
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb8',
|
|
275
|
+
mockSignatureCallback,
|
|
276
|
+
'ethereum'
|
|
277
|
+
)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Validation Checklist
|
|
281
|
+
|
|
282
|
+
✅ **Method Name**:
|
|
283
|
+
- Uses `authenticateWallet` (NOT signInWithWallet)
|
|
284
|
+
- Three parameters: address, callback, chainType
|
|
285
|
+
|
|
286
|
+
✅ **Signature Callback**:
|
|
287
|
+
- Receives message as parameter
|
|
288
|
+
- Returns signature as string or Promise<string>
|
|
289
|
+
- Properly signs with wallet
|
|
290
|
+
|
|
291
|
+
✅ **Chain Detection**:
|
|
292
|
+
- Uses `WalletUtils.detectChainType(address)`
|
|
293
|
+
- Passes correct chain type ('ethereum', 'solana', etc.)
|
|
294
|
+
|
|
295
|
+
✅ **Token Storage**:
|
|
296
|
+
- Calls `TokenManager.storeTokens(authResponse)`
|
|
297
|
+
- Sets up `TokenManager.autoRefreshToken(sdk)`
|
|
298
|
+
|
|
299
|
+
✅ **Error Handling**:
|
|
300
|
+
- Handles MetaMask not installed
|
|
301
|
+
- Handles signature rejection (code 4001)
|
|
302
|
+
- Handles SweetPotatoAPIError codes
|
|
303
|
+
|
|
304
|
+
## Common Mistakes to Avoid
|
|
305
|
+
|
|
306
|
+
❌ **Wrong method name**:
|
|
307
|
+
```typescript
|
|
308
|
+
// WRONG - This method doesn't exist!
|
|
309
|
+
sdk.auth.signInWithWallet(address, signature, chain)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
❌ **Not providing callback**:
|
|
313
|
+
```typescript
|
|
314
|
+
// WRONG - Missing signature callback
|
|
315
|
+
sdk.auth.authenticateWallet(address)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
❌ **Signing outside of callback**:
|
|
319
|
+
```typescript
|
|
320
|
+
// WRONG - Don't pre-sign
|
|
321
|
+
const signature = await signer.signMessage('some message')
|
|
322
|
+
sdk.auth.authenticateWallet(address, signature) // NO!
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
✅ **CORRECT - Provide callback**:
|
|
326
|
+
```typescript
|
|
327
|
+
sdk.auth.authenticateWallet(
|
|
328
|
+
address,
|
|
329
|
+
async (message) => await signer.signMessage(message),
|
|
330
|
+
'ethereum'
|
|
331
|
+
)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Testing Requirements
|
|
335
|
+
|
|
336
|
+
1. **With MetaMask**:
|
|
337
|
+
- Install MetaMask extension
|
|
338
|
+
- Connect to any network
|
|
339
|
+
- Click "Connect Wallet"
|
|
340
|
+
- Sign the message when prompted
|
|
341
|
+
- Should authenticate successfully
|
|
342
|
+
|
|
343
|
+
2. **Test Address**:
|
|
344
|
+
- Address: `0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb8`
|
|
345
|
+
- Should work with mock signature in local mode
|
|
346
|
+
|
|
347
|
+
3. **Error Cases**:
|
|
348
|
+
- Reject signature → Should show "rejected" error
|
|
349
|
+
- No MetaMask → Should show "install MetaMask" error
|
|
350
|
+
|
|
351
|
+
## Next Step
|
|
352
|
+
|
|
353
|
+
When ALL todos are ✅ complete and wallet auth works:
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
mcp__product-manager__get_agent_instructions({
|
|
357
|
+
category: "spaps-integration",
|
|
358
|
+
project: "spaps-demo",
|
|
359
|
+
step: 6
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Remember
|
|
364
|
+
|
|
365
|
+
🔴 **CRITICAL**: The method is `authenticateWallet` NOT `signInWithWallet`!
|
|
366
|
+
- This is the #1 mistake that causes integration failures
|
|
367
|
+
- Double-check your code uses the correct method name
|
|
368
|
+
- The signature must be provided via callback, not directly
|