@pioneer-platform/pioneer-sdk 4.21.6 → 4.21.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "highlander",
3
3
  "name": "@pioneer-platform/pioneer-sdk",
4
- "version": "4.21.6",
4
+ "version": "4.21.8",
5
5
  "dependencies": {
6
6
  "@keepkey/keepkey-sdk": "^0.2.62",
7
7
  "@pioneer-platform/loggerdog": "^8.11.0",
package/src/index.ts CHANGED
@@ -151,7 +151,7 @@ export class SDK {
151
151
  timeToConfirm: string | null;
152
152
  }>;
153
153
  public broadcastTx: (caip: string, signedTx: any) => Promise<any>;
154
- public signTx: (unsignedTx: any) => Promise<any>;
154
+ public signTx: (caip: string, unsignedTx: any) => Promise<any>;
155
155
  public buildTx: (sendPayload: any) => Promise<any>;
156
156
  public buildDelegateTx: (caip: string, params: StakingTxParams) => Promise<any>;
157
157
  public buildUndelegateTx: (caip: string, params: StakingTxParams) => Promise<any>;
@@ -1,384 +0,0 @@
1
- import { caipToNetworkId, NetworkIdToChain } from '@pioneer-platform/pioneer-caip';
2
- import { bip32ToAddressNList } from '@pioneer-platform/pioneer-coins';
3
- import coinSelect from 'coinselect';
4
- import coinSelectSplit from 'coinselect/split';
5
-
6
- export async function createUnsignedUxtoTx(
7
- caip: string,
8
- to: string,
9
- amount: number,
10
- memo: string,
11
- pubkeys: any,
12
- pioneer: any,
13
- keepKeySdk: any,
14
- isMax: boolean, // Added isMax parameter
15
- feeLevel: number = 5, // Added feeLevel parameter with default of 5 (average)
16
- changeScriptType?: string, // Added changeScriptType parameter for user preference
17
- ): Promise<any> {
18
- let tag = ' | createUnsignedUxtoTx | ';
19
-
20
- try {
21
- if (!pioneer) throw Error('Failed to init! pioneer');
22
-
23
- const networkId = caipToNetworkId(caip);
24
-
25
- // Auto-correct context if wrong network
26
- if (!keepKeySdk.pubkeyContext?.networks?.includes(networkId)) {
27
- keepKeySdk.pubkeyContext = pubkeys.find((pk) => pk.networks?.includes(networkId));
28
- }
29
-
30
- // For UTXO, we still need all relevant pubkeys to aggregate UTXOs
31
- const relevantPubkeys = pubkeys.filter(
32
- (e) => e.networks && Array.isArray(e.networks) && e.networks.includes(networkId),
33
- );
34
-
35
- const segwitNetworks = [
36
- 'bip122:000000000019d6689c085ae165831e93', // Bitcoin Mainnet
37
- // 'bip122:12a765e31ffd4059bada1e25190f6e98', // LTC
38
- ];
39
-
40
- // Check if the current networkId is in the SegWit networks array
41
- const isSegwit = segwitNetworks.includes(networkId);
42
-
43
- let chain = NetworkIdToChain[networkId];
44
-
45
- // Determine the change script type - use preference or default to p2wpkh for lower fees
46
- const actualChangeScriptType =
47
- changeScriptType ||
48
- relevantPubkeys.find((pk) => pk.scriptType === 'p2wpkh')?.scriptType || // Prefer native segwit if available
49
- relevantPubkeys[0].scriptType || // Fall back to first available
50
- 'p2wpkh'; // Ultimate default
51
- console.log(`${tag}: Using change script type: ${actualChangeScriptType}`);
52
-
53
- // Find the xpub that matches the desired script type
54
- const changeXpub =
55
- relevantPubkeys.find((pk) => pk.scriptType === actualChangeScriptType)?.pubkey ||
56
- relevantPubkeys.find((pk) => pk.scriptType === 'p2wpkh')?.pubkey || // Fall back to native segwit
57
- relevantPubkeys[0].pubkey; // Last resort: use first available
58
-
59
- console.log(
60
- `${tag}: Change xpub selected for ${actualChangeScriptType}:`,
61
- changeXpub?.substring(0, 10) + '...',
62
- );
63
-
64
- let changeAddressIndex = await pioneer.GetChangeAddress({
65
- network: chain,
66
- xpub: changeXpub,
67
- });
68
- changeAddressIndex = changeAddressIndex.data.changeIndex;
69
-
70
- // Determine BIP path based on script type
71
- let bipPath: string;
72
- switch (actualChangeScriptType) {
73
- case 'p2pkh':
74
- bipPath = `m/44'/0'/0'/1/${changeAddressIndex}`; // BIP44 for legacy
75
- break;
76
- case 'p2sh-p2wpkh':
77
- bipPath = `m/49'/0'/0'/1/${changeAddressIndex}`; // BIP49 for wrapped segwit
78
- break;
79
- case 'p2wpkh':
80
- default:
81
- bipPath = `m/84'/0'/0'/1/${changeAddressIndex}`; // BIP84 for native segwit
82
- break;
83
- }
84
-
85
- const path = bipPath;
86
- console.log(`${tag}: Change address path: ${path} (index: ${changeAddressIndex})`);
87
-
88
- const changeAddress = {
89
- path: path,
90
- isChange: true,
91
- index: changeAddressIndex,
92
- addressNList: bip32ToAddressNList(path),
93
- scriptType: actualChangeScriptType, // Store the script type with the change address
94
- };
95
-
96
- const utxos: any[] = [];
97
- for (const pubkey of relevantPubkeys) {
98
- //console.log('pubkey: ',pubkey)
99
- try {
100
- let utxosResp = await pioneer.ListUnspent({ network: chain, xpub: pubkey.pubkey });
101
- utxosResp = utxosResp.data;
102
- console.log(
103
- `${tag}: ListUnspent response for ${pubkey.scriptType}:`,
104
- utxosResp?.length || 0,
105
- 'UTXOs',
106
- );
107
-
108
- // Validate the response
109
- if (!utxosResp || !Array.isArray(utxosResp)) {
110
- console.warn(`${tag}: Invalid or empty UTXO response for ${pubkey.pubkey}`);
111
- continue;
112
- }
113
-
114
- //classify scriptType
115
- let scriptType = pubkey.scriptType;
116
- // Assign the scriptType to each UTXO in the array
117
- for (const u of utxosResp) {
118
- u.scriptType = scriptType;
119
- }
120
- utxos.push(...utxosResp);
121
- } catch (error) {
122
- console.error(`${tag}: Failed to fetch UTXOs for ${pubkey.pubkey}:`, error);
123
- // Continue to next pubkey instead of failing entirely
124
- }
125
- }
126
-
127
- console.log(`${tag}: Total UTXOs collected:`, utxos.length);
128
- if (!utxos || utxos.length === 0) throw Error('No UTXOs found across all addresses');
129
-
130
- for (const utxo of utxos) {
131
- utxo.value = Number(utxo.value);
132
- }
133
-
134
- let feeRateFromNode: any;
135
- try {
136
- // Try GetFeeRateByNetwork first (newer API), then fallback to GetFeeRate
137
- let feeResponse;
138
- if (pioneer.GetFeeRateByNetwork) {
139
- console.log(`${tag}: Trying GetFeeRateByNetwork for ${networkId}`);
140
- feeResponse = await pioneer.GetFeeRateByNetwork({ networkId });
141
- } else {
142
- console.log(`${tag}: Using GetFeeRate for ${networkId}`);
143
- feeResponse = await pioneer.GetFeeRate({ networkId });
144
- }
145
-
146
- feeRateFromNode = feeResponse.data;
147
- console.log(`${tag}: Got fee rates from API:`, JSON.stringify(feeRateFromNode, null, 2));
148
-
149
- // Validate the response has the expected structure
150
- if (!feeRateFromNode || typeof feeRateFromNode !== 'object') {
151
- throw new Error(`Invalid fee rate response from API: ${JSON.stringify(feeRateFromNode)}`);
152
- }
153
-
154
- // Log all available fee rates for debugging
155
- console.log(`${tag}: Available fee rates:`, {
156
- slow: feeRateFromNode.slow,
157
- average: feeRateFromNode.average,
158
- fast: feeRateFromNode.fast,
159
- fastest: feeRateFromNode.fastest,
160
- });
161
-
162
- // Check that we have at least one fee rate
163
- if (
164
- !feeRateFromNode.slow &&
165
- !feeRateFromNode.average &&
166
- !feeRateFromNode.fast &&
167
- !feeRateFromNode.fastest
168
- ) {
169
- throw new Error(`No valid fee rates in API response: ${JSON.stringify(feeRateFromNode)}`);
170
- }
171
- } catch (error: any) {
172
- console.error(`${tag}: Failed to get fee rates from Pioneer API:`, error.message || error);
173
- throw new Error(
174
- `Unable to get fee rate for network ${networkId}: ${error.message || 'API unavailable'}`,
175
- );
176
- }
177
-
178
- // Map fee level to fee rate (1-2 = slow, 3-4 = average, 5 = fastest)
179
- // Frontend typically sends: slow=1, average=3, fastest=5
180
- let effectiveFeeRate;
181
- console.log(`${tag}: Using fee level ${feeLevel}`);
182
-
183
- switch (feeLevel) {
184
- case 1:
185
- case 2:
186
- effectiveFeeRate = feeRateFromNode.slow || feeRateFromNode.average;
187
- console.log(`${tag}: Using SLOW fee rate: ${effectiveFeeRate} sat/vB`);
188
- break;
189
- case 3:
190
- case 4:
191
- effectiveFeeRate = feeRateFromNode.average || feeRateFromNode.fast;
192
- console.log(`${tag}: Using AVERAGE fee rate: ${effectiveFeeRate} sat/vB`);
193
- break;
194
- case 5:
195
- effectiveFeeRate = feeRateFromNode.fastest || feeRateFromNode.fast;
196
- console.log(`${tag}: Using FASTEST fee rate: ${effectiveFeeRate} sat/vB`);
197
- break;
198
- default:
199
- throw new Error(`Invalid fee level: ${feeLevel}. Must be 1-5`);
200
- }
201
-
202
- if (!effectiveFeeRate) throw new Error('Unable to get fee rate for network');
203
-
204
- // Log what we're using
205
- console.log(`${tag}: Using fee rate from API:`, {
206
- feeLevel,
207
- selectedRate: effectiveFeeRate,
208
- description: feeLevel <= 2 ? 'slow' : feeLevel <= 4 ? 'average' : 'fast',
209
- });
210
-
211
- // Only enforce minimum for safety if fee is 0 or negative
212
- if (effectiveFeeRate <= 0) throw Error('Failed to build valid fee! Must be > 0');
213
-
214
- // The effectiveFeeRate is now the actual sat/vB from the API
215
- console.log(`${tag}: Final fee rate to use: ${effectiveFeeRate} sat/vB`);
216
-
217
- amount = parseInt(String(amount * 1e8));
218
- if (amount <= 0 && !isMax) throw Error('Invalid amount! 0');
219
-
220
- // Log UTXOs before coin selection for debugging
221
- const totalBalance = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
222
- console.log(`${tag}: Coin selection inputs:`, {
223
- utxoCount: utxos.length,
224
- totalBalance: totalBalance / 1e8,
225
- requestedAmount: amount / 1e8,
226
- isMax,
227
- feeRate: effectiveFeeRate,
228
- });
229
-
230
- let result;
231
- if (isMax) {
232
- //console.log(tag, 'isMax:', isMax);
233
- // For max send, use coinSelectSplit
234
- result = coinSelectSplit(utxos, [{ address: to }], effectiveFeeRate);
235
- } else {
236
- //console.log(tag, 'isMax:', isMax)
237
- // Regular send
238
- result = coinSelect(utxos, [{ address: to, value: amount }], effectiveFeeRate);
239
- }
240
-
241
- console.log(tag, 'coinSelect result:', result ? 'Success' : 'Failed');
242
-
243
- if (!result || !result.inputs) {
244
- // Provide detailed error information
245
- const errorDetails = {
246
- utxoCount: utxos.length,
247
- totalBalance: totalBalance / 1e8,
248
- requestedAmount: amount / 1e8,
249
- feeRate: effectiveFeeRate,
250
- insufficientFunds: totalBalance < amount,
251
- };
252
- console.error(`${tag}: Coin selection failed:`, errorDetails);
253
-
254
- if (utxos.length === 0) {
255
- throw Error('No UTXOs available for coin selection');
256
- } else if (totalBalance < amount) {
257
- throw Error(`Insufficient funds: Have ${totalBalance / 1e8} BTC, need ${amount / 1e8} BTC`);
258
- } else {
259
- throw Error(
260
- 'Failed to create transaction: Coin selection failed (possibly due to high fees)',
261
- );
262
- }
263
- }
264
-
265
- let { inputs, outputs, fee } = result;
266
- if (!inputs) throw Error('Failed to create transaction: Missing inputs');
267
- if (!outputs) throw Error('Failed to create transaction: Missing outputs');
268
- if (fee === undefined) throw Error('Failed to calculate transaction fee');
269
-
270
- console.log(`${tag}: Transaction built with:`, {
271
- feeLevel,
272
- effectiveFeeRate: `${effectiveFeeRate} sat/vB`,
273
- calculatedFee: `${fee} satoshis`,
274
- inputCount: inputs.length,
275
- outputCount: outputs.length,
276
- });
277
-
278
- const uniqueInputSet = new Set();
279
- console.log(tag, 'inputs:', inputs);
280
- console.log(tag, 'inputs:', inputs[0]);
281
- const preparedInputs = inputs
282
- .map(transformInput)
283
- .filter(({ hash, index }) =>
284
- uniqueInputSet.has(`${hash}:${index}`) ? false : uniqueInputSet.add(`${hash}:${index}`),
285
- )
286
- .map(({ value, index, hash, txHex, path, scriptType }) => ({
287
- addressNList: bip32ToAddressNList(path),
288
- //TODO this is PER INPUT not per asset, we need to detect what pubkeys are segwit what are not
289
- scriptType,
290
- amount: value.toString(),
291
- vout: index,
292
- txid: hash,
293
- hex: txHex || '',
294
- }));
295
-
296
- // Remove hardcoded script type - use the one from changeAddress
297
-
298
- const preparedOutputs = outputs
299
- .map(({ value, address }) => {
300
- if (address) {
301
- return { address, amount: value.toString(), addressType: 'spend' };
302
- } else if (!isMax) {
303
- return {
304
- addressNList: changeAddress.addressNList,
305
- scriptType: changeAddress.scriptType, // Use the correct script type from change address
306
- isChange: true,
307
- amount: value.toString(),
308
- addressType: 'change',
309
- };
310
- }
311
- return null; // No change output when isMax is true
312
- })
313
- .filter((output) => output !== null);
314
-
315
- if (!fee) {
316
- fee =
317
- inputs.reduce((acc, input) => acc + input.value, 0) -
318
- outputs.reduce((acc, output) => acc + parseInt(output.amount), 0);
319
- }
320
-
321
- // Include the fee in the transaction object for the vault to use
322
- let unsignedTx = {
323
- inputs: preparedInputs,
324
- outputs: preparedOutputs,
325
- memo,
326
- fee: fee.toString(), // Add the calculated fee to the returned object
327
- };
328
- //console.log(tag, 'unsignedTx:', unsignedTx);
329
-
330
- return unsignedTx;
331
- } catch (error) {
332
- //console.log(tag, 'Error:', error);
333
- throw error;
334
- }
335
- }
336
-
337
- function transformInput(input) {
338
- const {
339
- txid,
340
- vout,
341
- value,
342
- address,
343
- height,
344
- confirmations,
345
- path,
346
- scriptType,
347
- hex: txHex,
348
- tx,
349
- coin,
350
- network,
351
- } = input;
352
-
353
- return {
354
- address,
355
- hash: txid,
356
- index: vout,
357
- value: parseInt(value),
358
- height,
359
- scriptType,
360
- confirmations,
361
- path,
362
- txHex,
363
- tx,
364
- coin,
365
- network,
366
- witnessUtxo: {
367
- value: parseInt(tx.vout[0].value),
368
- script: Buffer.from(tx.vout[0].scriptPubKey.hex, 'hex'),
369
- },
370
- };
371
- }
372
-
373
- function getScriptTypeFromXpub(xpub: string): string {
374
- if (xpub.startsWith('xpub')) {
375
- return 'p2pkh'; // Legacy
376
- } else if (xpub.startsWith('ypub')) {
377
- return 'p2sh'; // P2WPKH nested in P2SH
378
- } else if (xpub.startsWith('zpub')) {
379
- return 'p2wpkh'; // Native SegWit
380
- } else {
381
- // Default fallback
382
- return 'p2pkh';
383
- }
384
- }