@pioneer-platform/zapper-client 8.16.0 โ†’ 8.19.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 CHANGED
@@ -1,5 +1,38 @@
1
1
  # @pioneer-platform/zapper-client
2
2
 
3
+ ## 8.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - chore: Merge pull request #10 from coinmastersguild/feature/fix-blockbook-websocket-subscriptions
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @pioneer-platform/pioneer-caip@9.19.0
13
+
14
+ ## 8.18.0
15
+
16
+ ### Minor Changes
17
+
18
+ - Merge pull request #10 from coinmastersguild/feature/fix-blockbook-websocket-subscriptions
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies
23
+ - @pioneer-platform/pioneer-caip@9.18.0
24
+
25
+ ## 8.17.0
26
+
27
+ ### Minor Changes
28
+
29
+ - chore: chore: chore: chore: chore: chore: feat(pioneer): implement end-to-end Solana transaction signing
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies
34
+ - @pioneer-platform/pioneer-caip@9.17.0
35
+
3
36
  ## 8.16.0
4
37
 
5
38
  ### Minor Changes
@@ -0,0 +1,187 @@
1
+ # Zapper Integration - Error Handling Upgrade
2
+
3
+ ## ๐ŸŽฏ Changes Made
4
+
5
+ ### 1. **Proper Error Propagation** โœ…
6
+ **Before**: Functions returned empty data (0, [], null) on errors, which got cached
7
+ **After**: Functions throw proper `ZapperAPIError` exceptions with error codes
8
+
9
+ ```typescript
10
+ // OLD (BAD)
11
+ catch (e) {
12
+ return {
13
+ balances: [],
14
+ totalNetWorth: 0 // This gets cached!
15
+ }
16
+ }
17
+
18
+ // NEW (GOOD)
19
+ catch (e) {
20
+ await handleZapperError(e, 'getPortfolio');
21
+ // Throws error - won't be cached
22
+ }
23
+ ```
24
+
25
+ ### 2. **Discord Alerts for Critical Issues** ๐Ÿ””
26
+ Automatically sends Discord notifications when:
27
+ - ๐Ÿšจ **CRITICAL**: API credits exhausted (PAYMENT_REQUIRED)
28
+ - โš ๏ธ **WARNING**: Rate limit exceeded
29
+ - โŒ **ERROR**: Authentication failures
30
+
31
+ ### 3. **Specific Error Codes** ๐Ÿท๏ธ
32
+ - `402` - Payment Required (credits exhausted)
33
+ - `429` - Rate Limited
34
+ - `401` - Authentication Failed
35
+ - `500` - Generic API errors
36
+ - `503` - Network/connection errors
37
+
38
+ ### 4. **Error Handler Function** ๐Ÿ›ก๏ธ
39
+ New `handleZapperError()` function:
40
+ - Detects specific Zapper error types from GraphQL responses
41
+ - Sends Discord alerts for critical issues
42
+ - Throws typed `ZapperAPIError` with proper HTTP status codes
43
+ - Prevents empty data from being cached
44
+
45
+ ---
46
+
47
+ ## ๐Ÿ“‹ Setup Required
48
+
49
+ ### 1. Environment Variables
50
+ Add to `.env` file:
51
+ ```bash
52
+ # Required
53
+ ZAPPER_API_KEY=your-production-key-here
54
+
55
+ # Optional but HIGHLY recommended
56
+ DISCORD_WEBHOOK_ZAPPER_ALERTS=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
57
+ ```
58
+
59
+ ### 2. Restart Server
60
+ The module has been rebuilt, but the server needs restart to load changes:
61
+ ```bash
62
+ cd /Users/highlander/WebstormProjects/keepkey-stack/projects/pioneer/services/pioneer-server
63
+
64
+ # Kill existing server
65
+ lsof -ti:9001 | xargs kill -9
66
+
67
+ # Start fresh
68
+ make start
69
+ ```
70
+
71
+ ### 3. Clear Stale Cache
72
+ Clear any cached empty data from before:
73
+ ```bash
74
+ # Clear all Zapper cache
75
+ redis-cli KEYS "zapper_v1:*" | xargs redis-cli DEL
76
+
77
+ # Or via API
78
+ curl http://localhost:9001/api/v1/zapper/clear/YOUR_ADDRESS
79
+ ```
80
+
81
+ ---
82
+
83
+ ## ๐Ÿงช Testing
84
+
85
+ ### Test Error Handling
86
+ ```bash
87
+ # 1. Test with valid address (should work)
88
+ curl -s "http://localhost:9001/api/v1/zapper/totals/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" | jq
89
+
90
+ # 2. Simulate exhausted credits (if you have test key)
91
+ # Should return 402 and send Discord alert
92
+
93
+ # 3. Check cache stats
94
+ curl -s "http://localhost:9001/api/v1/zapper/stats/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" | jq
95
+ ```
96
+
97
+ ### Verify Discord Alerts
98
+ 1. Trigger an error (exhausted credits, rate limit, etc.)
99
+ 2. Check your Discord channel for alert
100
+ 3. Should see formatted embed with:
101
+ - Error severity (CRITICAL/WARNING/ERROR)
102
+ - Operation that failed
103
+ - Error message
104
+ - Action required
105
+
106
+ ---
107
+
108
+ ## ๐Ÿ” How It Works Now
109
+
110
+ ### Error Flow
111
+ ```
112
+ 1. User Request โ†’ Zapper API
113
+ โ†“
114
+ 2. API Error (e.g., PAYMENT_REQUIRED)
115
+ โ†“
116
+ 3. handleZapperError() detects error type
117
+ โ†“
118
+ 4. Send Discord Alert (if webhook configured)
119
+ โ†“
120
+ 5. Throw ZapperAPIError with proper status code
121
+ โ†“
122
+ 6. Cache layer: DON'T cache errors
123
+ โ†“
124
+ 7. Controller: Return error to user with HTTP status
125
+ ```
126
+
127
+ ### Cache Behavior
128
+ - โœ… **Success**: Cache for 24 hours
129
+ - โŒ **Error**: Don't cache, throw to user
130
+ - ๐Ÿ”„ **Stale**: Return cached + refresh in background
131
+
132
+ ---
133
+
134
+ ## ๐Ÿ“Š Benefits
135
+
136
+ 1. **No More Silent Failures**: Users see actual errors instead of empty portfolios
137
+ 2. **Proactive Monitoring**: Discord alerts notify you before users complain
138
+ 3. **No Stale Cache**: Errors aren't cached, so fixing API issues immediately reflects
139
+ 4. **Better Debugging**: Specific error codes and messages make troubleshooting easier
140
+ 5. **Production Ready**: Proper error handling for long-term reliability
141
+
142
+ ---
143
+
144
+ ## ๐Ÿšจ Common Issues & Solutions
145
+
146
+ ### Issue: Still seeing empty data
147
+ **Solution**: Clear cache and restart server
148
+ ```bash
149
+ redis-cli KEYS "zapper_v1:*" | xargs redis-cli DEL
150
+ lsof -ti:9001 | xargs kill -9 && make start
151
+ ```
152
+
153
+ ### Issue: Not receiving Discord alerts
154
+ **Solution**: Check webhook URL is correct in .env
155
+ ```bash
156
+ # Test webhook manually
157
+ curl -X POST "$DISCORD_WEBHOOK_ZAPPER_ALERTS" \
158
+ -H "Content-Type: application/json" \
159
+ -d '{"content": "Test alert from Zapper integration"}'
160
+ ```
161
+
162
+ ### Issue: Getting 402 errors
163
+ **Solution**: Add credits at https://build.zapper.xyz/dashboard
164
+
165
+ ---
166
+
167
+ ## ๐Ÿ“ Next Steps
168
+
169
+ 1. โœ… Module updated with error handling
170
+ 2. โœ… Documentation updated
171
+ 3. โณ **Add Discord webhook to .env**
172
+ 4. โณ **Restart server to load changes**
173
+ 5. โณ **Clear stale cache**
174
+ 6. โณ **Test with real address**
175
+ 7. โณ **Verify Discord alerts work**
176
+
177
+ ---
178
+
179
+ ## ๐ŸŽ“ Key Learnings
180
+
181
+ **Problem**: Zapper API failures were silently returning empty data (0, [], null), which got cached for 24 hours, making users think they had no assets.
182
+
183
+ **Root Cause**: Error handlers were catching exceptions but returning empty objects instead of re-throwing errors.
184
+
185
+ **Solution**: Proper error propagation with typed exceptions, Discord alerting, and cache-aware error handling.
186
+
187
+ **Impact**: System is now production-ready with proper observability and error handling.
package/lib/index.d.ts CHANGED
@@ -8,6 +8,23 @@ declare let API_KEY: string | undefined;
8
8
  declare const axios: any;
9
9
  declare const GRAPHQL_URL = "https://public.zapper.xyz/graphql";
10
10
  declare const REST_URL = "https://api.zapper.xyz";
11
+ declare const DISCORD_WEBHOOK_URL: string | undefined;
12
+ /**
13
+ * Custom error types for Zapper API failures
14
+ */
15
+ declare class ZapperAPIError extends Error {
16
+ code: string;
17
+ statusCode?: number | undefined;
18
+ constructor(message: string, code: string, statusCode?: number | undefined);
19
+ }
20
+ /**
21
+ * Send Discord alert for critical Zapper issues
22
+ */
23
+ declare const sendDiscordAlert: (message: string, severity: "error" | "warning" | "critical") => Promise<void>;
24
+ /**
25
+ * Handle Zapper API errors and throw appropriate exceptions
26
+ */
27
+ declare const handleZapperError: (error: any, operation: string) => Promise<never>;
11
28
  /**
12
29
  * Validates if a CAIP is correctly formatted for the given token
13
30
  */
package/lib/index.js CHANGED
@@ -3,6 +3,21 @@
3
3
  https://docs.blocknative.com/webhook-api
4
4
 
5
5
  */
6
+ var __extends = (this && this.__extends) || (function () {
7
+ var extendStatics = function (d, b) {
8
+ extendStatics = Object.setPrototypeOf ||
9
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
10
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
11
+ return extendStatics(d, b);
12
+ };
13
+ return function (d, b) {
14
+ if (typeof b !== "function" && b !== null)
15
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
16
+ extendStatics(d, b);
17
+ function __() { this.constructor = d; }
18
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
19
+ };
20
+ })();
6
21
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
22
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
23
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -53,6 +68,126 @@ var axios = Axios.create();
53
68
  // IMPORTANT: Zapper GraphQL API uses x-zapper-api-key header, not Authorization
54
69
  var GRAPHQL_URL = "https://public.zapper.xyz/graphql";
55
70
  var REST_URL = "https://api.zapper.xyz";
71
+ var DISCORD_WEBHOOK_URL = process.env['DISCORD_WEBHOOK_ZAPPER_ALERTS'];
72
+ /**
73
+ * Custom error types for Zapper API failures
74
+ */
75
+ var ZapperAPIError = /** @class */ (function (_super) {
76
+ __extends(ZapperAPIError, _super);
77
+ function ZapperAPIError(message, code, statusCode) {
78
+ var _this = _super.call(this, message) || this;
79
+ _this.code = code;
80
+ _this.statusCode = statusCode;
81
+ _this.name = 'ZapperAPIError';
82
+ return _this;
83
+ }
84
+ return ZapperAPIError;
85
+ }(Error));
86
+ /**
87
+ * Send Discord alert for critical Zapper issues
88
+ */
89
+ var sendDiscordAlert = function (message, severity) {
90
+ return __awaiter(this, void 0, void 0, function () {
91
+ var tag, colors, e_1;
92
+ return __generator(this, function (_a) {
93
+ switch (_a.label) {
94
+ case 0:
95
+ tag = TAG + " | sendDiscordAlert | ";
96
+ if (!DISCORD_WEBHOOK_URL) {
97
+ log.warn(tag, "Discord webhook not configured, skipping alert");
98
+ return [2 /*return*/];
99
+ }
100
+ colors = {
101
+ error: 15158332, // Red
102
+ warning: 16776960, // Yellow
103
+ critical: 10038562 // Dark red
104
+ };
105
+ _a.label = 1;
106
+ case 1:
107
+ _a.trys.push([1, 3, , 4]);
108
+ return [4 /*yield*/, Axios.post(DISCORD_WEBHOOK_URL, {
109
+ embeds: [{
110
+ title: "\uD83D\uDEA8 Zapper API ".concat(severity.toUpperCase()),
111
+ description: message,
112
+ color: colors[severity],
113
+ timestamp: new Date().toISOString(),
114
+ footer: {
115
+ text: 'Pioneer Zapper Integration'
116
+ }
117
+ }]
118
+ })];
119
+ case 2:
120
+ _a.sent();
121
+ log.info(tag, "Discord alert sent: ".concat(severity));
122
+ return [3 /*break*/, 4];
123
+ case 3:
124
+ e_1 = _a.sent();
125
+ log.error(tag, "Failed to send Discord alert:", e_1);
126
+ return [3 /*break*/, 4];
127
+ case 4: return [2 /*return*/];
128
+ }
129
+ });
130
+ });
131
+ };
132
+ /**
133
+ * Handle Zapper API errors and throw appropriate exceptions
134
+ */
135
+ var handleZapperError = function (error, operation) {
136
+ return __awaiter(this, void 0, void 0, function () {
137
+ var tag, errors, _i, errors_1, err, code, message, alertMessage;
138
+ var _a, _b, _c, _d, _e, _f;
139
+ return __generator(this, function (_g) {
140
+ switch (_g.label) {
141
+ case 0:
142
+ tag = TAG + " | handleZapperError | ";
143
+ if (!((_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.errors)) return [3 /*break*/, 9];
144
+ errors = error.response.data.errors;
145
+ _i = 0, errors_1 = errors;
146
+ _g.label = 1;
147
+ case 1:
148
+ if (!(_i < errors_1.length)) return [3 /*break*/, 9];
149
+ err = errors_1[_i];
150
+ code = (_c = err.extensions) === null || _c === void 0 ? void 0 : _c.code;
151
+ message = ((_d = err.extensions) === null || _d === void 0 ? void 0 : _d.message) || err.message;
152
+ if (!(code === 'PAYMENT_REQUIRED')) return [3 /*break*/, 3];
153
+ alertMessage = "**Zapper API Credits Exhausted**\n\n" +
154
+ "Operation: ".concat(operation, "\n") +
155
+ "Message: ".concat(message, "\n\n") +
156
+ "Action Required: Visit https://build.zapper.xyz/dashboard to add credits";
157
+ return [4 /*yield*/, sendDiscordAlert(alertMessage, 'critical')];
158
+ case 2:
159
+ _g.sent();
160
+ throw new ZapperAPIError('Zapper API credits exhausted. Please add credits at https://build.zapper.xyz/dashboard', 'PAYMENT_REQUIRED', 402);
161
+ case 3:
162
+ if (!(code === 'RATE_LIMITED' || err.message.includes('rate limit'))) return [3 /*break*/, 5];
163
+ return [4 /*yield*/, sendDiscordAlert("**Zapper API Rate Limited**\n\nOperation: ".concat(operation, "\nMessage: ").concat(message), 'warning')];
164
+ case 4:
165
+ _g.sent();
166
+ throw new ZapperAPIError('Zapper API rate limit exceeded. Please try again later.', 'RATE_LIMITED', 429);
167
+ case 5:
168
+ if (!(code === 'UNAUTHENTICATED' || message.includes('authentication'))) return [3 /*break*/, 7];
169
+ return [4 /*yield*/, sendDiscordAlert("**Zapper API Authentication Failed**\n\nOperation: ".concat(operation, "\nMessage: ").concat(message), 'error')];
170
+ case 6:
171
+ _g.sent();
172
+ throw new ZapperAPIError('Zapper API authentication failed. Please check API key.', 'UNAUTHENTICATED', 401);
173
+ case 7:
174
+ // Generic GraphQL error
175
+ throw new ZapperAPIError("Zapper API error: ".concat(message), code || 'UNKNOWN_ERROR', (_e = error.response) === null || _e === void 0 ? void 0 : _e.status);
176
+ case 8:
177
+ _i++;
178
+ return [3 /*break*/, 1];
179
+ case 9:
180
+ // Handle network/connection errors
181
+ if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
182
+ throw new ZapperAPIError('Failed to connect to Zapper API. Please check network connection.', 'NETWORK_ERROR');
183
+ }
184
+ // Generic error
185
+ log.error(tag, "Zapper API ".concat(operation, " failed:"), error);
186
+ throw new ZapperAPIError("Zapper API ".concat(operation, " failed: ").concat(error.message), 'API_ERROR', (_f = error.response) === null || _f === void 0 ? void 0 : _f.status);
187
+ }
188
+ });
189
+ });
190
+ };
56
191
  // Removed axios-retry - using basic axios
57
192
  /**
58
193
  * Validates if a CAIP is correctly formatted for the given token
@@ -181,16 +316,16 @@ module.exports = {
181
316
  };
182
317
  var get_portfolio = function (address) {
183
318
  return __awaiter(this, void 0, void 0, function () {
184
- var tag, output_1, appsGraphqlQuery, appsResponse, appsData, totalBalanceUSDApp, apps, _i, apps_1, appEdge, appNode, networkName, networkId, positions, _a, positions_1, posEdge, position, balance, tokenForCaip, tokens, _b, tokens_1, tokenWithMeta, token, balance, tokenForCaip, graphqlQuery, tokensResponse, totalBalanceUsdTokens_1, tokenData, tokens, nftGraphqlQuery, nftResponse, nftData, nftUsdNetWorth, allTokens, totalNetWorth, e_1;
319
+ var tag, output_1, appsGraphqlQuery, appsResponse, appsData, totalBalanceUSDApp, apps, _i, apps_1, appEdge, appNode, networkName, networkId, positions, _a, positions_1, posEdge, position, balance, tokenForCaip, tokens, _b, tokens_1, tokenWithMeta, token, balance, tokenForCaip, graphqlQuery, tokensResponse, totalBalanceUsdTokens_1, tokenData, tokens, nftGraphqlQuery, nftResponse, nftData, nftUsdNetWorth, allTokens, totalNetWorth, e_2;
185
320
  var _c;
186
- var _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9;
187
- return __generator(this, function (_10) {
188
- switch (_10.label) {
321
+ var _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11;
322
+ return __generator(this, function (_12) {
323
+ switch (_12.label) {
189
324
  case 0:
190
325
  tag = TAG + " | get_portfolio | ";
191
- _10.label = 1;
326
+ _12.label = 1;
192
327
  case 1:
193
- _10.trys.push([1, 5, , 6]);
328
+ _12.trys.push([1, 11, , 13]);
194
329
  output_1 = {
195
330
  balances: []
196
331
  };
@@ -207,39 +342,45 @@ var get_portfolio = function (address) {
207
342
  }
208
343
  })];
209
344
  case 2:
210
- appsResponse = _10.sent();
345
+ appsResponse = _12.sent();
346
+ if (!((_d = appsResponse.data) === null || _d === void 0 ? void 0 : _d.errors)) return [3 /*break*/, 4];
347
+ return [4 /*yield*/, handleZapperError({ response: appsResponse }, 'getPortfolio[apps]')];
348
+ case 3:
349
+ _12.sent();
350
+ return [2 /*return*/];
351
+ case 4:
211
352
  log.debug(tag, "GraphQL apps response:", appsResponse.data);
212
- appsData = (_f = (_e = (_d = appsResponse.data) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e.portfolioV2) === null || _f === void 0 ? void 0 : _f.appBalances;
353
+ appsData = (_g = (_f = (_e = appsResponse.data) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.portfolioV2) === null || _g === void 0 ? void 0 : _g.appBalances;
213
354
  totalBalanceUSDApp = (appsData === null || appsData === void 0 ? void 0 : appsData.totalBalanceUSD) || 0;
214
- apps = ((_g = appsData === null || appsData === void 0 ? void 0 : appsData.byApp) === null || _g === void 0 ? void 0 : _g.edges) || [];
355
+ apps = ((_h = appsData === null || appsData === void 0 ? void 0 : appsData.byApp) === null || _h === void 0 ? void 0 : _h.edges) || [];
215
356
  for (_i = 0, apps_1 = apps; _i < apps_1.length; _i++) {
216
357
  appEdge = apps_1[_i];
217
358
  appNode = appEdge.node;
218
359
  networkName = appNode.network.name.toLowerCase().replace(' ', '-');
219
- networkId = ((_h = evmCaips[networkName]) === null || _h === void 0 ? void 0 : _h.split('/')[0]) || ((_j = evmCaips[appNode.network.name.toLowerCase()]) === null || _j === void 0 ? void 0 : _j.split('/')[0]);
360
+ networkId = ((_j = evmCaips[networkName]) === null || _j === void 0 ? void 0 : _j.split('/')[0]) || ((_k = evmCaips[appNode.network.name.toLowerCase()]) === null || _k === void 0 ? void 0 : _k.split('/')[0]);
220
361
  if (!networkId) {
221
362
  log.warn(tag, "No CAIP found for network: ".concat(networkName));
222
363
  continue;
223
364
  }
224
- positions = ((_k = appNode.positionBalances) === null || _k === void 0 ? void 0 : _k.edges) || [];
365
+ positions = ((_l = appNode.positionBalances) === null || _l === void 0 ? void 0 : _l.edges) || [];
225
366
  for (_a = 0, positions_1 = positions; _a < positions_1.length; _a++) {
226
367
  posEdge = positions_1[_a];
227
368
  position = posEdge.node;
228
369
  if (position.type === 'app-token') {
229
370
  balance = {};
230
371
  balance.pubkey = address;
231
- balance.balance = ((_l = position.balance) === null || _l === void 0 ? void 0 : _l.toString()) || '0';
372
+ balance.balance = ((_m = position.balance) === null || _m === void 0 ? void 0 : _m.toString()) || '0';
232
373
  balance.chain = networkName;
233
374
  balance.networkId = networkId;
234
375
  balance.symbol = position.symbol;
235
376
  balance.ticker = position.symbol;
236
- balance.name = ((_m = position.displayProps) === null || _m === void 0 ? void 0 : _m.label) || position.symbol;
377
+ balance.name = ((_o = position.displayProps) === null || _o === void 0 ? void 0 : _o.label) || position.symbol;
237
378
  balance.appId = position.appId;
238
379
  balance.groupId = position.groupId;
239
- balance.icon = ((_p = (_o = position.displayProps) === null || _o === void 0 ? void 0 : _o.images) === null || _p === void 0 ? void 0 : _p[0]) || '';
240
- balance.display = ((_q = position.displayProps) === null || _q === void 0 ? void 0 : _q.images) || [];
241
- balance.priceUsd = ((_r = position.price) === null || _r === void 0 ? void 0 : _r.toString()) || '0';
242
- balance.valueUsd = ((_s = position.balanceUSD) === null || _s === void 0 ? void 0 : _s.toString()) || '0';
380
+ balance.icon = ((_q = (_p = position.displayProps) === null || _p === void 0 ? void 0 : _p.images) === null || _q === void 0 ? void 0 : _q[0]) || '';
381
+ balance.display = ((_r = position.displayProps) === null || _r === void 0 ? void 0 : _r.images) || [];
382
+ balance.priceUsd = ((_s = position.price) === null || _s === void 0 ? void 0 : _s.toString()) || '0';
383
+ balance.valueUsd = ((_t = position.balanceUSD) === null || _t === void 0 ? void 0 : _t.toString()) || '0';
243
384
  balance.tokenAddress = position.address;
244
385
  balance.tokenType = 'app-token';
245
386
  tokenForCaip = {
@@ -258,18 +399,18 @@ var get_portfolio = function (address) {
258
399
  if (token && token.balanceUSD > 0) {
259
400
  balance = {};
260
401
  balance.pubkey = address;
261
- balance.balance = ((_t = token.balance) === null || _t === void 0 ? void 0 : _t.toString()) || '0';
402
+ balance.balance = ((_u = token.balance) === null || _u === void 0 ? void 0 : _u.toString()) || '0';
262
403
  balance.chain = networkName;
263
404
  balance.networkId = networkId;
264
405
  balance.symbol = token.symbol;
265
406
  balance.ticker = token.symbol;
266
- balance.name = ((_u = position.displayProps) === null || _u === void 0 ? void 0 : _u.label) || token.symbol;
407
+ balance.name = ((_v = position.displayProps) === null || _v === void 0 ? void 0 : _v.label) || token.symbol;
267
408
  balance.appId = position.appId;
268
409
  balance.groupId = position.groupId;
269
- balance.icon = ((_w = (_v = position.displayProps) === null || _v === void 0 ? void 0 : _v.images) === null || _w === void 0 ? void 0 : _w[0]) || '';
270
- balance.display = ((_x = position.displayProps) === null || _x === void 0 ? void 0 : _x.images) || [];
271
- balance.priceUsd = ((_y = token.price) === null || _y === void 0 ? void 0 : _y.toString()) || '0';
272
- balance.valueUsd = ((_z = token.balanceUSD) === null || _z === void 0 ? void 0 : _z.toString()) || '0';
410
+ balance.icon = ((_x = (_w = position.displayProps) === null || _w === void 0 ? void 0 : _w.images) === null || _x === void 0 ? void 0 : _x[0]) || '';
411
+ balance.display = ((_y = position.displayProps) === null || _y === void 0 ? void 0 : _y.images) || [];
412
+ balance.priceUsd = ((_z = token.price) === null || _z === void 0 ? void 0 : _z.toString()) || '0';
413
+ balance.valueUsd = ((_0 = token.balanceUSD) === null || _0 === void 0 ? void 0 : _0.toString()) || '0';
273
414
  balance.tokenAddress = token.address;
274
415
  balance.tokenType = 'contract-position';
275
416
  balance.metaType = tokenWithMeta.metaType;
@@ -296,13 +437,19 @@ var get_portfolio = function (address) {
296
437
  'x-zapper-api-key': API_KEY,
297
438
  }
298
439
  })];
299
- case 3:
300
- tokensResponse = _10.sent();
440
+ case 5:
441
+ tokensResponse = _12.sent();
442
+ if (!((_1 = tokensResponse.data) === null || _1 === void 0 ? void 0 : _1.errors)) return [3 /*break*/, 7];
443
+ return [4 /*yield*/, handleZapperError({ response: tokensResponse }, 'getPortfolio[tokens]')];
444
+ case 6:
445
+ _12.sent();
446
+ return [2 /*return*/];
447
+ case 7:
301
448
  log.debug(tag, "GraphQL tokens response:", tokensResponse.data);
302
449
  totalBalanceUsdTokens_1 = 0;
303
- if ((_2 = (_1 = (_0 = tokensResponse.data) === null || _0 === void 0 ? void 0 : _0.data) === null || _1 === void 0 ? void 0 : _1.portfolioV2) === null || _2 === void 0 ? void 0 : _2.tokenBalances) {
450
+ if ((_4 = (_3 = (_2 = tokensResponse.data) === null || _2 === void 0 ? void 0 : _2.data) === null || _3 === void 0 ? void 0 : _3.portfolioV2) === null || _4 === void 0 ? void 0 : _4.tokenBalances) {
304
451
  tokenData = tokensResponse.data.data.portfolioV2.tokenBalances;
305
- tokens = ((_3 = tokenData.byToken) === null || _3 === void 0 ? void 0 : _3.edges) || [];
452
+ tokens = ((_5 = tokenData.byToken) === null || _5 === void 0 ? void 0 : _5.edges) || [];
306
453
  output_1.tokens = tokens.map(function (edge) { return edge.node; });
307
454
  log.info(tag, "tokens: ", tokens.length);
308
455
  if (tokens.length > 0) {
@@ -372,49 +519,47 @@ var get_portfolio = function (address) {
372
519
  'x-zapper-api-key': API_KEY,
373
520
  }
374
521
  })];
375
- case 4:
376
- nftResponse = _10.sent();
377
- nftData = (_6 = (_5 = (_4 = nftResponse.data) === null || _4 === void 0 ? void 0 : _4.data) === null || _5 === void 0 ? void 0 : _5.portfolioV2) === null || _6 === void 0 ? void 0 : _6.nftBalances;
522
+ case 8:
523
+ nftResponse = _12.sent();
524
+ if (!((_6 = nftResponse.data) === null || _6 === void 0 ? void 0 : _6.errors)) return [3 /*break*/, 10];
525
+ return [4 /*yield*/, handleZapperError({ response: nftResponse }, 'getPortfolio[nfts]')];
526
+ case 9:
527
+ _12.sent();
528
+ return [2 /*return*/];
529
+ case 10:
530
+ nftData = (_9 = (_8 = (_7 = nftResponse.data) === null || _7 === void 0 ? void 0 : _7.data) === null || _8 === void 0 ? void 0 : _8.portfolioV2) === null || _9 === void 0 ? void 0 : _9.nftBalances;
378
531
  nftUsdNetWorth = (nftData === null || nftData === void 0 ? void 0 : nftData.totalBalanceUSD) || 0;
379
- allTokens = ((_8 = (_7 = nftData === null || nftData === void 0 ? void 0 : nftData.byToken) === null || _7 === void 0 ? void 0 : _7.edges) === null || _8 === void 0 ? void 0 : _8.map(function (edge) { return edge.node.token; })) || [];
532
+ allTokens = ((_11 = (_10 = nftData === null || nftData === void 0 ? void 0 : nftData.byToken) === null || _10 === void 0 ? void 0 : _10.edges) === null || _11 === void 0 ? void 0 : _11.map(function (edge) { return edge.node.token; })) || [];
380
533
  output_1.nfts = allTokens;
381
534
  output_1.nftUsdNetWorth = (_c = {}, _c[address.toLowerCase()] = nftUsdNetWorth.toString(), _c);
382
535
  output_1.totalBalanceUsdTokens = totalBalanceUsdTokens_1;
383
536
  output_1.totalBalanceUSDApp = totalBalanceUSDApp;
384
- totalNetWorth = totalBalanceUSDApp + totalBalanceUsdTokens_1 + parseFloat(nftUsdNetWorth[address.toLowerCase()]);
537
+ totalNetWorth = totalBalanceUSDApp + totalBalanceUsdTokens_1 + nftUsdNetWorth;
385
538
  //console.log("totalNetWorth: ",totalNetWorth);
386
539
  output_1.totalNetWorth = totalNetWorth;
387
540
  return [2 /*return*/, output_1];
388
- case 5:
389
- e_1 = _10.sent();
390
- console.error(tag, "CRITICAL: Zapper API call failed:", e_1);
391
- console.error(tag, "Error details:", (_9 = e_1 === null || e_1 === void 0 ? void 0 : e_1.response) === null || _9 === void 0 ? void 0 : _9.status, e_1 === null || e_1 === void 0 ? void 0 : e_1.message);
392
- // Return empty result instead of undefined/null
393
- return [2 /*return*/, {
394
- balances: [],
395
- tokens: [],
396
- nfts: [],
397
- totalNetWorth: 0,
398
- totalBalanceUsdTokens: 0,
399
- totalBalanceUSDApp: 0,
400
- nftUsdNetWorth: {}
401
- }];
402
- case 6: return [2 /*return*/];
541
+ case 11:
542
+ e_2 = _12.sent();
543
+ return [4 /*yield*/, handleZapperError(e_2, 'getPortfolio')];
544
+ case 12:
545
+ _12.sent();
546
+ return [3 /*break*/, 13];
547
+ case 13: return [2 /*return*/];
403
548
  }
404
549
  });
405
550
  });
406
551
  };
407
552
  var get_total_networth = function (address) {
408
553
  return __awaiter(this, void 0, void 0, function () {
409
- var tag, graphqlQuery, portfolioResponse, portfolioData, totalBalanceUsdTokens, totalBalanceUSDApp, nftUsdNetWorth, totalNetWorth, e_2;
410
- var _a, _b, _c, _d, _e;
411
- return __generator(this, function (_f) {
412
- switch (_f.label) {
554
+ var tag, graphqlQuery, portfolioResponse, portfolioData, totalBalanceUsdTokens, totalBalanceUSDApp, nftUsdNetWorth, totalNetWorth, e_3;
555
+ var _a, _b, _c, _d, _e, _f;
556
+ return __generator(this, function (_g) {
557
+ switch (_g.label) {
413
558
  case 0:
414
559
  tag = TAG + " | get_total_networth | ";
415
- _f.label = 1;
560
+ _g.label = 1;
416
561
  case 1:
417
- _f.trys.push([1, 3, , 4]);
562
+ _g.trys.push([1, 5, , 7]);
418
563
  graphqlQuery = {
419
564
  query: "\n query PortfolioTotals($addresses: [Address!]!) {\n portfolioV2(addresses: $addresses) {\n tokenBalances {\n totalBalanceUSD\n }\n appBalances {\n totalBalanceUSD\n }\n nftBalances {\n totalBalanceUSD\n }\n }\n }\n ",
420
565
  variables: {
@@ -428,37 +573,45 @@ var get_total_networth = function (address) {
428
573
  }
429
574
  })];
430
575
  case 2:
431
- portfolioResponse = _f.sent();
432
- portfolioData = (_b = (_a = portfolioResponse.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.portfolioV2;
433
- totalBalanceUsdTokens = ((_c = portfolioData === null || portfolioData === void 0 ? void 0 : portfolioData.tokenBalances) === null || _c === void 0 ? void 0 : _c.totalBalanceUSD) || 0;
434
- totalBalanceUSDApp = ((_d = portfolioData === null || portfolioData === void 0 ? void 0 : portfolioData.appBalances) === null || _d === void 0 ? void 0 : _d.totalBalanceUSD) || 0;
435
- nftUsdNetWorth = ((_e = portfolioData === null || portfolioData === void 0 ? void 0 : portfolioData.nftBalances) === null || _e === void 0 ? void 0 : _e.totalBalanceUSD) || 0;
576
+ portfolioResponse = _g.sent();
577
+ if (!((_a = portfolioResponse.data) === null || _a === void 0 ? void 0 : _a.errors)) return [3 /*break*/, 4];
578
+ return [4 /*yield*/, handleZapperError({ response: portfolioResponse }, 'getTotalNetworth')];
579
+ case 3:
580
+ _g.sent();
581
+ return [2 /*return*/]; // TypeScript needs this even though handleZapperError throws
582
+ case 4:
583
+ portfolioData = (_c = (_b = portfolioResponse.data) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.portfolioV2;
584
+ totalBalanceUsdTokens = ((_d = portfolioData === null || portfolioData === void 0 ? void 0 : portfolioData.tokenBalances) === null || _d === void 0 ? void 0 : _d.totalBalanceUSD) || 0;
585
+ totalBalanceUSDApp = ((_e = portfolioData === null || portfolioData === void 0 ? void 0 : portfolioData.appBalances) === null || _e === void 0 ? void 0 : _e.totalBalanceUSD) || 0;
586
+ nftUsdNetWorth = ((_f = portfolioData === null || portfolioData === void 0 ? void 0 : portfolioData.nftBalances) === null || _f === void 0 ? void 0 : _f.totalBalanceUSD) || 0;
436
587
  log.debug(tag, "totalBalanceUsdTokens: ", totalBalanceUsdTokens);
437
588
  log.debug(tag, "totalBalanceUSDApp: ", totalBalanceUSDApp);
438
589
  log.debug(tag, "nftUsdNetWorth: ", nftUsdNetWorth);
439
590
  totalNetWorth = totalBalanceUSDApp + totalBalanceUsdTokens + nftUsdNetWorth;
440
591
  log.debug(tag, "totalNetWorth: ", totalNetWorth);
441
592
  return [2 /*return*/, totalNetWorth];
442
- case 3:
443
- e_2 = _f.sent();
444
- console.error(tag, "e: ", e_2);
445
- return [3 /*break*/, 4];
446
- case 4: return [2 /*return*/];
593
+ case 5:
594
+ e_3 = _g.sent();
595
+ return [4 /*yield*/, handleZapperError(e_3, 'getTotalNetworth')];
596
+ case 6:
597
+ _g.sent();
598
+ return [3 /*break*/, 7];
599
+ case 7: return [2 /*return*/];
447
600
  }
448
601
  });
449
602
  });
450
603
  };
451
604
  var get_tokens = function (address) {
452
605
  return __awaiter(this, void 0, void 0, function () {
453
- var tag, graphqlQuery, appsResponse, e_3;
454
- var _a, _b, _c, _d, _e;
455
- return __generator(this, function (_f) {
456
- switch (_f.label) {
606
+ var tag, graphqlQuery, appsResponse, e_4;
607
+ var _a, _b, _c, _d, _e, _f;
608
+ return __generator(this, function (_g) {
609
+ switch (_g.label) {
457
610
  case 0:
458
611
  tag = TAG + " | get_tokens | ";
459
- _f.label = 1;
612
+ _g.label = 1;
460
613
  case 1:
461
- _f.trys.push([1, 3, , 4]);
614
+ _g.trys.push([1, 5, , 7]);
462
615
  log.debug(tag, "Using API key:", (API_KEY === null || API_KEY === void 0 ? void 0 : API_KEY.substring(0, 10)) + "...");
463
616
  graphqlQuery = {
464
617
  query: "\n query AppBalances($addresses: [Address!]!) {\n portfolioV2(addresses: $addresses) {\n appBalances {\n byApp(first: 100) {\n edges {\n node {\n balanceUSD\n app {\n slug\n displayName\n }\n network {\n name\n }\n }\n }\n }\n }\n }\n }\n ",
@@ -473,28 +626,35 @@ var get_tokens = function (address) {
473
626
  }
474
627
  })];
475
628
  case 2:
476
- appsResponse = _f.sent();
477
- return [2 /*return*/, ((_e = (_d = (_c = (_b = (_a = appsResponse.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.portfolioV2) === null || _c === void 0 ? void 0 : _c.appBalances) === null || _d === void 0 ? void 0 : _d.byApp) === null || _e === void 0 ? void 0 : _e.edges) || []];
629
+ appsResponse = _g.sent();
630
+ if (!((_a = appsResponse.data) === null || _a === void 0 ? void 0 : _a.errors)) return [3 /*break*/, 4];
631
+ return [4 /*yield*/, handleZapperError({ response: appsResponse }, 'getTokens')];
478
632
  case 3:
479
- e_3 = _f.sent();
480
- console.error(tag, "e: ", e_3);
481
- return [3 /*break*/, 4];
482
- case 4: return [2 /*return*/];
633
+ _g.sent();
634
+ return [2 /*return*/];
635
+ case 4: return [2 /*return*/, ((_f = (_e = (_d = (_c = (_b = appsResponse.data) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.portfolioV2) === null || _d === void 0 ? void 0 : _d.appBalances) === null || _e === void 0 ? void 0 : _e.byApp) === null || _f === void 0 ? void 0 : _f.edges) || []];
636
+ case 5:
637
+ e_4 = _g.sent();
638
+ return [4 /*yield*/, handleZapperError(e_4, 'getTokens')];
639
+ case 6:
640
+ _g.sent();
641
+ return [3 /*break*/, 7];
642
+ case 7: return [2 /*return*/];
483
643
  }
484
644
  });
485
645
  });
486
646
  };
487
647
  var get_nfts = function (address) {
488
648
  return __awaiter(this, void 0, void 0, function () {
489
- var tag, graphqlQuery, result, nftData, tokens, e_4;
490
- var _a, _b, _c, _d, _e;
491
- return __generator(this, function (_f) {
492
- switch (_f.label) {
649
+ var tag, graphqlQuery, result, nftData, tokens, e_5;
650
+ var _a, _b, _c, _d, _e, _f;
651
+ return __generator(this, function (_g) {
652
+ switch (_g.label) {
493
653
  case 0:
494
654
  tag = TAG + " | get_nfts | ";
495
- _f.label = 1;
655
+ _g.label = 1;
496
656
  case 1:
497
- _f.trys.push([1, 3, , 4]);
657
+ _g.trys.push([1, 5, , 7]);
498
658
  graphqlQuery = {
499
659
  query: "\n query NFTBalances($addresses: [Address!]!) {\n portfolioV2(addresses: $addresses) {\n nftBalances {\n totalBalanceUSD\n totalTokensOwned\n byToken(first: 100) {\n edges {\n node {\n token {\n tokenId\n name\n description\n collection {\n address\n name\n network\n type\n }\n estimatedValue {\n valueUsd\n }\n mediasV3 {\n images {\n edges {\n node {\n originalUri\n thumbnail\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n ",
500
660
  variables: {
@@ -508,19 +668,27 @@ var get_nfts = function (address) {
508
668
  }
509
669
  })];
510
670
  case 2:
511
- result = _f.sent();
512
- nftData = (_c = (_b = (_a = result.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.portfolioV2) === null || _c === void 0 ? void 0 : _c.nftBalances;
513
- tokens = ((_e = (_d = nftData === null || nftData === void 0 ? void 0 : nftData.byToken) === null || _d === void 0 ? void 0 : _d.edges) === null || _e === void 0 ? void 0 : _e.map(function (edge) { return edge.node.token; })) || [];
671
+ result = _g.sent();
672
+ if (!((_a = result.data) === null || _a === void 0 ? void 0 : _a.errors)) return [3 /*break*/, 4];
673
+ return [4 /*yield*/, handleZapperError({ response: result }, 'getNFTs')];
674
+ case 3:
675
+ _g.sent();
676
+ return [2 /*return*/];
677
+ case 4:
678
+ nftData = (_d = (_c = (_b = result.data) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.portfolioV2) === null || _d === void 0 ? void 0 : _d.nftBalances;
679
+ tokens = ((_f = (_e = nftData === null || nftData === void 0 ? void 0 : nftData.byToken) === null || _e === void 0 ? void 0 : _e.edges) === null || _f === void 0 ? void 0 : _f.map(function (edge) { return edge.node.token; })) || [];
514
680
  return [2 /*return*/, {
515
681
  items: tokens,
516
682
  totalCount: (nftData === null || nftData === void 0 ? void 0 : nftData.totalTokensOwned) || 0,
517
683
  totalBalanceUSD: (nftData === null || nftData === void 0 ? void 0 : nftData.totalBalanceUSD) || 0
518
684
  }];
519
- case 3:
520
- e_4 = _f.sent();
521
- console.error(tag, "e: ", e_4);
522
- return [3 /*break*/, 4];
523
- case 4: return [2 /*return*/];
685
+ case 5:
686
+ e_5 = _g.sent();
687
+ return [4 /*yield*/, handleZapperError(e_5, 'getNFTs')];
688
+ case 6:
689
+ _g.sent();
690
+ return [3 /*break*/, 7];
691
+ case 7: return [2 /*return*/];
524
692
  }
525
693
  });
526
694
  });
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@pioneer-platform/zapper-client",
3
- "version": "8.16.0",
3
+ "version": "8.19.0",
4
4
  "main": "./lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "dependencies": {
7
7
  "@pioneer-platform/loggerdog": "^8.11.0",
8
- "@pioneer-platform/pioneer-caip": "^9.16.0",
8
+ "@pioneer-platform/pioneer-caip": "^9.19.0",
9
9
  "axios": "^1.6.0",
10
10
  "dotenv": "^8.2.0"
11
11
  },
package/test-api.js ADDED
@@ -0,0 +1,22 @@
1
+ require('dotenv').config({ path: '/Users/highlander/WebstormProjects/keepkey-stack/projects/pioneer/services/pioneer-server/.env' });
2
+ const zapper = require('./lib/index.js');
3
+
4
+ async function test() {
5
+ console.log('Testing Zapper API with key:', process.env.ZAPPER_API_KEY?.substring(0, 10) + '...');
6
+
7
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; // Vitalik
8
+ console.log('\nFetching portfolio for:', address);
9
+
10
+ try {
11
+ const result = await zapper.getTotalNetworth(address);
12
+ console.log('Total Net Worth:', result);
13
+ } catch (e) {
14
+ console.error('Error:', e.message);
15
+ if (e.response) {
16
+ console.error('API Response Status:', e.response.status);
17
+ console.error('API Response Data:', e.response.data);
18
+ }
19
+ }
20
+ }
21
+
22
+ test();
@@ -0,0 +1,33 @@
1
+ require('dotenv').config({ path: '../../../services/pioneer-server/.env' });
2
+ const zapper = require('./lib/index.js');
3
+
4
+ async function test() {
5
+ console.log('\n๐Ÿงช Testing Zapper Error Handling...\n');
6
+ console.log('API Key:', process.env.ZAPPER_API_KEY?.substring(0, 10) + '...\n');
7
+
8
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; // Vitalik
9
+
10
+ console.log('Testing getTotalNetworth()...');
11
+ try {
12
+ const result = await zapper.getTotalNetworth(address);
13
+ console.log('โœ… SUCCESS - Total Net Worth: $' + result.toLocaleString());
14
+ } catch (e) {
15
+ console.log('โŒ ERROR:', e.message);
16
+ console.log(' Code:', e.code);
17
+ console.log(' Status:', e.statusCode);
18
+ console.log(' Name:', e.name);
19
+ }
20
+
21
+ console.log('\nTesting getPortfolio()...');
22
+ try {
23
+ const portfolio = await zapper.getPortfolio(address);
24
+ console.log('โœ… SUCCESS - Balances:', portfolio.balances?.length || 0);
25
+ console.log(' Total Net Worth: $' + (portfolio.totalNetWorth || 0).toLocaleString());
26
+ } catch (e) {
27
+ console.log('โŒ ERROR:', e.message);
28
+ console.log(' Code:', e.code);
29
+ console.log(' Status:', e.statusCode);
30
+ }
31
+ }
32
+
33
+ test().catch(console.error);
@@ -0,0 +1,70 @@
1
+ require('dotenv').config({ path: '../../../services/pioneer-server/.env' });
2
+ const axios = require('axios');
3
+
4
+ async function testRawAPI() {
5
+ const API_KEY = process.env.ZAPPER_API_KEY;
6
+ console.log('\n๐Ÿงช Testing Raw Zapper GraphQL API...\n');
7
+ console.log('API Key:', API_KEY?.substring(0, 10) + '...\n');
8
+
9
+ const query = {
10
+ query: `
11
+ query PortfolioTotals($addresses: [Address!]!) {
12
+ portfolioV2(addresses: $addresses) {
13
+ tokenBalances {
14
+ totalBalanceUSD
15
+ }
16
+ appBalances {
17
+ totalBalanceUSD
18
+ }
19
+ nftBalances {
20
+ totalBalanceUSD
21
+ }
22
+ }
23
+ }
24
+ `,
25
+ variables: {
26
+ addresses: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045']
27
+ }
28
+ };
29
+
30
+ try {
31
+ const response = await axios.post(
32
+ 'https://public.zapper.xyz/graphql',
33
+ query,
34
+ {
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ 'x-zapper-api-key': API_KEY
38
+ }
39
+ }
40
+ );
41
+
42
+ console.log('๐Ÿ“ฅ RAW API RESPONSE:');
43
+ console.log(JSON.stringify(response.data, null, 2));
44
+
45
+ if (response.data.errors) {
46
+ console.log('\nโŒ ERRORS DETECTED:');
47
+ response.data.errors.forEach(err => {
48
+ console.log(' - Code:', err.extensions?.code);
49
+ console.log(' - Message:', err.extensions?.message || err.message);
50
+ });
51
+ }
52
+
53
+ if (response.data.data) {
54
+ console.log('\nโœ… DATA RECEIVED:');
55
+ const portfolio = response.data.data.portfolioV2;
56
+ console.log(' Tokens USD:', portfolio?.tokenBalances?.totalBalanceUSD);
57
+ console.log(' Apps USD:', portfolio?.appBalances?.totalBalanceUSD);
58
+ console.log(' NFTs USD:', portfolio?.nftBalances?.totalBalanceUSD);
59
+ }
60
+
61
+ } catch (e) {
62
+ console.log('โŒ REQUEST FAILED:', e.message);
63
+ if (e.response) {
64
+ console.log('Status:', e.response.status);
65
+ console.log('Data:', JSON.stringify(e.response.data, null, 2));
66
+ }
67
+ }
68
+ }
69
+
70
+ testRawAPI();