@pioneer-platform/pioneer-sdk 8.15.44 → 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/dist/index.cjs +58302 -121
- package/dist/index.es.js +46545 -2192
- package/dist/index.js +46545 -2192
- package/package.json +6 -6
- package/src/TransactionManager.ts +109 -11
- package/src/getPubkey.ts +29 -3
- package/src/index.ts +27 -2
- package/src/kkapi-batch-client.ts +97 -20
- package/src/supportedCaips.ts +6 -1
- package/src/txbuilder/createUnsignedSolanaTx.ts +132 -0
- package/src/txbuilder/createUnsignedTronTx.ts +97 -0
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "highlander",
|
|
3
3
|
"name": "@pioneer-platform/pioneer-sdk",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.19.0",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"
|
|
7
|
-
"@pioneer-platform/pioneer-caip": "^9.
|
|
6
|
+
"keepkey-vault-sdk": "^1.0.2",
|
|
7
|
+
"@pioneer-platform/pioneer-caip": "^9.14.0",
|
|
8
8
|
"@pioneer-platform/pioneer-client": "^9.10.24",
|
|
9
|
-
"@pioneer-platform/pioneer-coins": "^9.
|
|
10
|
-
"@pioneer-platform/pioneer-discovery": "^8.
|
|
11
|
-
"@pioneer-platform/pioneer-events": "^8.
|
|
9
|
+
"@pioneer-platform/pioneer-coins": "^9.15.0",
|
|
10
|
+
"@pioneer-platform/pioneer-discovery": "^8.19.0",
|
|
11
|
+
"@pioneer-platform/pioneer-events": "^8.15.0",
|
|
12
12
|
"coinselect": "^3.1.13",
|
|
13
13
|
"eventemitter3": "^5.0.1",
|
|
14
14
|
"neotraverse": "^0.6.8",
|
|
@@ -6,7 +6,9 @@ import type EventEmitter from 'events';
|
|
|
6
6
|
import { CAIP_TO_COIN_MAP, SUPPORTED_CAIPS } from './supportedCaips';
|
|
7
7
|
import { createUnsignedEvmTx } from './txbuilder/createUnsignedEvmTx';
|
|
8
8
|
import { createUnsignedRippleTx } from './txbuilder/createUnsignedRippleTx';
|
|
9
|
+
import { createUnsignedSolanaTx } from './txbuilder/createUnsignedSolanaTx';
|
|
9
10
|
import { createUnsignedTendermintTx } from './txbuilder/createUnsignedTendermintTx';
|
|
11
|
+
import { createUnsignedTronTx } from './txbuilder/createUnsignedTronTx';
|
|
10
12
|
import { createUnsignedUxtoTx } from './txbuilder/createUnsignedUxtoTx';
|
|
11
13
|
|
|
12
14
|
const TAG = ' | Transaction | ';
|
|
@@ -138,16 +140,43 @@ export class TransactionManager {
|
|
|
138
140
|
break;
|
|
139
141
|
}
|
|
140
142
|
case 'OTHER': {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
143
|
+
// Distinguish between different chains in the OTHER category
|
|
144
|
+
if (caip.startsWith('solana:')) {
|
|
145
|
+
unsignedTx = await createUnsignedSolanaTx(
|
|
146
|
+
caip,
|
|
147
|
+
to,
|
|
148
|
+
amount,
|
|
149
|
+
memo,
|
|
150
|
+
this.pubkeys,
|
|
151
|
+
this.pioneer,
|
|
152
|
+
this.pubkeyContext,
|
|
153
|
+
isMax,
|
|
154
|
+
);
|
|
155
|
+
} else if (caip === 'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144') {
|
|
156
|
+
unsignedTx = await createUnsignedRippleTx(
|
|
157
|
+
caip,
|
|
158
|
+
to,
|
|
159
|
+
amount,
|
|
160
|
+
memo,
|
|
161
|
+
this.pubkeys,
|
|
162
|
+
this.pioneer,
|
|
163
|
+
this.pubkeyContext,
|
|
164
|
+
isMax,
|
|
165
|
+
);
|
|
166
|
+
} else if (caip.startsWith('tron:')) {
|
|
167
|
+
unsignedTx = await createUnsignedTronTx(
|
|
168
|
+
caip,
|
|
169
|
+
to,
|
|
170
|
+
amount,
|
|
171
|
+
memo,
|
|
172
|
+
this.pubkeys,
|
|
173
|
+
this.pioneer,
|
|
174
|
+
this.pubkeyContext,
|
|
175
|
+
isMax,
|
|
176
|
+
);
|
|
177
|
+
} else {
|
|
178
|
+
throw new Error(`Unsupported OTHER CAIP for transaction building: ${caip}`);
|
|
179
|
+
}
|
|
151
180
|
break;
|
|
152
181
|
}
|
|
153
182
|
default: {
|
|
@@ -331,10 +360,79 @@ export class TransactionManager {
|
|
|
331
360
|
break;
|
|
332
361
|
}
|
|
333
362
|
case 'OTHER': {
|
|
334
|
-
if (caip
|
|
363
|
+
if (caip.startsWith('solana:')) {
|
|
364
|
+
// Solana signing via keepkey-server REST API
|
|
365
|
+
const solanaSignRequest = {
|
|
366
|
+
addressNList: unsignedTx.addressNList,
|
|
367
|
+
serialized: unsignedTx.serialized,
|
|
368
|
+
fromAddress: unsignedTx.fromAddress,
|
|
369
|
+
toAddress: unsignedTx.toAddress,
|
|
370
|
+
lamports: unsignedTx.lamports,
|
|
371
|
+
blockhash: unsignedTx.blockhash
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const response = await fetch('http://localhost:1646/solana/sign-transaction', {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
headers: {
|
|
378
|
+
'Content-Type': 'application/json',
|
|
379
|
+
},
|
|
380
|
+
body: JSON.stringify(solanaSignRequest)
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (!response.ok) {
|
|
384
|
+
const errorData = await response.json();
|
|
385
|
+
throw new Error(`Solana signing failed: ${errorData.error || response.statusText}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const responseSign = await response.json();
|
|
389
|
+
if (responseSign?.serialized) {
|
|
390
|
+
signedTx = responseSign.serialized;
|
|
391
|
+
} else {
|
|
392
|
+
throw new Error('Solana signing failed - no serialized transaction in response');
|
|
393
|
+
}
|
|
394
|
+
} catch (e: any) {
|
|
395
|
+
console.error(tag, 'Solana signing error:', e);
|
|
396
|
+
throw new Error(`Solana signing failed: ${e.message}`);
|
|
397
|
+
}
|
|
398
|
+
} else if (caip === 'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144') {
|
|
335
399
|
let responseSign = await this.keepKeySdk.xrp.xrpSignTransaction(unsignedTx);
|
|
336
400
|
if (typeof responseSign === 'string') responseSign = JSON.parse(responseSign);
|
|
337
401
|
signedTx = responseSign.value.signatures[0].serializedTx;
|
|
402
|
+
} else if (caip.startsWith('tron:')) {
|
|
403
|
+
// TRON signing via keepkey-server REST API
|
|
404
|
+
const tronSignRequest = {
|
|
405
|
+
addressNList: unsignedTx.addressNList,
|
|
406
|
+
from: unsignedTx.from,
|
|
407
|
+
to: unsignedTx.to,
|
|
408
|
+
amount: unsignedTx.amountInSun,
|
|
409
|
+
memo: unsignedTx.memo || '',
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const response = await fetch('http://localhost:1646/tron/sign-transaction', {
|
|
414
|
+
method: 'POST',
|
|
415
|
+
headers: {
|
|
416
|
+
'Content-Type': 'application/json',
|
|
417
|
+
},
|
|
418
|
+
body: JSON.stringify(tronSignRequest)
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
const errorData = await response.json();
|
|
423
|
+
throw new Error(`TRON signing failed: ${errorData.error || response.statusText}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const responseSign = await response.json();
|
|
427
|
+
if (responseSign?.serialized) {
|
|
428
|
+
signedTx = responseSign.serialized;
|
|
429
|
+
} else {
|
|
430
|
+
throw new Error('TRON signing failed - no serialized transaction in response');
|
|
431
|
+
}
|
|
432
|
+
} catch (e: any) {
|
|
433
|
+
console.error(tag, 'TRON signing error:', e);
|
|
434
|
+
throw new Error(`TRON signing failed: ${e.message}`);
|
|
435
|
+
}
|
|
338
436
|
} else {
|
|
339
437
|
throw new Error(`Unsupported OTHER CAIP: ${caip}`);
|
|
340
438
|
}
|
package/src/getPubkey.ts
CHANGED
|
@@ -38,7 +38,9 @@ export const getPubkey = async (networkId: string, path: any, sdk: any, context:
|
|
|
38
38
|
'eip155:137': 'EVM',
|
|
39
39
|
'eip155:*': 'EVM',
|
|
40
40
|
'ripple:4109c6f2045fc7eff4cde8f9905d19c2': 'XRP',
|
|
41
|
-
'solana:
|
|
41
|
+
'solana:5eykt4usfv8p8njdtrepy1vzqkqzkvdp': 'SOLANA', // Solana
|
|
42
|
+
'tron:0x2b6653dc': 'TRON', // TRON (Shasta testnet)
|
|
43
|
+
'ton:-239': 'TON', // TON (testnet)
|
|
42
44
|
'zcash:main': 'UTXO',
|
|
43
45
|
};
|
|
44
46
|
|
|
@@ -73,8 +75,32 @@ export const getPubkey = async (networkId: string, path: any, sdk: any, context:
|
|
|
73
75
|
({ address } = await sdk.address.xrpGetAddress(addressInfo));
|
|
74
76
|
break;
|
|
75
77
|
case 'SOLANA':
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
// Check if SDK has solanaGetAddress method
|
|
79
|
+
if (sdk.address.solanaGetAddress) {
|
|
80
|
+
({ address } = await sdk.address.solanaGetAddress(addressInfo));
|
|
81
|
+
} else {
|
|
82
|
+
console.warn(`⚠️ Solana address derivation not yet supported by KeepKey SDK. Skipping ${networkId}`);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case 'TRON':
|
|
87
|
+
// Check if SDK has tronGetAddress method
|
|
88
|
+
if (sdk.address.tronGetAddress) {
|
|
89
|
+
({ address } = await sdk.address.tronGetAddress(addressInfo));
|
|
90
|
+
} else {
|
|
91
|
+
console.warn(`⚠️ TRON address derivation not yet supported by KeepKey SDK. Skipping ${networkId}`);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
case 'TON':
|
|
96
|
+
// Check if SDK has tonGetAddress method
|
|
97
|
+
if (sdk.address.tonGetAddress) {
|
|
98
|
+
({ address } = await sdk.address.tonGetAddress(addressInfo));
|
|
99
|
+
} else {
|
|
100
|
+
console.warn(`⚠️ TON address derivation not yet supported by KeepKey SDK. Skipping ${networkId}`);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
78
104
|
default:
|
|
79
105
|
throw new Error(`Unsupported network type for networkId: ${networkId}`);
|
|
80
106
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeepKeySdk } from '
|
|
1
|
+
import { KeepKeySdk } from 'keepkey-vault-sdk';
|
|
2
2
|
import { caipToNetworkId, networkIdToCaip } from '@pioneer-platform/pioneer-caip';
|
|
3
3
|
import { Pioneer } from '@pioneer-platform/pioneer-client';
|
|
4
4
|
import { addressNListToBIP32, getPaths } from '@pioneer-platform/pioneer-coins';
|
|
@@ -10,7 +10,7 @@ import { getCharts } from './charts/index.js';
|
|
|
10
10
|
//internal
|
|
11
11
|
import { logger } from './utils/logger.js';
|
|
12
12
|
import { getPubkey } from './getPubkey.js';
|
|
13
|
-
import { optimizedGetPubkeys } from './kkapi-batch-client.js';
|
|
13
|
+
import { optimizedGetPubkeys, checkKkapiHealth } from './kkapi-batch-client.js';
|
|
14
14
|
import { OfflineClient } from './offline-client.js';
|
|
15
15
|
import { TransactionManager } from './TransactionManager.js';
|
|
16
16
|
import { createUnsignedTendermintTx } from './txbuilder/createUnsignedTendermintTx.js';
|
|
@@ -301,6 +301,11 @@ export class SDK {
|
|
|
301
301
|
const key = getPubkeyKey(pubkey);
|
|
302
302
|
if (this.pubkeySet.has(key)) return false;
|
|
303
303
|
|
|
304
|
+
// PHASE 4: Normalize network IDs to lowercase for consistency
|
|
305
|
+
if (pubkey.networks && Array.isArray(pubkey.networks)) {
|
|
306
|
+
pubkey.networks = pubkey.networks.map((n: string) => n.toLowerCase());
|
|
307
|
+
}
|
|
308
|
+
|
|
304
309
|
this.pubkeys.push(pubkey);
|
|
305
310
|
this.pubkeySet.add(key);
|
|
306
311
|
return true;
|
|
@@ -375,6 +380,12 @@ export class SDK {
|
|
|
375
380
|
|
|
376
381
|
// Update SDK state if we have balances
|
|
377
382
|
if (allBalances.length > 0) {
|
|
383
|
+
// PHASE 5: Normalize all CAIPs to lowercase for consistency
|
|
384
|
+
allBalances.forEach((balance: any) => {
|
|
385
|
+
if (balance.caip) balance.caip = balance.caip.toLowerCase();
|
|
386
|
+
if (balance.networkId) balance.networkId = balance.networkId.toLowerCase();
|
|
387
|
+
});
|
|
388
|
+
|
|
378
389
|
this.balances = allBalances;
|
|
379
390
|
this.events.emit('SET_BALANCES', this.balances);
|
|
380
391
|
}
|
|
@@ -1840,6 +1851,12 @@ export class SDK {
|
|
|
1840
1851
|
|
|
1841
1852
|
console.log(`⏱️ [PERF] Enrichment completed in ${(performance.now() - enrichStart).toFixed(0)}ms`);
|
|
1842
1853
|
|
|
1854
|
+
// PHASE 5: Normalize all CAIPs to lowercase for consistency
|
|
1855
|
+
balances.forEach((balance: any) => {
|
|
1856
|
+
if (balance.caip) balance.caip = balance.caip.toLowerCase();
|
|
1857
|
+
if (balance.networkId) balance.networkId = balance.networkId.toLowerCase();
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1843
1860
|
// CRITICAL: Deduplicate balances BEFORE setting
|
|
1844
1861
|
// Merge new balances with existing balances, deduplicating by identifier (caip:pubkey)
|
|
1845
1862
|
console.log(tag, `⚙️ Merging balances: ${balances.length} new + ${this.balances.length} existing`);
|
|
@@ -2061,6 +2078,10 @@ export class SDK {
|
|
|
2061
2078
|
}
|
|
2062
2079
|
|
|
2063
2080
|
if (!asset.caip) throw Error('Invalid Asset! missing caip!');
|
|
2081
|
+
|
|
2082
|
+
// PHASE 6: Normalize CAIP to lowercase for consistency
|
|
2083
|
+
asset.caip = asset.caip.toLowerCase();
|
|
2084
|
+
|
|
2064
2085
|
if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
|
|
2065
2086
|
|
|
2066
2087
|
// Validate pubkeys for network (throws descriptive errors)
|
|
@@ -2318,6 +2339,10 @@ export class SDK {
|
|
|
2318
2339
|
}
|
|
2319
2340
|
|
|
2320
2341
|
if (!asset.caip) throw Error('Invalid Asset! missing caip!');
|
|
2342
|
+
|
|
2343
|
+
// PHASE 6: Normalize CAIP to lowercase for consistency
|
|
2344
|
+
asset.caip = asset.caip.toLowerCase();
|
|
2345
|
+
|
|
2321
2346
|
if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
|
|
2322
2347
|
|
|
2323
2348
|
console.log(tag, 'networkId:', asset.networkId);
|
|
@@ -32,42 +32,93 @@ export interface KkapiHealthStatus {
|
|
|
32
32
|
device_connected: boolean;
|
|
33
33
|
cached_pubkeys: number;
|
|
34
34
|
vault_version?: string;
|
|
35
|
+
apiVersion?: number;
|
|
36
|
+
supportedChains?: string[];
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* Check if kkapi:// vault is available and ready
|
|
39
41
|
*/
|
|
40
42
|
export async function checkKkapiHealth(baseUrl: string = 'kkapi://'): Promise<KkapiHealthStatus> {
|
|
43
|
+
// Default V1 supported chains (legacy chains without Solana, TRON, TON)
|
|
44
|
+
const V1_SUPPORTED_CHAINS = [
|
|
45
|
+
'bip122:000000000019d6689c085ae165831e93', // BTC
|
|
46
|
+
'bip122:000000000000000000651ef99cb9fcbe', // BCH
|
|
47
|
+
'bip122:000007d91d1254d60e2dd1ae58038307', // DASH
|
|
48
|
+
'bip122:00000000001a91e3dace36e2be3bf030', // DOGE
|
|
49
|
+
'bip122:12a765e31ffd4059bada1e25190f6e98', // LTC
|
|
50
|
+
'bip122:4da631f2ac1bed857bd968c67c913978', // DGB
|
|
51
|
+
'bip122:00040fe8ec8471911baa1db1266ea15d', // ZEC
|
|
52
|
+
'eip155:1', // ETH
|
|
53
|
+
'eip155:137', // MATIC
|
|
54
|
+
'eip155:8453', // BASE
|
|
55
|
+
'eip155:56', // BSC
|
|
56
|
+
'cosmos:cosmoshub-4', // ATOM
|
|
57
|
+
'cosmos:osmosis-1', // OSMO
|
|
58
|
+
'cosmos:mayachain-mainnet-v1', // MAYA
|
|
59
|
+
'cosmos:thorchain-mainnet-v1', // RUNE
|
|
60
|
+
'ripple:4109c6f2045fc7eff4cde8f9905d19c2', // XRP
|
|
61
|
+
];
|
|
62
|
+
|
|
41
63
|
try {
|
|
42
64
|
const healthResponse = await fetch(`${baseUrl}/api/health`);
|
|
65
|
+
|
|
66
|
+
// If health endpoint doesn't exist (404), assume v1 server (Desktop app)
|
|
67
|
+
if (healthResponse.status === 404) {
|
|
68
|
+
console.log('📡 [VERSION] No /api/health endpoint - assuming v1 server (Desktop app)');
|
|
69
|
+
return {
|
|
70
|
+
available: true,
|
|
71
|
+
device_connected: true,
|
|
72
|
+
cached_pubkeys: 0,
|
|
73
|
+
apiVersion: 1,
|
|
74
|
+
supportedChains: V1_SUPPORTED_CHAINS
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
43
78
|
if (!healthResponse.ok) {
|
|
44
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
available: false,
|
|
81
|
+
device_connected: false,
|
|
82
|
+
cached_pubkeys: 0,
|
|
83
|
+
apiVersion: 1,
|
|
84
|
+
supportedChains: V1_SUPPORTED_CHAINS
|
|
85
|
+
};
|
|
45
86
|
}
|
|
46
87
|
|
|
47
88
|
const healthData = await healthResponse.json();
|
|
48
|
-
|
|
89
|
+
|
|
90
|
+
// V2 server returns apiVersion and supportedChains
|
|
91
|
+
if (healthData.apiVersion === 2 && healthData.supportedChains) {
|
|
49
92
|
return {
|
|
50
93
|
available: true,
|
|
51
94
|
device_connected: healthData.device_connected || false,
|
|
52
95
|
cached_pubkeys: healthData.cached_pubkeys || 0,
|
|
53
|
-
vault_version: healthData.version
|
|
96
|
+
vault_version: healthData.version,
|
|
97
|
+
apiVersion: healthData.apiVersion,
|
|
98
|
+
supportedChains: healthData.supportedChains
|
|
54
99
|
};
|
|
55
100
|
}
|
|
56
101
|
|
|
57
|
-
|
|
58
|
-
if (!cacheResponse.ok) {
|
|
59
|
-
return { available: true, device_connected: true, cached_pubkeys: 0 };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const cacheData = await cacheResponse.json();
|
|
102
|
+
// V1 server or old format - use default v1 chains
|
|
63
103
|
return {
|
|
64
104
|
available: true,
|
|
65
|
-
device_connected:
|
|
66
|
-
cached_pubkeys:
|
|
67
|
-
vault_version:
|
|
105
|
+
device_connected: healthData.device_connected || false,
|
|
106
|
+
cached_pubkeys: healthData.cached_pubkeys || 0,
|
|
107
|
+
vault_version: healthData.version,
|
|
108
|
+
apiVersion: 1,
|
|
109
|
+
supportedChains: V1_SUPPORTED_CHAINS
|
|
68
110
|
};
|
|
111
|
+
|
|
69
112
|
} catch (error: any) {
|
|
70
|
-
|
|
113
|
+
// Network error or server unreachable - assume v1 for backward compatibility
|
|
114
|
+
console.log('📡 [VERSION] Health check failed - assuming v1 server');
|
|
115
|
+
return {
|
|
116
|
+
available: false,
|
|
117
|
+
device_connected: false,
|
|
118
|
+
cached_pubkeys: 0,
|
|
119
|
+
apiVersion: 1,
|
|
120
|
+
supportedChains: V1_SUPPORTED_CHAINS
|
|
121
|
+
};
|
|
71
122
|
}
|
|
72
123
|
}
|
|
73
124
|
|
|
@@ -138,6 +189,28 @@ export async function optimizedGetPubkeys(
|
|
|
138
189
|
|
|
139
190
|
const vaultHealth = await checkKkapiHealth(baseUrl);
|
|
140
191
|
|
|
192
|
+
// Filter blockchains based on server's supportedChains
|
|
193
|
+
let supportedBlockchains = blockchains;
|
|
194
|
+
if (vaultHealth.supportedChains && vaultHealth.supportedChains.length > 0) {
|
|
195
|
+
console.log(`📡 [VERSION] Server API v${vaultHealth.apiVersion} supports ${vaultHealth.supportedChains.length} chains`);
|
|
196
|
+
|
|
197
|
+
// Filter blockchains to only include ones the server supports
|
|
198
|
+
const unsupportedBlockchains: string[] = [];
|
|
199
|
+
supportedBlockchains = blockchains.filter(blockchain => {
|
|
200
|
+
// Check if this blockchain is in the server's supported list
|
|
201
|
+
const isSupported = vaultHealth.supportedChains!.includes(blockchain);
|
|
202
|
+
if (!isSupported) {
|
|
203
|
+
unsupportedBlockchains.push(blockchain);
|
|
204
|
+
}
|
|
205
|
+
return isSupported;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (unsupportedBlockchains.length > 0) {
|
|
209
|
+
console.log(`⏭️ [VERSION] Skipping ${unsupportedBlockchains.length} unsupported chains:`, unsupportedBlockchains.join(', '));
|
|
210
|
+
}
|
|
211
|
+
console.log(`✅ [VERSION] Using ${supportedBlockchains.length} supported chains`);
|
|
212
|
+
}
|
|
213
|
+
|
|
141
214
|
let pubkeys: any[] = [];
|
|
142
215
|
let remainingPaths: any[] = [];
|
|
143
216
|
let remainingBlockchains: string[] = [];
|
|
@@ -146,15 +219,15 @@ export async function optimizedGetPubkeys(
|
|
|
146
219
|
const batchResponse = await batchGetPubkeys(paths, context, baseUrl);
|
|
147
220
|
pubkeys = batchResponse.pubkeys;
|
|
148
221
|
const cachedPaths = new Set(batchResponse.pubkeys.map(p => p.path));
|
|
149
|
-
|
|
150
|
-
for (let i = 0; i <
|
|
151
|
-
const blockchain =
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < supportedBlockchains.length; i++) {
|
|
224
|
+
const blockchain = supportedBlockchains[i];
|
|
152
225
|
const pathsForChain = paths.filter(path => path.networks && Array.isArray(path.networks) && path.networks.includes(blockchain));
|
|
153
|
-
|
|
226
|
+
|
|
154
227
|
for (const path of pathsForChain) {
|
|
155
228
|
const { addressNListToBIP32 } = await import('@pioneer-platform/pioneer-coins');
|
|
156
229
|
const pathBip32 = addressNListToBIP32(path.addressNList);
|
|
157
|
-
|
|
230
|
+
|
|
158
231
|
if (!cachedPaths.has(pathBip32)) {
|
|
159
232
|
remainingPaths.push(path);
|
|
160
233
|
if (!remainingBlockchains.includes(blockchain)) {
|
|
@@ -164,8 +237,12 @@ export async function optimizedGetPubkeys(
|
|
|
164
237
|
}
|
|
165
238
|
}
|
|
166
239
|
} else {
|
|
167
|
-
|
|
168
|
-
|
|
240
|
+
// Filter paths to only include supported blockchains
|
|
241
|
+
remainingPaths = paths.filter(path =>
|
|
242
|
+
path.networks && Array.isArray(path.networks) &&
|
|
243
|
+
path.networks.some((network: string) => supportedBlockchains.includes(network))
|
|
244
|
+
);
|
|
245
|
+
remainingBlockchains = supportedBlockchains;
|
|
169
246
|
}
|
|
170
247
|
|
|
171
248
|
if (remainingPaths.length > 0) {
|
package/src/supportedCaips.ts
CHANGED
|
@@ -30,7 +30,12 @@ export const CAIP_TO_COIN_MAP: { [key: string]: string } = {
|
|
|
30
30
|
'bip122:00040fe8ec8471911baa1db1266ea15d/slip44:133': 'Zcash',
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const OTHER_SUPPORT = [
|
|
33
|
+
export const OTHER_SUPPORT = [
|
|
34
|
+
'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144', // XRP
|
|
35
|
+
'solana:5eykt4usfv8p8njdtrepy1vzqkqzkvdp/solana:so11111111111111111111111111111111111111112', // SOL
|
|
36
|
+
'tron:0x2b6653dc/slip44:195', // TRX
|
|
37
|
+
'ton:-239/slip44:607', // TON
|
|
38
|
+
];
|
|
34
39
|
|
|
35
40
|
export const SUPPORTED_CAIPS = {
|
|
36
41
|
UTXO: UTXO_SUPPORT,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Create Unsigned Solana Transaction
|
|
3
|
+
*/
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { caipToNetworkId } from '@pioneer-platform/pioneer-caip';
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import {
|
|
8
|
+
Connection,
|
|
9
|
+
PublicKey,
|
|
10
|
+
Transaction,
|
|
11
|
+
SystemProgram,
|
|
12
|
+
LAMPORTS_PER_SOL,
|
|
13
|
+
} from '@solana/web3.js';
|
|
14
|
+
|
|
15
|
+
const TAG = ' | createUnsignedSolanaTx | ';
|
|
16
|
+
|
|
17
|
+
// Default Solana RPC (could be made configurable)
|
|
18
|
+
const DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
|
|
19
|
+
|
|
20
|
+
export async function createUnsignedSolanaTx(
|
|
21
|
+
caip: string,
|
|
22
|
+
to: string,
|
|
23
|
+
amount: any,
|
|
24
|
+
memo: string,
|
|
25
|
+
pubkeys: any,
|
|
26
|
+
pioneer: any,
|
|
27
|
+
pubkeyContext: any,
|
|
28
|
+
isMax: boolean,
|
|
29
|
+
): Promise<any> {
|
|
30
|
+
let tag = TAG + ' | createUnsignedSolanaTx | ';
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (!pioneer) throw new Error('Failed to init! pioneer');
|
|
34
|
+
|
|
35
|
+
// Determine networkId from caip
|
|
36
|
+
const networkId = caipToNetworkId(caip);
|
|
37
|
+
|
|
38
|
+
// Use the passed pubkeyContext directly - it's already been set by Pioneer SDK
|
|
39
|
+
if (!pubkeyContext) {
|
|
40
|
+
throw new Error(`No pubkey context provided for networkId: ${networkId}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!pubkeyContext.networks?.includes(networkId)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Pubkey context is for wrong network. Expected ${networkId}, got ${pubkeyContext.networks}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(tag, `✅ Using pubkeyContext for network ${networkId}:`, {
|
|
50
|
+
address: pubkeyContext.address,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const fromAddress = pubkeyContext.address || pubkeyContext.pubkey;
|
|
54
|
+
|
|
55
|
+
// Get account balance to calculate max amount if needed
|
|
56
|
+
let accountInfo = await pioneer.GetAccountInfo({
|
|
57
|
+
address: fromAddress,
|
|
58
|
+
network: 'solana',
|
|
59
|
+
});
|
|
60
|
+
accountInfo = accountInfo.data;
|
|
61
|
+
|
|
62
|
+
// Calculate amount
|
|
63
|
+
let amountInSol: number;
|
|
64
|
+
if (isMax) {
|
|
65
|
+
// Reserve fee (approximately 0.000005 SOL = 5000 lamports)
|
|
66
|
+
const feeInSol = 0.000005;
|
|
67
|
+
amountInSol = parseFloat(accountInfo.balance) - feeInSol;
|
|
68
|
+
if (amountInSol <= 0) {
|
|
69
|
+
throw new Error('Insufficient balance to cover fee');
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
amountInSol = parseFloat(amount);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build transaction using @solana/web3.js
|
|
76
|
+
const connection = new Connection(DEFAULT_RPC_URL, 'confirmed');
|
|
77
|
+
|
|
78
|
+
// Convert to PublicKey objects
|
|
79
|
+
const fromPubkey = new PublicKey(fromAddress);
|
|
80
|
+
const toPubkey = new PublicKey(to);
|
|
81
|
+
|
|
82
|
+
// Convert SOL to lamports
|
|
83
|
+
const lamports = Math.floor(amountInSol * LAMPORTS_PER_SOL);
|
|
84
|
+
|
|
85
|
+
// Get recent blockhash
|
|
86
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
87
|
+
|
|
88
|
+
// Create transfer instruction
|
|
89
|
+
const transferInstruction = SystemProgram.transfer({
|
|
90
|
+
fromPubkey,
|
|
91
|
+
toPubkey,
|
|
92
|
+
lamports,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Create transaction
|
|
96
|
+
const transaction = new Transaction({
|
|
97
|
+
feePayer: fromPubkey,
|
|
98
|
+
blockhash,
|
|
99
|
+
lastValidBlockHeight,
|
|
100
|
+
}).add(transferInstruction);
|
|
101
|
+
|
|
102
|
+
// Add memo instruction if provided
|
|
103
|
+
if (memo && memo.trim() !== '') {
|
|
104
|
+
// TODO: Add memo instruction
|
|
105
|
+
console.log(tag, 'Memo support not yet implemented');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Serialize transaction message for signing
|
|
109
|
+
const serialized = transaction.serializeMessage();
|
|
110
|
+
|
|
111
|
+
const unsignedTx = {
|
|
112
|
+
transaction,
|
|
113
|
+
serialized: serialized.toString('base64'),
|
|
114
|
+
blockhash,
|
|
115
|
+
lastValidBlockHeight,
|
|
116
|
+
addressNList: pubkeyContext.addressNList || pubkeyContext.addressNListMaster,
|
|
117
|
+
fromAddress,
|
|
118
|
+
toAddress: to,
|
|
119
|
+
amount: amountInSol,
|
|
120
|
+
lamports,
|
|
121
|
+
caip,
|
|
122
|
+
networkId,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
console.log(tag, '✅ Solana transaction built successfully');
|
|
126
|
+
|
|
127
|
+
return unsignedTx;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(tag, 'Error:', error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|