@b3dotfun/sdk 0.0.9-alpha.1 → 0.0.9-alpha.3
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/dist/cjs/anyspend/types/api.d.ts +7 -31
- package/dist/esm/anyspend/types/api.d.ts +7 -31
- package/dist/types/anyspend/types/api.d.ts +7 -31
- package/package.json +1 -1
- package/src/anyspend/README.md +70 -556
- package/src/anyspend/docs/components.md +292 -0
- package/src/anyspend/docs/contributing.md +448 -0
- package/src/anyspend/docs/error-handling.md +735 -0
- package/src/anyspend/docs/examples.md +707 -0
- package/src/anyspend/docs/hooks.md +465 -0
- package/src/anyspend/docs/installation.md +113 -0
- package/src/anyspend/types/api.ts +7 -31
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
# Error Handling & Troubleshooting
|
|
2
|
+
|
|
3
|
+
Comprehensive guide to handling errors gracefully and debugging common issues with AnySpend.
|
|
4
|
+
|
|
5
|
+
## 📊 Order Status Lifecycle
|
|
6
|
+
|
|
7
|
+
Understanding order states is crucial for proper error handling and user experience.
|
|
8
|
+
|
|
9
|
+
### Order Status Types
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
enum OrderStatus {
|
|
13
|
+
// Initial States
|
|
14
|
+
SCANNING_DEPOSIT_TRANSACTION = "scanning_deposit_transaction",
|
|
15
|
+
WAITING_STRIPE_PAYMENT = "waiting_stripe_payment",
|
|
16
|
+
OBTAIN_TOKEN = "obtain_token",
|
|
17
|
+
|
|
18
|
+
// Processing States
|
|
19
|
+
SENDING_TOKEN_FROM_VAULT = "sending_token_from_vault",
|
|
20
|
+
RELAY = "relay",
|
|
21
|
+
|
|
22
|
+
// Success States
|
|
23
|
+
EXECUTED = "executed",
|
|
24
|
+
|
|
25
|
+
// Failure States
|
|
26
|
+
OBTAIN_FAILED = "obtain_failed",
|
|
27
|
+
EXPIRED = "expired",
|
|
28
|
+
REFUNDING = "refunding",
|
|
29
|
+
REFUNDED = "refunded",
|
|
30
|
+
FAILURE = "failure",
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Status Descriptions
|
|
35
|
+
|
|
36
|
+
| Status | Description | User Action Required |
|
|
37
|
+
|--------|-------------|---------------------|
|
|
38
|
+
| `scanning_deposit_transaction` | Waiting for payment confirmation | None - wait for blockchain confirmation |
|
|
39
|
+
| `waiting_stripe_payment` | Processing credit card payment | May need to complete 3D Secure |
|
|
40
|
+
| `obtain_token` | Getting source tokens from vault | None - automatic process |
|
|
41
|
+
| `sending_token_from_vault` | Sending tokens for swap | None - automatic process |
|
|
42
|
+
| `relay` | Cross-chain transaction in progress | None - wait for completion |
|
|
43
|
+
| `executed` | Transaction completed successfully | None - success! |
|
|
44
|
+
| `obtain_failed` | Failed to obtain source tokens | Check payment method/balance |
|
|
45
|
+
| `expired` | Order expired before completion | Create new order |
|
|
46
|
+
| `refunding` | Automatic refund in progress | None - wait for refund |
|
|
47
|
+
| `refunded` | Refund completed | Check wallet for refunded tokens |
|
|
48
|
+
| `failure` | Transaction failed | Review error details, retry |
|
|
49
|
+
|
|
50
|
+
## ⚠️ Common Error Codes
|
|
51
|
+
|
|
52
|
+
### Payment Errors
|
|
53
|
+
|
|
54
|
+
| Error Code | Description | Solution |
|
|
55
|
+
|------------|-------------|----------|
|
|
56
|
+
| `INSUFFICIENT_BALANCE` | User doesn't have enough tokens | Request user to add funds |
|
|
57
|
+
| `INVALID_TOKEN_ADDRESS` | Token contract not supported | Verify token is supported on target chain |
|
|
58
|
+
| `MINIMUM_AMOUNT_NOT_MET` | Amount below minimum threshold | Increase transaction amount |
|
|
59
|
+
| `MAXIMUM_AMOUNT_EXCEEDED` | Amount above maximum limit | Reduce transaction amount or split |
|
|
60
|
+
|
|
61
|
+
### Network Errors
|
|
62
|
+
|
|
63
|
+
| Error Code | Description | Solution |
|
|
64
|
+
|------------|-------------|----------|
|
|
65
|
+
| `SLIPPAGE` | Price moved beyond tolerance | Retry with higher slippage or wait |
|
|
66
|
+
| `NETWORK_ERROR` | RPC or connectivity issues | Retry after delay |
|
|
67
|
+
| `QUOTE_EXPIRED` | Price quote no longer valid | Get fresh quote |
|
|
68
|
+
| `CHAIN_NOT_SUPPORTED` | Blockchain not supported | Use supported chain |
|
|
69
|
+
|
|
70
|
+
### Contract Errors
|
|
71
|
+
|
|
72
|
+
| Error Code | Description | Solution |
|
|
73
|
+
|------------|-------------|----------|
|
|
74
|
+
| `CONTRACT_CALL_FAILED` | Smart contract execution failed | Check contract parameters |
|
|
75
|
+
| `INSUFFICIENT_GAS` | Gas limit too low | Increase gas limit |
|
|
76
|
+
| `NONCE_TOO_LOW` | Transaction nonce issue | Wait and retry |
|
|
77
|
+
| `TRANSACTION_REVERTED` | Contract reverted transaction | Check contract state and parameters |
|
|
78
|
+
|
|
79
|
+
## 🛠️ Error Handling Patterns
|
|
80
|
+
|
|
81
|
+
### Component-Level Error Handling
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useAnyspendCreateOrder } from "@b3dotfun/sdk/anyspend";
|
|
85
|
+
|
|
86
|
+
function PaymentComponent() {
|
|
87
|
+
const [error, setError] = useState<string | null>(null);
|
|
88
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
89
|
+
|
|
90
|
+
const { createOrder, isCreatingOrder } = useAnyspendCreateOrder({
|
|
91
|
+
onError: (error) => {
|
|
92
|
+
console.error("Payment failed:", error);
|
|
93
|
+
|
|
94
|
+
// Handle specific errors
|
|
95
|
+
switch (error.message) {
|
|
96
|
+
case "INSUFFICIENT_BALANCE":
|
|
97
|
+
setError("Insufficient balance. Please add funds to your wallet.");
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case "SLIPPAGE":
|
|
101
|
+
if (retryCount < 3) {
|
|
102
|
+
setError("Price moved unfavorably. Retrying...");
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
setRetryCount(prev => prev + 1);
|
|
105
|
+
retryPayment();
|
|
106
|
+
}, 2000);
|
|
107
|
+
} else {
|
|
108
|
+
setError("Price too volatile. Please try again later.");
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case "NETWORK_ERROR":
|
|
113
|
+
setError("Network issue. Please check your connection and try again.");
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case "QUOTE_EXPIRED":
|
|
117
|
+
setError("Price quote expired. Getting fresh quote...");
|
|
118
|
+
refreshQuote();
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
default:
|
|
122
|
+
setError("Payment failed. Please try again or contact support.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Track errors for monitoring
|
|
126
|
+
analytics.track("payment_error", {
|
|
127
|
+
error: error.message,
|
|
128
|
+
retryCount,
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
onSuccess: () => {
|
|
134
|
+
setError(null);
|
|
135
|
+
setRetryCount(0);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="payment-component">
|
|
141
|
+
{error && (
|
|
142
|
+
<div className="error-banner">
|
|
143
|
+
<span className="error-icon">⚠️</span>
|
|
144
|
+
<span>{error}</span>
|
|
145
|
+
<button onClick={() => setError(null)}>Dismiss</button>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
<button
|
|
150
|
+
onClick={handlePayment}
|
|
151
|
+
disabled={isCreatingOrder}
|
|
152
|
+
>
|
|
153
|
+
{isCreatingOrder ? "Processing..." : "Pay Now"}
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Order Status Monitoring
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { useAnyspendOrderAndTransactions } from "@b3dotfun/sdk/anyspend";
|
|
164
|
+
|
|
165
|
+
function OrderStatusMonitor({ orderId }: { orderId: string }) {
|
|
166
|
+
const { orderAndTransactions, getOrderAndTransactionsError } =
|
|
167
|
+
useAnyspendOrderAndTransactions(true, orderId);
|
|
168
|
+
|
|
169
|
+
if (getOrderAndTransactionsError) {
|
|
170
|
+
return (
|
|
171
|
+
<div className="error-state">
|
|
172
|
+
<h3>Unable to load order status</h3>
|
|
173
|
+
<p>Please check your connection and try again.</p>
|
|
174
|
+
<button onClick={() => window.location.reload()}>
|
|
175
|
+
Retry
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!orderAndTransactions) {
|
|
182
|
+
return <div>Loading order status...</div>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { order, depositTxs, executeTx, refundTxs } = orderAndTransactions.data;
|
|
186
|
+
|
|
187
|
+
const renderStatusMessage = () => {
|
|
188
|
+
switch (order.status) {
|
|
189
|
+
case "scanning_deposit_transaction":
|
|
190
|
+
return (
|
|
191
|
+
<div className="status-pending">
|
|
192
|
+
<div className="spinner" />
|
|
193
|
+
<div>
|
|
194
|
+
<h3>⏳ Waiting for payment confirmation</h3>
|
|
195
|
+
<p>This usually takes 1-2 minutes. Please don't close this window.</p>
|
|
196
|
+
{depositTxs.length > 0 && (
|
|
197
|
+
<a
|
|
198
|
+
href={getExplorerUrl(depositTxs[0].txHash, depositTxs[0].chainId)}
|
|
199
|
+
target="_blank"
|
|
200
|
+
rel="noopener noreferrer"
|
|
201
|
+
>
|
|
202
|
+
View payment transaction
|
|
203
|
+
</a>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
case "relay":
|
|
210
|
+
return (
|
|
211
|
+
<div className="status-processing">
|
|
212
|
+
<div className="spinner" />
|
|
213
|
+
<div>
|
|
214
|
+
<h3>🔄 Processing cross-chain transaction</h3>
|
|
215
|
+
<p>Your payment is being processed. This may take a few minutes.</p>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
case "executed":
|
|
221
|
+
return (
|
|
222
|
+
<div className="status-success">
|
|
223
|
+
<div className="success-icon">✅</div>
|
|
224
|
+
<div>
|
|
225
|
+
<h3>Transaction completed successfully!</h3>
|
|
226
|
+
<p>Your order has been processed.</p>
|
|
227
|
+
{executeTx && (
|
|
228
|
+
<a
|
|
229
|
+
href={getExplorerUrl(executeTx.txHash, executeTx.chainId)}
|
|
230
|
+
target="_blank"
|
|
231
|
+
rel="noopener noreferrer"
|
|
232
|
+
>
|
|
233
|
+
View transaction
|
|
234
|
+
</a>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
case "failure":
|
|
241
|
+
case "obtain_failed":
|
|
242
|
+
return (
|
|
243
|
+
<div className="status-error">
|
|
244
|
+
<div className="error-icon">❌</div>
|
|
245
|
+
<div>
|
|
246
|
+
<h3>Transaction failed</h3>
|
|
247
|
+
<p>{order.errorDetails || "An error occurred while processing your order."}</p>
|
|
248
|
+
<div className="error-actions">
|
|
249
|
+
<button onClick={() => createNewOrder()}>
|
|
250
|
+
Try Again
|
|
251
|
+
</button>
|
|
252
|
+
<button onClick={() => contactSupport(orderId)}>
|
|
253
|
+
Contact Support
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
case "refunded":
|
|
261
|
+
return (
|
|
262
|
+
<div className="status-refunded">
|
|
263
|
+
<div className="refund-icon">↩️</div>
|
|
264
|
+
<div>
|
|
265
|
+
<h3>Refund processed</h3>
|
|
266
|
+
<p>Your payment has been refunded automatically.</p>
|
|
267
|
+
{refundTxs.length > 0 && (
|
|
268
|
+
<a
|
|
269
|
+
href={getExplorerUrl(refundTxs[0].txHash, refundTxs[0].chainId)}
|
|
270
|
+
target="_blank"
|
|
271
|
+
rel="noopener noreferrer"
|
|
272
|
+
>
|
|
273
|
+
View refund transaction
|
|
274
|
+
</a>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
case "expired":
|
|
281
|
+
return (
|
|
282
|
+
<div className="status-expired">
|
|
283
|
+
<div className="expired-icon">⏰</div>
|
|
284
|
+
<div>
|
|
285
|
+
<h3>Order expired</h3>
|
|
286
|
+
<p>This order expired before payment was received.</p>
|
|
287
|
+
<button onClick={() => createNewOrder()}>
|
|
288
|
+
Create New Order
|
|
289
|
+
</button>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
default:
|
|
295
|
+
return (
|
|
296
|
+
<div className="status-unknown">
|
|
297
|
+
<div className="spinner" />
|
|
298
|
+
<div>
|
|
299
|
+
<h3>Processing...</h3>
|
|
300
|
+
<p>Order status: {order.status}</p>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className="order-status-monitor">
|
|
309
|
+
<div className="order-header">
|
|
310
|
+
<h2>Order #{orderId.slice(0, 8)}</h2>
|
|
311
|
+
<div className="order-meta">
|
|
312
|
+
<span>Created: {new Date(order.createdAt).toLocaleString()}</span>
|
|
313
|
+
<span>Status: {order.status}</span>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
{renderStatusMessage()}
|
|
318
|
+
|
|
319
|
+
{/* Debug information in development */}
|
|
320
|
+
{process.env.NODE_ENV === "development" && (
|
|
321
|
+
<details className="debug-info">
|
|
322
|
+
<summary>Debug Information</summary>
|
|
323
|
+
<pre>{JSON.stringify(order, null, 2)}</pre>
|
|
324
|
+
</details>
|
|
325
|
+
)}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Global Error Boundary
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import React, { Component, ErrorInfo } from "react";
|
|
335
|
+
|
|
336
|
+
interface Props {
|
|
337
|
+
children: React.ReactNode;
|
|
338
|
+
fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
interface State {
|
|
342
|
+
hasError: boolean;
|
|
343
|
+
error?: Error;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
class AnySpendErrorBoundary extends Component<Props, State> {
|
|
347
|
+
constructor(props: Props) {
|
|
348
|
+
super(props);
|
|
349
|
+
this.state = { hasError: false };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
static getDerivedStateFromError(error: Error): State {
|
|
353
|
+
return { hasError: true, error };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
357
|
+
console.error("AnySpend Error Boundary caught an error:", error, errorInfo);
|
|
358
|
+
|
|
359
|
+
// Report to error tracking service
|
|
360
|
+
if (typeof window !== "undefined") {
|
|
361
|
+
// Example: Sentry.captureException(error, { contexts: { errorInfo } });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
resetError = () => {
|
|
366
|
+
this.setState({ hasError: false, error: undefined });
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
render() {
|
|
370
|
+
if (this.state.hasError) {
|
|
371
|
+
const FallbackComponent = this.props.fallback || DefaultErrorFallback;
|
|
372
|
+
return (
|
|
373
|
+
<FallbackComponent
|
|
374
|
+
error={this.state.error!}
|
|
375
|
+
resetError={this.resetError}
|
|
376
|
+
/>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return this.props.children;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function DefaultErrorFallback({ error, resetError }: { error: Error; resetError: () => void }) {
|
|
385
|
+
return (
|
|
386
|
+
<div className="error-fallback">
|
|
387
|
+
<h2>Something went wrong</h2>
|
|
388
|
+
<p>An unexpected error occurred in the payment component.</p>
|
|
389
|
+
<details className="error-details">
|
|
390
|
+
<summary>Error details</summary>
|
|
391
|
+
<pre>{error.message}</pre>
|
|
392
|
+
</details>
|
|
393
|
+
<div className="error-actions">
|
|
394
|
+
<button onClick={resetError}>Try Again</button>
|
|
395
|
+
<button onClick={() => window.location.reload()}>
|
|
396
|
+
Reload Page
|
|
397
|
+
</button>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Usage
|
|
404
|
+
function App() {
|
|
405
|
+
return (
|
|
406
|
+
<AnySpendErrorBoundary>
|
|
407
|
+
<AnySpendProvider>
|
|
408
|
+
{/* Your app components */}
|
|
409
|
+
</AnySpendProvider>
|
|
410
|
+
</AnySpendErrorBoundary>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## 🐛 Troubleshooting Common Issues
|
|
416
|
+
|
|
417
|
+
### Issue: "Get rate error" when trying to swap
|
|
418
|
+
|
|
419
|
+
**Symptoms:**
|
|
420
|
+
- Quote request fails
|
|
421
|
+
- Unable to get pricing information
|
|
422
|
+
- Network requests timeout
|
|
423
|
+
|
|
424
|
+
**Solutions:**
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
function DiagnoseQuoteIssue() {
|
|
428
|
+
const [quoteRequest, setQuoteRequest] = useState(null);
|
|
429
|
+
const [diagnosis, setDiagnosis] = useState("");
|
|
430
|
+
|
|
431
|
+
const diagnoseIssue = async () => {
|
|
432
|
+
try {
|
|
433
|
+
// Check if tokens are valid
|
|
434
|
+
const srcTokenValid = await validateToken(quoteRequest.srcTokenAddress, quoteRequest.srcChain);
|
|
435
|
+
const dstTokenValid = await validateToken(quoteRequest.dstTokenAddress, quoteRequest.dstChain);
|
|
436
|
+
|
|
437
|
+
if (!srcTokenValid) {
|
|
438
|
+
setDiagnosis("Source token address is invalid or not supported");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (!dstTokenValid) {
|
|
443
|
+
setDiagnosis("Destination token address is invalid or not supported");
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Check if amount is within limits
|
|
448
|
+
const amount = parseFloat(quoteRequest.amount);
|
|
449
|
+
if (amount < MIN_SWAP_AMOUNT) {
|
|
450
|
+
setDiagnosis(`Amount too small. Minimum: ${MIN_SWAP_AMOUNT}`);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (amount > MAX_SWAP_AMOUNT) {
|
|
455
|
+
setDiagnosis(`Amount too large. Maximum: ${MAX_SWAP_AMOUNT}`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check if chain pair is supported
|
|
460
|
+
const chainPairSupported = await checkChainPairSupport(
|
|
461
|
+
quoteRequest.srcChain,
|
|
462
|
+
quoteRequest.dstChain
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
if (!chainPairSupported) {
|
|
466
|
+
setDiagnosis("This chain pair is not currently supported");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
setDiagnosis("All checks passed. Try refreshing the quote.");
|
|
471
|
+
|
|
472
|
+
} catch (error) {
|
|
473
|
+
setDiagnosis(`Network error: ${error.message}`);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
return (
|
|
478
|
+
<div className="quote-diagnostics">
|
|
479
|
+
<button onClick={diagnoseIssue}>Diagnose Quote Issue</button>
|
|
480
|
+
{diagnosis && <p>{diagnosis}</p>}
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Issue: Order stuck in "scanning_deposit_transaction"
|
|
487
|
+
|
|
488
|
+
**Symptoms:**
|
|
489
|
+
- Order remains in pending state for over 10 minutes
|
|
490
|
+
- Payment transaction confirmed on blockchain
|
|
491
|
+
- No progress in AnySpend system
|
|
492
|
+
|
|
493
|
+
**Solutions:**
|
|
494
|
+
|
|
495
|
+
```tsx
|
|
496
|
+
function DepositDiagnostics({ orderId }: { orderId: string }) {
|
|
497
|
+
const [depositDiag, setDepositDiag] = useState(null);
|
|
498
|
+
|
|
499
|
+
const diagnoseDespositIssue = async () => {
|
|
500
|
+
try {
|
|
501
|
+
const order = await anyspendService.getOrder(true, orderId);
|
|
502
|
+
|
|
503
|
+
if (!order.depositAddress) {
|
|
504
|
+
setDepositDiag({
|
|
505
|
+
issue: "No deposit address generated",
|
|
506
|
+
solution: "Contact support with order ID"
|
|
507
|
+
});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Check if payment was sent to correct address
|
|
512
|
+
const expectedAddress = order.depositAddress;
|
|
513
|
+
const userTxs = await getTransactionsToAddress(expectedAddress);
|
|
514
|
+
|
|
515
|
+
if (userTxs.length === 0) {
|
|
516
|
+
setDepositDiag({
|
|
517
|
+
issue: "No payment received at deposit address",
|
|
518
|
+
solution: "Ensure you sent payment to: " + expectedAddress
|
|
519
|
+
});
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Check if amount matches
|
|
524
|
+
const expectedAmount = order.srcAmount;
|
|
525
|
+
const receivedAmount = userTxs[0].amount;
|
|
526
|
+
|
|
527
|
+
if (receivedAmount !== expectedAmount) {
|
|
528
|
+
setDepositDiag({
|
|
529
|
+
issue: `Amount mismatch. Expected: ${expectedAmount}, Received: ${receivedAmount}`,
|
|
530
|
+
solution: "Send the exact amount specified"
|
|
531
|
+
});
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Check transaction confirmations
|
|
536
|
+
const confirmations = await getTransactionConfirmations(userTxs[0].hash);
|
|
537
|
+
const requiredConfirmations = getRequiredConfirmations(order.srcChain);
|
|
538
|
+
|
|
539
|
+
if (confirmations < requiredConfirmations) {
|
|
540
|
+
setDepositDiag({
|
|
541
|
+
issue: `Waiting for confirmations: ${confirmations}/${requiredConfirmations}`,
|
|
542
|
+
solution: "Wait for more blockchain confirmations"
|
|
543
|
+
});
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
setDepositDiag({
|
|
548
|
+
issue: "Payment detected but not processed",
|
|
549
|
+
solution: "This may be a system delay. Contact support if it persists."
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
} catch (error) {
|
|
553
|
+
setDepositDiag({
|
|
554
|
+
issue: "Unable to diagnose",
|
|
555
|
+
solution: "Check your network connection and try again"
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
return (
|
|
561
|
+
<div className="deposit-diagnostics">
|
|
562
|
+
<button onClick={diagnoseDespositIssue}>
|
|
563
|
+
Diagnose Deposit Issue
|
|
564
|
+
</button>
|
|
565
|
+
|
|
566
|
+
{depositDiag && (
|
|
567
|
+
<div className="diagnosis-result">
|
|
568
|
+
<h4>Issue: {depositDiag.issue}</h4>
|
|
569
|
+
<p>Solution: {depositDiag.solution}</p>
|
|
570
|
+
</div>
|
|
571
|
+
)}
|
|
572
|
+
</div>
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Issue: React Native Build Problems
|
|
578
|
+
|
|
579
|
+
**Symptoms:**
|
|
580
|
+
- Metro bundler errors
|
|
581
|
+
- Module resolution failures
|
|
582
|
+
- Platform-specific component issues
|
|
583
|
+
|
|
584
|
+
**Solutions:**
|
|
585
|
+
|
|
586
|
+
1. **Correct Import Paths:**
|
|
587
|
+
```tsx
|
|
588
|
+
// ✅ Correct for React Native
|
|
589
|
+
import { anyspendService } from "@b3dotfun/sdk/anyspend";
|
|
590
|
+
import { useAnyspendQuote } from "@b3dotfun/sdk/anyspend";
|
|
591
|
+
|
|
592
|
+
// ❌ Incorrect for React Native (web-only components)
|
|
593
|
+
import { AnySpend } from "@b3dotfun/sdk/anyspend/react";
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
2. **Metro Configuration:**
|
|
597
|
+
```javascript
|
|
598
|
+
// metro.config.js
|
|
599
|
+
module.exports = {
|
|
600
|
+
resolver: {
|
|
601
|
+
alias: {
|
|
602
|
+
'@b3dotfun/sdk': require.resolve('@b3dotfun/sdk/index.native.js'),
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
3. **Platform-Specific Code:**
|
|
609
|
+
```tsx
|
|
610
|
+
import { Platform } from "react-native";
|
|
611
|
+
|
|
612
|
+
function PaymentComponent() {
|
|
613
|
+
if (Platform.OS === "web") {
|
|
614
|
+
// Use web components
|
|
615
|
+
const { AnySpend } = require("@b3dotfun/sdk/anyspend/react");
|
|
616
|
+
return <AnySpend {...props} />;
|
|
617
|
+
} else {
|
|
618
|
+
// Use service layer for native
|
|
619
|
+
return <CustomNativePaymentFlow />;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## 🔧 Debug Mode
|
|
625
|
+
|
|
626
|
+
Enable comprehensive logging for troubleshooting:
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
// Browser
|
|
630
|
+
localStorage.setItem("debug", "anyspend:*");
|
|
631
|
+
|
|
632
|
+
// Node.js/React Native
|
|
633
|
+
process.env.DEBUG = "anyspend:*";
|
|
634
|
+
|
|
635
|
+
// Or programmatically
|
|
636
|
+
import { anyspendService } from "@b3dotfun/sdk/anyspend";
|
|
637
|
+
|
|
638
|
+
// Enable debug logging
|
|
639
|
+
anyspendService.setDebugMode(true);
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Debug Information Collection
|
|
643
|
+
|
|
644
|
+
```tsx
|
|
645
|
+
function DebugInfo({ orderId }: { orderId: string }) {
|
|
646
|
+
const [debugData, setDebugData] = useState(null);
|
|
647
|
+
|
|
648
|
+
const collectDebugInfo = async () => {
|
|
649
|
+
const debugInfo = {
|
|
650
|
+
timestamp: new Date().toISOString(),
|
|
651
|
+
userAgent: navigator.userAgent,
|
|
652
|
+
url: window.location.href,
|
|
653
|
+
orderId,
|
|
654
|
+
|
|
655
|
+
// Network info
|
|
656
|
+
connection: (navigator as any).connection,
|
|
657
|
+
|
|
658
|
+
// Order data
|
|
659
|
+
order: await anyspendService.getOrder(true, orderId),
|
|
660
|
+
|
|
661
|
+
// Browser storage
|
|
662
|
+
localStorage: { ...localStorage },
|
|
663
|
+
|
|
664
|
+
// Console errors
|
|
665
|
+
recentErrors: getRecentConsoleErrors(),
|
|
666
|
+
|
|
667
|
+
// SDK version
|
|
668
|
+
sdkVersion: process.env.REACT_APP_SDK_VERSION,
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
setDebugData(debugInfo);
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const copyDebugInfo = () => {
|
|
675
|
+
navigator.clipboard.writeText(JSON.stringify(debugData, null, 2));
|
|
676
|
+
toast.success("Debug info copied to clipboard");
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
return (
|
|
680
|
+
<div className="debug-info">
|
|
681
|
+
<button onClick={collectDebugInfo}>
|
|
682
|
+
Collect Debug Info
|
|
683
|
+
</button>
|
|
684
|
+
|
|
685
|
+
{debugData && (
|
|
686
|
+
<div>
|
|
687
|
+
<button onClick={copyDebugInfo}>
|
|
688
|
+
Copy to Clipboard
|
|
689
|
+
</button>
|
|
690
|
+
<pre className="debug-output">
|
|
691
|
+
{JSON.stringify(debugData, null, 2)}
|
|
692
|
+
</pre>
|
|
693
|
+
</div>
|
|
694
|
+
)}
|
|
695
|
+
</div>
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## 📞 Support Channels
|
|
701
|
+
|
|
702
|
+
When you need additional help:
|
|
703
|
+
|
|
704
|
+
- **Documentation**: [View latest docs](../README.md)
|
|
705
|
+
- **GitHub Issues**: [Report bugs](https://github.com/b3-fun/b3/issues)
|
|
706
|
+
- **Discord**: [Join community](https://discord.gg/b3dotfun)
|
|
707
|
+
- **Email Support**: Include debug information and order IDs
|
|
708
|
+
|
|
709
|
+
### Creating Effective Bug Reports
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
interface BugReport {
|
|
713
|
+
title: string;
|
|
714
|
+
description: string;
|
|
715
|
+
stepsToReproduce: string[];
|
|
716
|
+
expectedBehavior: string;
|
|
717
|
+
actualBehavior: string;
|
|
718
|
+
environment: {
|
|
719
|
+
sdkVersion: string;
|
|
720
|
+
browser: string;
|
|
721
|
+
platform: string;
|
|
722
|
+
networkType: "mainnet" | "testnet";
|
|
723
|
+
};
|
|
724
|
+
orderId?: string;
|
|
725
|
+
transactionHash?: string;
|
|
726
|
+
errorMessage?: string;
|
|
727
|
+
debugInfo?: object;
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
## Next Steps
|
|
732
|
+
|
|
733
|
+
- [View Installation Guide →](./installation.md)
|
|
734
|
+
- [Explore Components →](./components.md)
|
|
735
|
+
- [See Examples →](./examples.md)
|