@pioneer-platform/pioneer-sdk 8.15.41 → 8.17.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 +58437 -123
- package/dist/index.es.js +46684 -2198
- package/dist/index.js +46684 -2198
- package/package.json +6 -6
- package/src/TransactionManager.ts +109 -11
- package/src/getPubkey.ts +29 -3
- package/src/index.ts +239 -5
- 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/src/utils/portfolio-helpers.ts +2 -2
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Create Unsigned TRON Transaction
|
|
3
|
+
*/
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { caipToNetworkId } from '@pioneer-platform/pioneer-caip';
|
|
6
|
+
|
|
7
|
+
const TAG = ' | createUnsignedTronTx | ';
|
|
8
|
+
|
|
9
|
+
export async function createUnsignedTronTx(
|
|
10
|
+
caip: string,
|
|
11
|
+
to: string,
|
|
12
|
+
amount: any,
|
|
13
|
+
memo: string,
|
|
14
|
+
pubkeys: any,
|
|
15
|
+
pioneer: any,
|
|
16
|
+
pubkeyContext: any,
|
|
17
|
+
isMax: boolean,
|
|
18
|
+
): Promise<any> {
|
|
19
|
+
let tag = TAG + ' | createUnsignedTronTx | ';
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (!pioneer) throw new Error('Failed to init! pioneer');
|
|
23
|
+
|
|
24
|
+
// Determine networkId from caip
|
|
25
|
+
const networkId = caipToNetworkId(caip);
|
|
26
|
+
|
|
27
|
+
// Use the passed pubkeyContext directly - it's already been set by Pioneer SDK
|
|
28
|
+
if (!pubkeyContext) {
|
|
29
|
+
throw new Error(`No pubkey context provided for networkId: ${networkId}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!pubkeyContext.networks?.includes(networkId)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Pubkey context is for wrong network. Expected ${networkId}, got ${pubkeyContext.networks}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(tag, `✅ Using pubkeyContext for network ${networkId}:`, {
|
|
39
|
+
address: pubkeyContext.address,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const fromAddress = pubkeyContext.address || pubkeyContext.pubkey;
|
|
43
|
+
|
|
44
|
+
// Calculate amount in TRX (6 decimals for TRON)
|
|
45
|
+
let amountInTrx: number;
|
|
46
|
+
if (isMax) {
|
|
47
|
+
// For max transfers, get account balance
|
|
48
|
+
try {
|
|
49
|
+
let accountInfo = await pioneer.GetAccountInfo({
|
|
50
|
+
address: fromAddress,
|
|
51
|
+
network: 'tron',
|
|
52
|
+
});
|
|
53
|
+
accountInfo = accountInfo.data;
|
|
54
|
+
|
|
55
|
+
// Reserve fee (approximately 0.1 TRX)
|
|
56
|
+
const feeInTrx = 0.1;
|
|
57
|
+
amountInTrx = parseFloat(accountInfo.balance) - feeInTrx;
|
|
58
|
+
if (amountInTrx <= 0) {
|
|
59
|
+
throw new Error('Insufficient balance to cover fee');
|
|
60
|
+
}
|
|
61
|
+
} catch (e: any) {
|
|
62
|
+
console.warn(tag, 'GetAccountInfo not available for TRON, max transfer not supported:', e.message);
|
|
63
|
+
throw new Error('Max transfer not supported for TRON - Pioneer backend needs TRON account info support');
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// For non-max transfers, use the provided amount directly
|
|
67
|
+
amountInTrx = parseFloat(amount);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Convert TRX to sun (1 TRX = 1,000,000 sun)
|
|
71
|
+
const amountInSun = Math.floor(amountInTrx * 1000000);
|
|
72
|
+
|
|
73
|
+
// Build unsigned transaction object for TRON
|
|
74
|
+
const unsignedTx = {
|
|
75
|
+
from: fromAddress,
|
|
76
|
+
to: to,
|
|
77
|
+
amount: amountInTrx,
|
|
78
|
+
amountInSun: amountInSun,
|
|
79
|
+
memo: memo || '',
|
|
80
|
+
addressNList: pubkeyContext.addressNList || pubkeyContext.addressNListMaster,
|
|
81
|
+
caip,
|
|
82
|
+
networkId,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
console.log(tag, '✅ TRON transaction built successfully');
|
|
86
|
+
console.log(tag, 'Transaction details:', {
|
|
87
|
+
from: fromAddress,
|
|
88
|
+
to: to,
|
|
89
|
+
amount: `${amountInTrx} TRX (${amountInSun} sun)`,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return unsignedTx;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(tag, 'Error:', error);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -57,8 +57,8 @@ const EXPLORER_BASE_URLS: Record<string, { address: string; tx: string }> = {
|
|
|
57
57
|
},
|
|
58
58
|
// Dash
|
|
59
59
|
'bip122:000007d91d1254d60e2dd1ae58038307': {
|
|
60
|
-
address: 'https://
|
|
61
|
-
tx: 'https://
|
|
60
|
+
address: 'https://explorer.dash.org/insight/address/',
|
|
61
|
+
tx: 'https://explorer.dash.org/insight/tx/'
|
|
62
62
|
},
|
|
63
63
|
// DigiByte
|
|
64
64
|
'bip122:4da631f2ac1bed857bd968c67c913978': {
|