@coinmasters/e2e-staking-suite 1.11.4

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/index.js ADDED
@@ -0,0 +1,740 @@
1
+ "use strict";
2
+ /*
3
+ E2E Staking Test Suite
4
+
5
+ This test suite focuses on Cosmos staking operations:
6
+ - Delegate to validators
7
+ - Undelegate from validators
8
+ - Claim staking rewards
9
+ - Validator API testing
10
+
11
+ ============================================================================
12
+ ๐Ÿ”ง CONFIGURATION - Edit TEST_CONFIG object to enable/disable specific tests
13
+ ============================================================================
14
+
15
+ TEST_VALIDATOR_API: Test validator fetching APIs
16
+ TEST_STAKING_POSITIONS_API: Test staking positions detection
17
+ TEST_DIRECT_STAKING_API: Test direct staking API calls
18
+ TEST_CLAIM_REWARDS: Test reward claiming (๐ŸŽฏ RECOMMENDED TO START)
19
+ TEST_DELEGATE: Test delegation transactions
20
+ TEST_UNDELEGATE: Test undelegation transactions
21
+ TEST_STAKING_INTEGRATION: Test staking data integration
22
+ ACTUALLY_EXECUTE_TRANSACTIONS: Set to true to actually execute transactions
23
+ NETWORKS: Array of networks to test
24
+
25
+ Example for reward claiming only:
26
+ {
27
+ TEST_CLAIM_REWARDS: true,
28
+ TEST_DELEGATE: false,
29
+ TEST_UNDELEGATE: false,
30
+ ACTUALLY_EXECUTE_TRANSACTIONS: true, // To actually claim rewards
31
+ NETWORKS: ['GAIA'] // Cosmos Hub only
32
+ }
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ require("dotenv").config();
36
+ require('dotenv').config({ path: "../../.env" });
37
+ require('dotenv').config({ path: "./../../.env" });
38
+ require("dotenv").config({ path: '../../../.env' });
39
+ require("dotenv").config({ path: '../../../../.env' });
40
+ const TAG = " | staking-e2e-test | ";
41
+ // @ts-ignore
42
+ const pioneer_caip_1 = require("@pioneer-platform/pioneer-caip");
43
+ const types_1 = require("@coinmasters/types");
44
+ const log = require("@pioneer-platform/loggerdog")();
45
+ let assert = require('assert');
46
+ let SDK = require('@coinmasters/pioneer-sdk');
47
+ let wait = require('wait-promise');
48
+ let { ChainToNetworkId } = require('@pioneer-platform/pioneer-caip');
49
+ let sleep = wait.sleep;
50
+ const pioneer_coins_1 = require("@pioneer-platform/pioneer-coins");
51
+ const test_staking_service = async function () {
52
+ let tag = TAG + " | test_staking_service | ";
53
+ try {
54
+ console.time('staking-test-start');
55
+ // ๐ŸŽฏ Simple Configuration - Focus on reward claiming
56
+ const TEST_CONFIG = {
57
+ TEST_VALIDATOR_API: false,
58
+ TEST_STAKING_POSITIONS_API: true,
59
+ TEST_DIRECT_STAKING_API: true,
60
+ TEST_CLAIM_REWARDS: true,
61
+ TEST_DELEGATE: false,
62
+ TEST_UNDELEGATE: false,
63
+ TEST_STAKING_INTEGRATION: false,
64
+ ACTUALLY_EXECUTE_TRANSACTIONS: true,
65
+ NETWORKS: ['GAIA'] // Cosmos Hub only
66
+ };
67
+ log.info(tag, "๐Ÿ”ง Test Configuration:", TEST_CONFIG);
68
+ // Test configuration
69
+ const queryKey = "sdk:staking-test:" + Math.random();
70
+ log.info(tag, "queryKey: ", queryKey);
71
+ const username = "staking-user:" + Math.random();
72
+ assert(username);
73
+ // Use local or remote Pioneer API
74
+ let spec = process.env.PIONEER_SPEC || 'https://pioneers.dev/spec/swagger.json';
75
+ // let spec = 'http://127.0.0.1:9001/spec/swagger.json' // For local testing
76
+ // Focus on Cosmos chains that support staking
77
+ let stakingChains = TEST_CONFIG.NETWORKS;
78
+ const allByCaip = stakingChains.map(chainStr => {
79
+ const chain = (0, types_1.getChainEnumValue)(chainStr);
80
+ if (chain) {
81
+ return ChainToNetworkId[chain];
82
+ }
83
+ return;
84
+ }).filter(Boolean);
85
+ let blockchains = allByCaip;
86
+ log.info(tag, "Staking blockchains: ", blockchains);
87
+ // Get paths for staking chains
88
+ let paths = (0, pioneer_coins_1.getPaths)(blockchains);
89
+ log.info(tag, "Staking paths: ", paths.length);
90
+ let config = {
91
+ username,
92
+ queryKey,
93
+ spec,
94
+ wss: process.env.VITE_PIONEER_URL_WSS || 'wss://pioneers.dev',
95
+ keepkeyApiKey: process.env.KEEPKEY_API_KEY || 'e4ea6479-5ea4-4c7d-b824-e075101bf9fd',
96
+ keepkeyEndpoint: 'http://127.0.0.1:1647',
97
+ paths,
98
+ blockchains,
99
+ nodes: [],
100
+ pubkeys: [],
101
+ balances: [],
102
+ };
103
+ log.info(tag, "Initializing Pioneer SDK for staking tests...");
104
+ log.info(tag, "๐ŸŒ WebSocket URL:", config.wss);
105
+ log.info(tag, "๐Ÿ”‘ KeepKey API Key:", config.keepkeyApiKey ? 'configured' : 'missing');
106
+ log.info(tag, "๐Ÿ”Œ KeepKey Endpoint:", config.keepkeyEndpoint);
107
+ let app = new SDK.SDK(spec, config);
108
+ let resultInit = await app.init({}, {});
109
+ log.info(tag, "โœ… Pioneer SDK initialized");
110
+ // Manual step-by-step initialization like working test
111
+ log.info(tag, "๐Ÿ” Starting getGasAssets()...");
112
+ await app.getGasAssets();
113
+ log.info(tag, "โœ… getGasAssets() complete");
114
+ log.info(tag, "๐Ÿ” Starting getPubkeys()...");
115
+ await app.getPubkeys();
116
+ log.info(tag, "โœ… getPubkeys() complete - pubkeys count:", app.pubkeys.length);
117
+ log.info(tag, "๐Ÿ” Starting getBalances()...");
118
+ await app.getBalances();
119
+ log.info(tag, "โœ… getBalances() complete - balances count:", app.balances.length);
120
+ // Verify basic requirements
121
+ assert(app.blockchains, "Blockchains not initialized");
122
+ assert(app.pubkeys, "Pubkeys not initialized");
123
+ assert(app.balances, "Balances not initialized");
124
+ assert(app.pioneer, "Pioneer API not initialized");
125
+ // Debug: Check what we have after initialization
126
+ log.info(tag, "๐Ÿ“Š After initialization:");
127
+ log.info(tag, ` - Pubkeys: ${app.pubkeys.length}`);
128
+ log.info(tag, ` - Balances: ${app.balances.length}`);
129
+ log.info(tag, ` - KeepKey SDK: ${app.keepKeySdk ? 'initialized' : 'not initialized'}`);
130
+ if (app.pubkeys.length > 0) {
131
+ log.info(tag, "๐Ÿ”‘ Available pubkeys:");
132
+ app.pubkeys.forEach((pubkey, index) => {
133
+ log.info(tag, ` ${index + 1}. ${pubkey.address} (${pubkey.networks.join(', ')})`);
134
+ });
135
+ }
136
+ if (app.balances.length > 0) {
137
+ log.info(tag, "๐Ÿ’ฐ Available balances:");
138
+ app.balances.slice(0, 10).forEach((balance, index) => {
139
+ log.info(tag, ` ${index + 1}. ${balance.balance} ${balance.ticker} (${balance.type || 'normal'})`);
140
+ });
141
+ }
142
+ // **TEST 1: Validator API Testing**
143
+ if (TEST_CONFIG.TEST_VALIDATOR_API) {
144
+ log.info(tag, "");
145
+ log.info(tag, "=".repeat(60));
146
+ log.info(tag, "๐Ÿงช TEST 1: Validator API Testing");
147
+ log.info(tag, "=".repeat(60));
148
+ }
149
+ else {
150
+ log.info(tag, "โญ๏ธ Skipping TEST 1: Validator API Testing");
151
+ }
152
+ const validatorTests = new Map();
153
+ if (TEST_CONFIG.TEST_VALIDATOR_API) {
154
+ for (const networkId of blockchains) {
155
+ log.info(tag, `Testing validator API for ${networkId}...`);
156
+ try {
157
+ let network;
158
+ if (networkId === 'cosmos:cosmoshub-4') {
159
+ network = 'cosmos';
160
+ }
161
+ else if (networkId === 'cosmos:osmosis-1') {
162
+ network = 'osmosis';
163
+ }
164
+ else {
165
+ log.warn(tag, `Unsupported networkId for validator testing: ${networkId}`);
166
+ continue;
167
+ }
168
+ // Test GetValidators API
169
+ log.info(tag, `Fetching validators for ${network}...`);
170
+ const validatorsResponse = await app.pioneer.GetValidators({ network });
171
+ if (validatorsResponse && validatorsResponse.data && validatorsResponse.data.length > 0) {
172
+ const validators = validatorsResponse.data;
173
+ log.info(tag, `โœ… Found ${validators.length} validators for ${network}`);
174
+ // Validate validator structure
175
+ const firstValidator = validators[0];
176
+ assert(firstValidator.address, `Validator missing address`);
177
+ assert(firstValidator.moniker, `Validator missing moniker`);
178
+ assert(firstValidator.commission !== undefined, `Validator missing commission`);
179
+ assert(firstValidator.status, `Validator missing status`);
180
+ // Store for later use in delegation tests
181
+ validatorTests.set(networkId, validators.slice(0, 3)); // Keep top 3 validators
182
+ // Log sample validators
183
+ log.info(tag, `Sample validators for ${network}:`);
184
+ validators.slice(0, 3).forEach((validator, index) => {
185
+ log.info(tag, ` ${index + 1}. ${validator.moniker} - ${(parseFloat(validator.commission) * 100).toFixed(2)}% commission`);
186
+ });
187
+ }
188
+ else {
189
+ log.warn(tag, `โš ๏ธ No validators found for ${network}`);
190
+ }
191
+ }
192
+ catch (error) {
193
+ log.error(tag, `โŒ Error fetching validators for ${networkId}:`, error);
194
+ // Continue with other networks
195
+ }
196
+ }
197
+ }
198
+ // **TEST 2: Existing Staking Positions Detection**
199
+ if (TEST_CONFIG.TEST_STAKING_POSITIONS_API) {
200
+ log.info(tag, "");
201
+ log.info(tag, "=".repeat(60));
202
+ log.info(tag, "๐Ÿงช TEST 2: Existing Staking Positions Detection");
203
+ log.info(tag, "=".repeat(60));
204
+ }
205
+ else {
206
+ log.info(tag, "โญ๏ธ Skipping TEST 2: Existing Staking Positions Detection");
207
+ }
208
+ // Find existing staking positions
209
+ let existingStakingPositions = [];
210
+ if (TEST_CONFIG.TEST_STAKING_POSITIONS_API) {
211
+ existingStakingPositions = app.balances.filter((balance) => balance.chart === 'staking' || balance.type === 'delegation' || balance.type === 'reward' || balance.type === 'unbonding');
212
+ log.info(tag, `Found ${existingStakingPositions.length} existing staking positions`);
213
+ if (existingStakingPositions.length > 0) {
214
+ log.info(tag, "๐Ÿ“Š Existing staking positions:");
215
+ existingStakingPositions.forEach((position, index) => {
216
+ log.info(tag, ` ${index + 1}. ${position.type} - ${position.balance} ${position.ticker} (${position.validator || 'N/A'})`);
217
+ });
218
+ }
219
+ else {
220
+ log.info(tag, "โ„น๏ธ No existing staking positions found");
221
+ }
222
+ }
223
+ // **TEST 3: Direct Staking Position API Testing**
224
+ if (TEST_CONFIG.TEST_DIRECT_STAKING_API) {
225
+ log.info(tag, "");
226
+ log.info(tag, "=".repeat(60));
227
+ log.info(tag, "๐Ÿงช TEST 3: Direct Staking Position API Testing");
228
+ log.info(tag, "=".repeat(60));
229
+ }
230
+ else {
231
+ log.info(tag, "โญ๏ธ Skipping TEST 3: Direct Staking Position API Testing");
232
+ }
233
+ const stakingPositionsByNetwork = new Map();
234
+ if (TEST_CONFIG.TEST_DIRECT_STAKING_API) {
235
+ for (const networkId of blockchains) {
236
+ log.info(tag, `Testing staking positions API for ${networkId}...`);
237
+ // Find pubkeys for this network
238
+ const networkPubkeys = app.pubkeys.filter((pubkey) => pubkey.networks.includes(networkId));
239
+ if (networkPubkeys.length === 0) {
240
+ log.warn(tag, `No pubkeys found for ${networkId}`);
241
+ continue;
242
+ }
243
+ try {
244
+ let network;
245
+ if (networkId === 'cosmos:cosmoshub-4') {
246
+ network = 'cosmos';
247
+ }
248
+ else if (networkId === 'cosmos:osmosis-1') {
249
+ network = 'osmosis';
250
+ }
251
+ else {
252
+ log.warn(tag, `Unsupported networkId for staking positions: ${networkId}`);
253
+ continue;
254
+ }
255
+ for (const pubkey of networkPubkeys) {
256
+ log.info(tag, `Checking staking positions for ${pubkey.address} on ${network}...`);
257
+ const stakingResponse = await app.pioneer.GetStakingPositions({
258
+ network: network,
259
+ address: pubkey.address
260
+ });
261
+ if (stakingResponse && stakingResponse.data && stakingResponse.data.length > 0) {
262
+ const positions = stakingResponse.data;
263
+ log.info(tag, `โœ… Found ${positions.length} staking positions for ${pubkey.address}`);
264
+ // Store positions for later use
265
+ stakingPositionsByNetwork.set(networkId, positions);
266
+ // Also add to existingStakingPositions for reward claiming
267
+ positions.forEach((position) => {
268
+ // Add missing fields for compatibility
269
+ if (!position.networkId)
270
+ position.networkId = networkId;
271
+ if (!position.caip)
272
+ position.caip = (0, pioneer_caip_1.networkIdToCaip)(networkId) || networkId;
273
+ existingStakingPositions.push(position);
274
+ });
275
+ // Log position details
276
+ positions.forEach((position, index) => {
277
+ log.info(tag, ` ${index + 1}. ${position.type}: ${position.balance} ${position.ticker} (${position.validator})`);
278
+ });
279
+ // Debug: Log the actual API response structure
280
+ log.info(tag, `Raw API response for ${network}:`, JSON.stringify(positions, null, 2));
281
+ // Validate position structure
282
+ positions.forEach((position, index) => {
283
+ log.info(tag, `Position ${index + 1} raw data:`, position);
284
+ assert(position.type, 'Position must have type');
285
+ assert(position.balance, 'Position must have balance');
286
+ assert(position.ticker, 'Position must have ticker');
287
+ if (position.type === 'delegation') {
288
+ if (!position.validatorAddress) {
289
+ log.error(tag, `โŒ CRITICAL: Delegation position missing validatorAddress`);
290
+ log.error(tag, `Position data:`, position);
291
+ throw new Error(`API response missing validatorAddress for delegation position. This is required for transactions.`);
292
+ }
293
+ }
294
+ if (position.type === 'reward') {
295
+ if (!position.validatorAddress) {
296
+ log.error(tag, `โŒ CRITICAL: Reward position missing validatorAddress`);
297
+ log.error(tag, `Position data:`, position);
298
+ throw new Error(`API response missing validatorAddress for reward position. This is required for claiming rewards.`);
299
+ }
300
+ }
301
+ });
302
+ break; // Found positions for this network, no need to check other pubkeys
303
+ }
304
+ else {
305
+ log.info(tag, `โ„น๏ธ No staking positions found for ${pubkey.address} on ${network}`);
306
+ }
307
+ }
308
+ }
309
+ catch (error) {
310
+ log.error(tag, `โŒ Error fetching staking positions for ${networkId}:`, error);
311
+ // Continue with other networks
312
+ }
313
+ }
314
+ }
315
+ // **TEST 4: Undelegation Flow Testing**
316
+ if (TEST_CONFIG.TEST_UNDELEGATE) {
317
+ log.info(tag, "");
318
+ log.info(tag, "=".repeat(60));
319
+ log.info(tag, "๐Ÿงช TEST 4: Undelegation Flow Testing");
320
+ log.info(tag, "=".repeat(60));
321
+ }
322
+ else {
323
+ log.info(tag, "โญ๏ธ Skipping TEST 4: Undelegation Flow Testing");
324
+ }
325
+ if (TEST_CONFIG.TEST_UNDELEGATE) {
326
+ // Find delegation positions to undelegate from
327
+ const delegationPositions = existingStakingPositions.filter((position) => position.type === 'delegation' && parseFloat(position.balance) > 0);
328
+ if (delegationPositions.length > 0) {
329
+ log.info(tag, `Found ${delegationPositions.length} delegation positions available for undelegation`);
330
+ // Test undelegation with the first delegation position
331
+ const targetDelegation = delegationPositions[0];
332
+ log.info(tag, `๐ŸŽฏ Testing undelegation from: ${targetDelegation.validator}`);
333
+ log.info(tag, `๐Ÿ“Š Current delegation: ${targetDelegation.balance} ${targetDelegation.ticker}`);
334
+ const networkId = targetDelegation.networkId || (0, pioneer_caip_1.caipToNetworkId)(targetDelegation.caip);
335
+ const caip = targetDelegation.caip;
336
+ // Set asset context for undelegation
337
+ await app.setAssetContext({ caip });
338
+ // Calculate small undelegation amount (10% of current delegation)
339
+ const currentBalance = parseFloat(targetDelegation.balance);
340
+ const undelegateAmount = Math.max(0.01, currentBalance * 0.1); // At least 0.01 or 10%
341
+ log.info(tag, `๐Ÿ“‰ Attempting to undelegate ${undelegateAmount} ${targetDelegation.ticker}...`);
342
+ try {
343
+ // Build undelegation transaction
344
+ const undelegatePayload = {
345
+ validatorAddress: targetDelegation.validatorAddress || targetDelegation.validator,
346
+ amount: undelegateAmount,
347
+ memo: 'E2E Undelegation Test'
348
+ };
349
+ log.info(tag, `๐Ÿ”จ Building undelegation transaction...`);
350
+ log.info(tag, `๐Ÿ“ค Payload:`, undelegatePayload);
351
+ // Test actual undelegation transaction building
352
+ try {
353
+ const unsignedTx = await app.buildUndelegateTx(caip, undelegatePayload);
354
+ log.info(tag, `โœ… Undelegation transaction built successfully`);
355
+ log.info(tag, `๐Ÿ“‹ Transaction structure:`, JSON.stringify(unsignedTx, null, 2));
356
+ // Validate transaction structure
357
+ assert(unsignedTx.signDoc, 'Transaction must have signDoc');
358
+ assert(unsignedTx.signDoc.msgs, 'Transaction must have messages');
359
+ assert(unsignedTx.signDoc.msgs[0].type === 'cosmos-sdk/MsgUndelegate', 'Must be MsgUndelegate');
360
+ assert(unsignedTx.signDoc.msgs[0].value.delegator_address, 'Must have delegator_address');
361
+ assert(unsignedTx.signDoc.msgs[0].value.validator_address, 'Must have validator_address');
362
+ assert(unsignedTx.signDoc.msgs[0].value.amount, 'Must have amount');
363
+ log.info(tag, `โœ… Undelegation transaction structure validated`);
364
+ if (TEST_CONFIG.ACTUALLY_EXECUTE_TRANSACTIONS) {
365
+ log.info(tag, `๐Ÿš€ EXECUTING UNDELEGATION TRANSACTION...`);
366
+ log.info(tag, `โš ๏ธ This will start unbonding period (21 days for Cosmos)`);
367
+ // Sign the transaction
368
+ log.info(tag, `โœ๏ธ Signing transaction...`);
369
+ const signedTx = await app.signTx({ caip, unsignedTx });
370
+ log.info(tag, `โœ… Transaction signed successfully`);
371
+ // Broadcast the transaction
372
+ log.info(tag, `๐Ÿ“ก Broadcasting transaction...`);
373
+ const broadcast = await app.broadcastTx(caip, signedTx);
374
+ log.info(tag, `โœ… Transaction broadcasted:`, broadcast);
375
+ // Follow the transaction
376
+ log.info(tag, `๐Ÿ‘€ Following transaction...`);
377
+ const followResult = await app.followTransaction(caip, broadcast);
378
+ log.info(tag, `โœ… Transaction completed:`, followResult);
379
+ }
380
+ else {
381
+ log.info(tag, `โ„น๏ธ Skipping execution - set ACTUALLY_EXECUTE_TRANSACTIONS to true to execute`);
382
+ }
383
+ }
384
+ catch (buildError) {
385
+ log.error(tag, `โŒ Error building undelegation transaction:`, buildError);
386
+ // Continue with test - this is expected if no actual delegation exists
387
+ }
388
+ }
389
+ catch (error) {
390
+ log.error(tag, `โŒ Error in undelegation flow:`, error);
391
+ }
392
+ }
393
+ else {
394
+ log.info(tag, `โ„น๏ธ No delegation positions found for undelegation testing`);
395
+ }
396
+ }
397
+ // **TEST 5: Delegation Flow Testing**
398
+ if (TEST_CONFIG.TEST_DELEGATE) {
399
+ log.info(tag, "");
400
+ log.info(tag, "=".repeat(60));
401
+ log.info(tag, "๐Ÿงช TEST 5: Delegation Flow Testing");
402
+ log.info(tag, "=".repeat(60));
403
+ }
404
+ else {
405
+ log.info(tag, "โญ๏ธ Skipping TEST 5: Delegation Flow Testing");
406
+ }
407
+ if (TEST_CONFIG.TEST_DELEGATE) {
408
+ // Test delegation for each network with available balance
409
+ for (const networkId of blockchains) {
410
+ log.info(tag, `Testing delegation flow for ${networkId}...`);
411
+ const caip = (0, pioneer_caip_1.networkIdToCaip)(networkId);
412
+ if (!caip) {
413
+ log.warn(tag, `Could not convert networkId to CAIP: ${networkId}`);
414
+ continue;
415
+ }
416
+ // Find available balance for delegation
417
+ const availableBalance = app.balances.find((balance) => balance.caip === caip && balance.chart !== 'staking');
418
+ if (!availableBalance || parseFloat(availableBalance.balance) <= 0) {
419
+ log.info(tag, `โ„น๏ธ No available balance for delegation on ${networkId}`);
420
+ continue;
421
+ }
422
+ // Get validators for this network
423
+ const validators = validatorTests.get(networkId);
424
+ if (!validators || validators.length === 0) {
425
+ log.info(tag, `โ„น๏ธ No validators available for delegation on ${networkId}`);
426
+ continue;
427
+ }
428
+ // Set asset context
429
+ await app.setAssetContext({ caip });
430
+ // Calculate small delegation amount
431
+ const currentBalance = parseFloat(availableBalance.balance);
432
+ const delegateAmount = Math.min(0.1, currentBalance * 0.1); // Max 0.1 or 10% of balance
433
+ if (delegateAmount < 0.01) {
434
+ log.info(tag, `โ„น๏ธ Balance too low for delegation test on ${networkId}`);
435
+ continue;
436
+ }
437
+ // Select first validator for delegation
438
+ const targetValidator = validators[0];
439
+ log.info(tag, `๐ŸŽฏ Testing delegation to: ${targetValidator.moniker}`);
440
+ log.info(tag, `๐Ÿ’ฐ Available balance: ${currentBalance} ${availableBalance.ticker}`);
441
+ log.info(tag, `๐Ÿ“ˆ Delegation amount: ${delegateAmount} ${availableBalance.ticker}`);
442
+ try {
443
+ // Build delegation transaction payload
444
+ const delegatePayload = {
445
+ validatorAddress: targetValidator.address,
446
+ amount: delegateAmount,
447
+ memo: 'E2E Delegation Test'
448
+ };
449
+ log.info(tag, `๐Ÿ”จ Building delegation transaction...`);
450
+ log.info(tag, `๐Ÿ“ค Payload:`, delegatePayload);
451
+ // Test actual delegation transaction building
452
+ try {
453
+ const unsignedTx = await app.buildDelegateTx(caip, delegatePayload);
454
+ log.info(tag, `โœ… Delegation transaction built successfully`);
455
+ log.info(tag, `๐Ÿ“‹ Transaction structure:`, JSON.stringify(unsignedTx, null, 2));
456
+ // Validate transaction structure
457
+ assert(unsignedTx.signDoc, 'Transaction must have signDoc');
458
+ assert(unsignedTx.signDoc.msgs, 'Transaction must have messages');
459
+ assert(unsignedTx.signDoc.msgs[0].type === 'cosmos-sdk/MsgDelegate', 'Must be MsgDelegate');
460
+ assert(unsignedTx.signDoc.msgs[0].value.delegator_address, 'Must have delegator_address');
461
+ assert(unsignedTx.signDoc.msgs[0].value.validator_address, 'Must have validator_address');
462
+ assert(unsignedTx.signDoc.msgs[0].value.amount, 'Must have amount');
463
+ log.info(tag, `โœ… Delegation transaction structure validated`);
464
+ if (TEST_CONFIG.ACTUALLY_EXECUTE_TRANSACTIONS) {
465
+ log.info(tag, `๐Ÿš€ EXECUTING DELEGATION TRANSACTION...`);
466
+ // Sign the transaction
467
+ log.info(tag, `โœ๏ธ Signing transaction...`);
468
+ const signedTx = await app.signTx({ caip, unsignedTx });
469
+ log.info(tag, `โœ… Transaction signed successfully`);
470
+ // Broadcast the transaction
471
+ log.info(tag, `๐Ÿ“ก Broadcasting transaction...`);
472
+ const broadcast = await app.broadcastTx(caip, signedTx);
473
+ log.info(tag, `โœ… Transaction broadcasted:`, broadcast);
474
+ // Follow the transaction
475
+ log.info(tag, `๐Ÿ‘€ Following transaction...`);
476
+ const followResult = await app.followTransaction(caip, broadcast);
477
+ log.info(tag, `โœ… Transaction completed:`, followResult);
478
+ }
479
+ else {
480
+ log.info(tag, `โ„น๏ธ Skipping execution - set ACTUALLY_EXECUTE_TRANSACTIONS to true to execute`);
481
+ }
482
+ }
483
+ catch (buildError) {
484
+ log.error(tag, `โŒ Error building delegation transaction:`, buildError);
485
+ // Continue with test - this is expected if no balance available
486
+ }
487
+ }
488
+ catch (error) {
489
+ log.error(tag, `โŒ Error in delegation flow:`, error);
490
+ }
491
+ }
492
+ }
493
+ // **TEST 6: Reward Claiming Testing**
494
+ if (TEST_CONFIG.TEST_CLAIM_REWARDS) {
495
+ log.info(tag, "");
496
+ log.info(tag, "=".repeat(60));
497
+ log.info(tag, "๐Ÿงช TEST 6: Reward Claiming Testing");
498
+ log.info(tag, "=".repeat(60));
499
+ }
500
+ else {
501
+ log.info(tag, "โญ๏ธ Skipping TEST 6: Reward Claiming Testing");
502
+ }
503
+ if (TEST_CONFIG.TEST_CLAIM_REWARDS) {
504
+ // Find reward positions
505
+ const rewardPositions = existingStakingPositions.filter((position) => position.type === 'reward' && parseFloat(position.balance) > 0);
506
+ if (rewardPositions.length > 0) {
507
+ log.info(tag, `Found ${rewardPositions.length} reward positions available for claiming`);
508
+ rewardPositions.forEach((reward, index) => {
509
+ log.info(tag, ` ${index + 1}. ${reward.balance} ${reward.ticker} from ${reward.validatorAddress}`);
510
+ });
511
+ // Test reward claiming with the first reward position
512
+ const targetReward = rewardPositions[0];
513
+ const networkId = targetReward.networkId || (0, pioneer_caip_1.caipToNetworkId)(targetReward.caip);
514
+ const caip = targetReward.caip;
515
+ log.info(tag, `๐ŸŽฏ Testing reward claiming for: ${targetReward.validatorAddress}`);
516
+ log.info(tag, `๐Ÿ’ฐ Available rewards: ${targetReward.balance} ${targetReward.ticker}`);
517
+ try {
518
+ // Build reward claiming transaction payload
519
+ const claimPayload = {
520
+ validatorAddress: targetReward.validatorAddress,
521
+ memo: 'E2E Reward Claim Test'
522
+ };
523
+ log.info(tag, `๐Ÿ”จ Building reward claim transaction...`);
524
+ log.info(tag, `๐Ÿ“ค Payload:`, claimPayload);
525
+ // Test actual reward claiming transaction building
526
+ try {
527
+ const unsignedTx = await app.buildClaimRewardsTx(caip, claimPayload);
528
+ log.info(tag, `โœ… Reward claim transaction built successfully`);
529
+ log.info(tag, `๐Ÿ“‹ Transaction structure:`, JSON.stringify(unsignedTx, null, 2));
530
+ // Validate transaction structure
531
+ assert(unsignedTx.signDoc, 'Transaction must have signDoc');
532
+ assert(unsignedTx.signDoc.msgs, 'Transaction must have messages');
533
+ assert(unsignedTx.signDoc.msgs[0].type === 'cosmos-sdk/MsgWithdrawDelegationReward', 'Must be MsgWithdrawDelegationReward');
534
+ assert(unsignedTx.signDoc.msgs[0].value.delegator_address, 'Must have delegator_address');
535
+ assert(unsignedTx.signDoc.msgs[0].value.validator_address, 'Must have validator_address');
536
+ log.info(tag, `โœ… Reward claim transaction structure validated`);
537
+ if (TEST_CONFIG.ACTUALLY_EXECUTE_TRANSACTIONS) {
538
+ log.info(tag, `๐Ÿš€ EXECUTING REWARD CLAIM TRANSACTION...`);
539
+ // Sign the transaction
540
+ log.info(tag, `โœ๏ธ Signing transaction...`);
541
+ log.info(tag, `๐Ÿ“‹ Signing with CAIP: ${caip}`);
542
+ log.info(tag, `๐Ÿ“‹ Signing with unsignedTx keys: ${Object.keys(unsignedTx)}`);
543
+ const signedTx = await app.signTx({ caip, unsignedTx });
544
+ log.info(tag, `โœ… Transaction signed successfully`);
545
+ // Broadcast the transaction
546
+ log.info(tag, `๐Ÿ“ก Broadcasting transaction...`);
547
+ const broadcast = await app.broadcastTx(caip, signedTx);
548
+ log.info(tag, `โœ… Transaction broadcasted:`, broadcast);
549
+ // Follow the transaction
550
+ log.info(tag, `๐Ÿ‘€ Following transaction...`);
551
+ const followResult = await app.followTransaction(caip, broadcast);
552
+ log.info(tag, `โœ… Transaction completed:`, followResult);
553
+ }
554
+ else {
555
+ log.info(tag, `โ„น๏ธ Skipping execution - set ACTUALLY_EXECUTE_TRANSACTIONS to true to execute`);
556
+ }
557
+ }
558
+ catch (buildError) {
559
+ log.error(tag, `โŒ Error building reward claim transaction:`, buildError);
560
+ // Continue with test - this is expected if no rewards available
561
+ }
562
+ }
563
+ catch (error) {
564
+ log.error(tag, `โŒ Error in reward claiming flow:`, error);
565
+ }
566
+ }
567
+ else {
568
+ log.info(tag, `โ„น๏ธ No reward positions found for claiming testing`);
569
+ // Try to find delegation positions that might have rewards
570
+ const delegationPositions = existingStakingPositions.filter((position) => position.type === 'delegation' && parseFloat(position.balance) > 0);
571
+ if (delegationPositions.length > 0) {
572
+ log.info(tag, `๐Ÿ’ก Found ${delegationPositions.length} delegation positions that might have rewards:`);
573
+ delegationPositions.forEach((delegation, index) => {
574
+ log.info(tag, ` ${index + 1}. ${delegation.balance} ${delegation.ticker} delegated to ${delegation.validatorAddress}`);
575
+ });
576
+ // Test claiming ALL rewards from all delegations
577
+ const validatorAddresses = delegationPositions.map((delegation) => delegation.validatorAddress);
578
+ const caip = delegationPositions[0].caip;
579
+ log.info(tag, `๐ŸŽฏ Testing CLAIM ALL REWARDS from ${validatorAddresses.length} validators`);
580
+ try {
581
+ const claimAllPayload = {
582
+ validatorAddresses: validatorAddresses,
583
+ memo: 'E2E Claim All Rewards Test'
584
+ };
585
+ log.info(tag, `๐Ÿ”จ Building claim all rewards transaction...`);
586
+ log.info(tag, `๐Ÿ“ค Payload:`, claimAllPayload);
587
+ const unsignedTx = await app.buildClaimAllRewardsTx(caip, claimAllPayload);
588
+ log.info(tag, `โœ… Claim all rewards transaction built successfully`);
589
+ log.info(tag, `๐Ÿ“‹ Transaction structure:`, JSON.stringify(unsignedTx, null, 2));
590
+ // Validate transaction structure
591
+ assert(unsignedTx.signDoc, 'Transaction must have signDoc');
592
+ assert(unsignedTx.signDoc.msgs, 'Transaction must have messages');
593
+ assert(unsignedTx.signDoc.msgs.length === validatorAddresses.length, `Must have ${validatorAddresses.length} messages`);
594
+ // Validate each message
595
+ unsignedTx.signDoc.msgs.forEach((msg, index) => {
596
+ assert(msg.type === 'cosmos-sdk/MsgWithdrawDelegationReward', `Message ${index} must be MsgWithdrawDelegationReward`);
597
+ assert(msg.value.delegator_address, `Message ${index} must have delegator_address`);
598
+ assert(msg.value.validator_address, `Message ${index} must have validator_address`);
599
+ });
600
+ log.info(tag, `โœ… Claim all rewards transaction structure validated`);
601
+ if (TEST_CONFIG.ACTUALLY_EXECUTE_TRANSACTIONS) {
602
+ log.info(tag, `๐Ÿš€ EXECUTING CLAIM ALL REWARDS TRANSACTION...`);
603
+ log.info(tag, `๐Ÿ’ฐ Claiming rewards from ${validatorAddresses.length} validators`);
604
+ // Sign the transaction
605
+ log.info(tag, `โœ๏ธ Signing transaction...`);
606
+ const signedTx = await app.signTx({ caip, unsignedTx });
607
+ log.info(tag, `โœ… Transaction signed successfully`);
608
+ // Broadcast the transaction
609
+ log.info(tag, `๐Ÿ“ก Broadcasting transaction...`);
610
+ const broadcast = await app.broadcastTx(caip, signedTx);
611
+ log.info(tag, `โœ… Transaction broadcasted:`, broadcast);
612
+ // Follow the transaction
613
+ log.info(tag, `๐Ÿ‘€ Following transaction...`);
614
+ const followResult = await app.followTransaction(caip, broadcast);
615
+ log.info(tag, `โœ… Transaction completed:`, followResult);
616
+ }
617
+ else {
618
+ log.info(tag, `โ„น๏ธ Skipping execution - set ACTUALLY_EXECUTE_TRANSACTIONS to true to execute`);
619
+ }
620
+ }
621
+ catch (buildError) {
622
+ log.error(tag, `โŒ Error building claim all rewards transaction:`, buildError);
623
+ }
624
+ }
625
+ }
626
+ }
627
+ // **TEST 7: Staking Integration Validation**
628
+ if (TEST_CONFIG.TEST_STAKING_INTEGRATION) {
629
+ log.info(tag, "");
630
+ log.info(tag, "=".repeat(60));
631
+ log.info(tag, "๐Ÿงช TEST 7: Staking Integration Validation");
632
+ log.info(tag, "=".repeat(60));
633
+ }
634
+ else {
635
+ log.info(tag, "โญ๏ธ Skipping TEST 7: Staking Integration Validation");
636
+ }
637
+ if (TEST_CONFIG.TEST_STAKING_INTEGRATION) {
638
+ // Validate that staking positions are properly integrated into the app
639
+ const allStakingInBalances = app.balances.filter((balance) => balance.chart === 'staking' || ['delegation', 'reward', 'unbonding'].includes(balance.type));
640
+ log.info(tag, `Total staking positions in app.balances: ${allStakingInBalances.length}`);
641
+ if (allStakingInBalances.length > 0) {
642
+ const delegations = allStakingInBalances.filter((p) => p.type === 'delegation');
643
+ const rewards = allStakingInBalances.filter((p) => p.type === 'reward');
644
+ const unbonding = allStakingInBalances.filter((p) => p.type === 'unbonding');
645
+ log.info(tag, `๐Ÿ“Š Breakdown:`);
646
+ log.info(tag, ` - Delegations: ${delegations.length}`);
647
+ log.info(tag, ` - Rewards: ${rewards.length}`);
648
+ log.info(tag, ` - Unbonding: ${unbonding.length}`);
649
+ // Validate pricing integration
650
+ const stakingWithPricing = allStakingInBalances.filter((p) => p.priceUsd && p.priceUsd > 0 && p.valueUsd && p.valueUsd > 0);
651
+ log.info(tag, `๐Ÿ’ฐ Positions with pricing: ${stakingWithPricing.length}/${allStakingInBalances.length}`);
652
+ // Calculate total staking value
653
+ const totalStakingValue = allStakingInBalances.reduce((sum, position) => sum + (parseFloat(position.valueUsd) || 0), 0);
654
+ log.info(tag, `๐Ÿ’ต Total staking value: $${totalStakingValue.toFixed(2)}`);
655
+ }
656
+ }
657
+ // **TEST RESULTS VALIDATION**
658
+ log.info(tag, "");
659
+ log.info(tag, "=".repeat(60));
660
+ log.info(tag, "๐Ÿ STAKING TEST SUITE RESULTS");
661
+ log.info(tag, "=".repeat(60));
662
+ console.timeEnd('staking-test-start');
663
+ // Check critical requirements for test success
664
+ let testsPassed = 0;
665
+ let testsTotal = 0;
666
+ let criticalFailures = [];
667
+ // CRITICAL: KeepKey device connection
668
+ testsTotal++;
669
+ if (app.pubkeys.length > 0) {
670
+ log.info(tag, `โœ… KeepKey device connected: ${app.pubkeys.length} pubkeys loaded`);
671
+ testsPassed++;
672
+ }
673
+ else {
674
+ log.error(tag, `โŒ CRITICAL FAILURE: KeepKey device not connected (0 pubkeys)`);
675
+ criticalFailures.push("KeepKey device not connected - no pubkeys loaded");
676
+ }
677
+ // CRITICAL: Staking positions found
678
+ testsTotal++;
679
+ if (existingStakingPositions.length > 0) {
680
+ log.info(tag, `โœ… Staking positions found: ${existingStakingPositions.length}`);
681
+ testsPassed++;
682
+ }
683
+ else {
684
+ log.error(tag, `โŒ CRITICAL FAILURE: No staking positions found`);
685
+ criticalFailures.push("No staking positions found - nothing to claim rewards from");
686
+ }
687
+ // CRITICAL: Transaction execution (if enabled)
688
+ if (TEST_CONFIG.ACTUALLY_EXECUTE_TRANSACTIONS) {
689
+ testsTotal++;
690
+ // This will be updated by the transaction execution logic
691
+ log.info(tag, `โณ Transaction execution: Checking if any transactions were executed...`);
692
+ // For now, mark as failed since we know no transactions were executed
693
+ log.error(tag, `โŒ CRITICAL FAILURE: No transactions were executed`);
694
+ criticalFailures.push("No transactions were executed - test set to execute but nothing happened");
695
+ }
696
+ // Final test result
697
+ log.info(tag, "");
698
+ log.info(tag, "=".repeat(60));
699
+ if (criticalFailures.length > 0) {
700
+ log.error(tag, "โŒ STAKING TEST SUITE FAILED!");
701
+ log.error(tag, "");
702
+ log.error(tag, "๐Ÿ’ฅ CRITICAL FAILURES:");
703
+ criticalFailures.forEach((failure, index) => {
704
+ log.error(tag, ` ${index + 1}. ${failure}`);
705
+ });
706
+ log.error(tag, "");
707
+ log.error(tag, "๐Ÿ”ง TO FIX:");
708
+ log.error(tag, " 1. Connect and unlock your KeepKey device");
709
+ log.error(tag, " 2. Ensure you have existing staking positions with rewards");
710
+ log.error(tag, " 3. Make sure your device is properly paired");
711
+ log.error(tag, "");
712
+ // Exit with error code
713
+ process.exit(1);
714
+ }
715
+ else {
716
+ log.info(tag, "๐ŸŽ‰ STAKING TEST SUITE COMPLETED SUCCESSFULLY!");
717
+ log.info(tag, `๐Ÿ“Š Tests passed: ${testsPassed}/${testsTotal}`);
718
+ }
719
+ log.info(tag, "");
720
+ // All staking functionality is now implemented and ready to use:
721
+ log.info(tag, "โœ… Implementation Status:");
722
+ log.info(tag, " โœ… buildDelegateTx() method - IMPLEMENTED");
723
+ log.info(tag, " โœ… buildUndelegateTx() method - IMPLEMENTED");
724
+ log.info(tag, " โœ… buildClaimRewardsTx() method - IMPLEMENTED");
725
+ log.info(tag, " โœ… buildClaimAllRewardsTx() method - IMPLEMENTED");
726
+ log.info(tag, " โœ… Staking transaction templates - IMPLEMENTED");
727
+ log.info(tag, " โœ… KeepKey SDK staking signing methods - IMPLEMENTED");
728
+ log.info(tag, "");
729
+ log.info(tag, "๐Ÿ’ก To test staking functionality:");
730
+ log.info(tag, " 1. Make sure your KeepKey device is connected and unlocked");
731
+ log.info(tag, " 2. Ensure you have existing staking positions with rewards");
732
+ log.info(tag, " 3. Run this test - it will ask you to sign transactions if positions are found");
733
+ }
734
+ catch (e) {
735
+ log.error(tag, "โŒ STAKING TEST FAILED:", e);
736
+ process.exit(1);
737
+ }
738
+ };
739
+ // Run the staking test suite
740
+ test_staking_service();