@pioneer-platform/pioneer-sdk 4.20.0 → 4.20.1
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 +593 -445
- package/dist/index.es.js +594 -445
- package/dist/index.js +594 -445
- package/package.json +2 -2
- package/src/TransactionManager.ts +21 -4
- package/src/fees/index.ts +60 -57
- package/src/fees/index.ts.backup +510 -0
- package/src/getPubkey.ts +57 -26
- package/src/index.ts +391 -61
- package/src/txbuilder/createUnsignedEvmTx.ts +118 -63
- package/src/txbuilder/createUnsignedRippleTx.ts +12 -11
- package/src/txbuilder/createUnsignedStakingTx.ts +13 -11
- package/src/txbuilder/createUnsignedTendermintTx.ts +41 -17
- package/src/txbuilder/createUnsignedUxtoTx.ts +122 -57
- package/src/txbuilder/createUnsignedUxtoTx.ts.backup +384 -0
- package/src/txbuilder/templates/mayachain.ts +2 -0
|
@@ -98,7 +98,7 @@ export async function createUnsignedEvmTx(
|
|
|
98
98
|
memo,
|
|
99
99
|
pubkeys,
|
|
100
100
|
pioneer,
|
|
101
|
-
|
|
101
|
+
pubkeyContext,
|
|
102
102
|
isMax,
|
|
103
103
|
feeLevel = 5, // Added feeLevel parameter with default of 5 (average)
|
|
104
104
|
) {
|
|
@@ -112,6 +112,11 @@ export async function createUnsignedEvmTx(
|
|
|
112
112
|
// Extract chainId from networkId
|
|
113
113
|
const chainId = extractChainIdFromNetworkId(networkId);
|
|
114
114
|
|
|
115
|
+
// Use the passed pubkeyContext directly - it's already been set by Pioneer SDK
|
|
116
|
+
if (!pubkeyContext) {
|
|
117
|
+
throw new Error(`No pubkey context provided for networkId: ${networkId}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
// Check if context is valid for this network
|
|
116
121
|
const isValidForNetwork = (pubkey: any) => {
|
|
117
122
|
if (!pubkey?.networks) return false;
|
|
@@ -122,66 +127,116 @@ export async function createUnsignedEvmTx(
|
|
|
122
127
|
// For non-EVM, check exact match
|
|
123
128
|
return pubkey.networks.includes(networkId);
|
|
124
129
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
console.log(tag, 'No context set - auto-selecting first matching pubkey');
|
|
129
|
-
keepKeySdk.pubkeyContext = pubkeys.find(pk => isValidForNetwork(pk));
|
|
130
|
-
} else {
|
|
131
|
-
// We have a context - check if it's valid for this network
|
|
132
|
-
console.log(tag, 'Current context networks:', keepKeySdk.pubkeyContext.networks, 'For networkId:', networkId);
|
|
133
|
-
|
|
134
|
-
if (!isValidForNetwork(keepKeySdk.pubkeyContext)) {
|
|
135
|
-
// Context exists but wrong network - auto-correct
|
|
136
|
-
console.log(tag, 'Auto-correcting context - wrong network detected');
|
|
137
|
-
keepKeySdk.pubkeyContext = pubkeys.find(pk => isValidForNetwork(pk));
|
|
138
|
-
} else {
|
|
139
|
-
console.log(tag, 'Context is valid for this network - using existing context');
|
|
140
|
-
}
|
|
130
|
+
|
|
131
|
+
if (!isValidForNetwork(pubkeyContext)) {
|
|
132
|
+
throw new Error(`Pubkey context is for wrong network. Expected ${networkId}, got ${pubkeyContext.networks}`);
|
|
141
133
|
}
|
|
142
|
-
|
|
143
|
-
const address =
|
|
144
|
-
console.log(tag, '✅ Using FROM address from pubkeyContext:', address, 'note:',
|
|
134
|
+
|
|
135
|
+
const address = pubkeyContext.address || pubkeyContext.pubkey;
|
|
136
|
+
console.log(tag, '✅ Using FROM address from pubkeyContext:', address, 'note:', pubkeyContext.note);
|
|
145
137
|
if (!address) throw new Error('No address found for the specified network');
|
|
146
138
|
|
|
147
|
-
// Fetch gas price
|
|
148
|
-
// Note: In the future, we should fetch different gas prices for different fee levels
|
|
149
|
-
// For now, we'll use a multiplier on the base gas price
|
|
139
|
+
// Fetch gas price and convert to wei
|
|
150
140
|
const gasPriceData = await pioneer.GetGasPriceByNetwork({ networkId });
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// Check if the returned value is reasonable (in wei or gwei)
|
|
154
|
-
// If it's less than 1 gwei (1e9 wei), it's probably already in wei but too low
|
|
155
|
-
// For mainnet, we need at least 10-30 gwei typically
|
|
156
|
-
const MIN_GAS_PRICE_WEI = BigInt(10e9); // 10 gwei minimum for mainnet
|
|
157
|
-
|
|
158
|
-
if (BigInt(gasPriceData.data) < MIN_GAS_PRICE_WEI) {
|
|
159
|
-
// The API is returning a value that's way too low (like 0.296 gwei)
|
|
160
|
-
// Use a reasonable default for mainnet
|
|
161
|
-
console.log(tag, 'Gas price from API too low:', gasPriceData.data, 'wei - using minimum:', MIN_GAS_PRICE_WEI.toString());
|
|
162
|
-
baseGasPrice = MIN_GAS_PRICE_WEI;
|
|
163
|
-
} else {
|
|
164
|
-
baseGasPrice = BigInt(gasPriceData.data);
|
|
165
|
-
}
|
|
141
|
+
console.log(tag, 'Gas price data from API:', JSON.stringify(gasPriceData.data));
|
|
166
142
|
|
|
167
|
-
// Apply fee level multiplier
|
|
168
|
-
// feeLevel: 1 = slow (80% of base), 5 = average (100%), 9 = fast (150%)
|
|
169
143
|
let gasPrice: bigint;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
144
|
+
|
|
145
|
+
// Default fallback gas prices by chain ID (in gwei)
|
|
146
|
+
const defaultGasPrices: Record<number, number> = {
|
|
147
|
+
1: 30, // Ethereum mainnet
|
|
148
|
+
56: 3, // BSC
|
|
149
|
+
137: 50, // Polygon
|
|
150
|
+
43114: 25, // Avalanche
|
|
151
|
+
8453: 0.1, // Base
|
|
152
|
+
10: 0.1, // Optimism
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const fallbackGasGwei = defaultGasPrices[chainId] || 20; // Default 20 gwei
|
|
156
|
+
const MIN_GAS_PRICE_WEI = BigInt(fallbackGasGwei * 1e9);
|
|
157
|
+
|
|
158
|
+
// Check if API returned an object with fee levels or a single value
|
|
159
|
+
if (typeof gasPriceData.data === 'object' && gasPriceData.data !== null && !Array.isArray(gasPriceData.data)) {
|
|
160
|
+
// API returned object with fee levels (e.g., { slow, average, fastest })
|
|
161
|
+
let selectedGasPrice: string | number | undefined;
|
|
162
|
+
|
|
163
|
+
if (feeLevel <= 2) {
|
|
164
|
+
// Slow
|
|
165
|
+
selectedGasPrice = gasPriceData.data.slow || gasPriceData.data.average || gasPriceData.data.fastest;
|
|
166
|
+
console.log(tag, 'Selecting SLOW gas price from API');
|
|
167
|
+
} else if (feeLevel >= 8) {
|
|
168
|
+
// Fast
|
|
169
|
+
selectedGasPrice = gasPriceData.data.fastest || gasPriceData.data.fast || gasPriceData.data.average;
|
|
170
|
+
console.log(tag, 'Selecting FAST gas price from API');
|
|
171
|
+
} else {
|
|
172
|
+
// Average
|
|
173
|
+
selectedGasPrice = gasPriceData.data.average || gasPriceData.data.fast || gasPriceData.data.fastest;
|
|
174
|
+
console.log(tag, 'Selecting AVERAGE gas price from API');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Convert to number and validate
|
|
178
|
+
let gasPriceNum: number;
|
|
179
|
+
if (selectedGasPrice === undefined || selectedGasPrice === null) {
|
|
180
|
+
console.warn(tag, 'No valid gas price found in API response, using fallback:', fallbackGasGwei, 'gwei');
|
|
181
|
+
gasPriceNum = fallbackGasGwei;
|
|
182
|
+
} else {
|
|
183
|
+
gasPriceNum = typeof selectedGasPrice === 'string' ? parseFloat(selectedGasPrice) : selectedGasPrice;
|
|
184
|
+
|
|
185
|
+
// Check for NaN
|
|
186
|
+
if (isNaN(gasPriceNum) || !isFinite(gasPriceNum)) {
|
|
187
|
+
console.warn(tag, 'Invalid gas price (NaN or Infinite):', selectedGasPrice, '- using fallback:', fallbackGasGwei, 'gwei');
|
|
188
|
+
gasPriceNum = fallbackGasGwei;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Assume API returns gwei, convert to wei
|
|
193
|
+
gasPrice = BigInt(Math.round(gasPriceNum * 1e9));
|
|
194
|
+
|
|
195
|
+
// Apply minimum gas price if too low
|
|
196
|
+
if (gasPrice < MIN_GAS_PRICE_WEI) {
|
|
197
|
+
console.warn(tag, 'Gas price from API too low:', gasPrice.toString(), 'wei - using minimum:', MIN_GAS_PRICE_WEI.toString());
|
|
198
|
+
gasPrice = MIN_GAS_PRICE_WEI;
|
|
199
|
+
}
|
|
178
200
|
} else {
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
// API returned a single value or something unexpected
|
|
202
|
+
let gasPriceNum: number;
|
|
203
|
+
|
|
204
|
+
if (gasPriceData.data === undefined || gasPriceData.data === null) {
|
|
205
|
+
console.warn(tag, 'Gas price API returned null/undefined, using fallback:', fallbackGasGwei, 'gwei');
|
|
206
|
+
gasPriceNum = fallbackGasGwei;
|
|
207
|
+
} else {
|
|
208
|
+
gasPriceNum = typeof gasPriceData.data === 'string' ? parseFloat(gasPriceData.data) : gasPriceData.data;
|
|
209
|
+
|
|
210
|
+
// Check for NaN
|
|
211
|
+
if (isNaN(gasPriceNum) || !isFinite(gasPriceNum)) {
|
|
212
|
+
console.warn(tag, 'Invalid gas price (NaN or Infinite):', gasPriceData.data, '- using fallback:', fallbackGasGwei, 'gwei');
|
|
213
|
+
gasPriceNum = fallbackGasGwei;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Assume API returns gwei, convert to wei
|
|
218
|
+
const baseGasPrice = BigInt(Math.round(gasPriceNum * 1e9));
|
|
219
|
+
|
|
220
|
+
// Apply fee level multiplier
|
|
221
|
+
if (feeLevel <= 2) {
|
|
222
|
+
gasPrice = (baseGasPrice * BigInt(80)) / BigInt(100);
|
|
223
|
+
console.log(tag, 'Using SLOW gas price (80% of base)');
|
|
224
|
+
} else if (feeLevel >= 8) {
|
|
225
|
+
gasPrice = (baseGasPrice * BigInt(150)) / BigInt(100);
|
|
226
|
+
console.log(tag, 'Using FAST gas price (150% of base)');
|
|
227
|
+
} else {
|
|
228
|
+
gasPrice = baseGasPrice;
|
|
229
|
+
console.log(tag, 'Using AVERAGE gas price (100% of base)');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Apply minimum gas price if too low
|
|
233
|
+
if (gasPrice < MIN_GAS_PRICE_WEI) {
|
|
234
|
+
console.warn(tag, 'Gas price too low:', gasPrice.toString(), 'wei - using minimum:', MIN_GAS_PRICE_WEI.toString());
|
|
235
|
+
gasPrice = MIN_GAS_PRICE_WEI;
|
|
236
|
+
}
|
|
182
237
|
}
|
|
183
|
-
|
|
184
|
-
console.log(tag, '
|
|
238
|
+
|
|
239
|
+
console.log(tag, 'Final gasPrice:', gasPrice.toString(), 'wei (', Number(gasPrice) / 1e9, 'gwei)');
|
|
185
240
|
|
|
186
241
|
let nonce;
|
|
187
242
|
try {
|
|
@@ -501,22 +556,22 @@ export async function createUnsignedEvmTx(
|
|
|
501
556
|
|
|
502
557
|
// Address path for hardware wallets - use the path from the pubkey context
|
|
503
558
|
// The pubkey context should have either addressNListMaster or pathMaster
|
|
504
|
-
if (
|
|
559
|
+
if (pubkeyContext.addressNListMaster) {
|
|
505
560
|
// Direct use if we have addressNListMaster
|
|
506
|
-
unsignedTx.addressNList =
|
|
561
|
+
unsignedTx.addressNList = pubkeyContext.addressNListMaster;
|
|
507
562
|
console.log(tag, '✅ Using addressNListMaster from pubkey context:', unsignedTx.addressNList, 'for address:', address);
|
|
508
|
-
} else if (
|
|
563
|
+
} else if (pubkeyContext.pathMaster) {
|
|
509
564
|
// Convert BIP32 path to addressNList if we have pathMaster
|
|
510
|
-
unsignedTx.addressNList = bip32ToAddressNList(
|
|
511
|
-
console.log(tag, '✅ Converted pathMaster to addressNList:',
|
|
512
|
-
} else if (
|
|
565
|
+
unsignedTx.addressNList = bip32ToAddressNList(pubkeyContext.pathMaster);
|
|
566
|
+
console.log(tag, '✅ Converted pathMaster to addressNList:', pubkeyContext.pathMaster, '→', unsignedTx.addressNList);
|
|
567
|
+
} else if (pubkeyContext.addressNList) {
|
|
513
568
|
// Use addressNList if available (but this would be the non-master path)
|
|
514
|
-
unsignedTx.addressNList =
|
|
569
|
+
unsignedTx.addressNList = pubkeyContext.addressNList;
|
|
515
570
|
console.log(tag, '✅ Using addressNList from pubkey context:', unsignedTx.addressNList);
|
|
516
|
-
} else if (
|
|
571
|
+
} else if (pubkeyContext.path) {
|
|
517
572
|
// Last resort - convert regular path to addressNList
|
|
518
|
-
unsignedTx.addressNList = bip32ToAddressNList(
|
|
519
|
-
console.log(tag, '⚠️ Using regular path (not master):',
|
|
573
|
+
unsignedTx.addressNList = bip32ToAddressNList(pubkeyContext.path);
|
|
574
|
+
console.log(tag, '⚠️ Using regular path (not master):', pubkeyContext.path, '→', unsignedTx.addressNList);
|
|
520
575
|
} else {
|
|
521
576
|
// Fallback to default account 0
|
|
522
577
|
unsignedTx.addressNList = [0x80000000 + 44, 0x80000000 + 60, 0x80000000, 0, 0];
|
|
@@ -15,7 +15,7 @@ export async function createUnsignedRippleTx(
|
|
|
15
15
|
memo: string,
|
|
16
16
|
pubkeys: any,
|
|
17
17
|
pioneer: any,
|
|
18
|
-
|
|
18
|
+
pubkeyContext: any,
|
|
19
19
|
isMax: boolean,
|
|
20
20
|
): Promise<any> {
|
|
21
21
|
let tag = TAG + ' | createUnsignedRippleTx | ';
|
|
@@ -25,20 +25,21 @@ export async function createUnsignedRippleTx(
|
|
|
25
25
|
|
|
26
26
|
// Determine networkId from caip
|
|
27
27
|
const networkId = caipToNetworkId(caip);
|
|
28
|
-
//console.log(tag, 'networkId:', networkId);
|
|
29
28
|
|
|
30
|
-
//
|
|
31
|
-
if (!
|
|
32
|
-
|
|
33
|
-
pk.networks?.includes(networkId)
|
|
34
|
-
);
|
|
29
|
+
// Use the passed pubkeyContext directly - it's already been set by Pioneer SDK
|
|
30
|
+
if (!pubkeyContext) {
|
|
31
|
+
throw new Error(`No pubkey context provided for networkId: ${networkId}`);
|
|
35
32
|
}
|
|
36
|
-
|
|
37
|
-
if (!
|
|
38
|
-
throw new Error(`
|
|
33
|
+
|
|
34
|
+
if (!pubkeyContext.networks?.includes(networkId)) {
|
|
35
|
+
throw new Error(`Pubkey context is for wrong network. Expected ${networkId}, got ${pubkeyContext.networks}`);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
console.log(tag, `✅ Using pubkeyContext for network ${networkId}:`, {
|
|
39
|
+
address: pubkeyContext.address,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const fromAddress = pubkeyContext.address || pubkeyContext.pubkey;
|
|
42
43
|
|
|
43
44
|
let accountInfo = await pioneer.GetAccountInfo({
|
|
44
45
|
address: fromAddress,
|
|
@@ -24,7 +24,7 @@ export async function createUnsignedStakingTx(
|
|
|
24
24
|
params: StakingTxParams,
|
|
25
25
|
pubkeys: any[],
|
|
26
26
|
pioneer: any,
|
|
27
|
-
|
|
27
|
+
pubkeyContext: any,
|
|
28
28
|
): Promise<any> {
|
|
29
29
|
const tag = TAG + ' | createUnsignedStakingTx | ';
|
|
30
30
|
|
|
@@ -32,18 +32,20 @@ export async function createUnsignedStakingTx(
|
|
|
32
32
|
if (!pioneer) throw new Error('Failed to init! pioneer');
|
|
33
33
|
|
|
34
34
|
const networkId = caipToNetworkId(caip);
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
if (!
|
|
38
|
-
|
|
39
|
-
pk.networks?.includes(networkId)
|
|
40
|
-
);
|
|
35
|
+
|
|
36
|
+
// Use the passed pubkeyContext directly - it's already been set by Pioneer SDK
|
|
37
|
+
if (!pubkeyContext) {
|
|
38
|
+
throw new Error(`No pubkey context provided for networkId: ${networkId}`);
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
if (!
|
|
44
|
-
throw new Error(`
|
|
40
|
+
|
|
41
|
+
if (!pubkeyContext.networks?.includes(networkId)) {
|
|
42
|
+
throw new Error(`Pubkey context is for wrong network. Expected ${networkId}, got ${pubkeyContext.networks}`);
|
|
45
43
|
}
|
|
46
44
|
|
|
45
|
+
console.log(tag, `✅ Using pubkeyContext for network ${networkId}:`, {
|
|
46
|
+
address: pubkeyContext.address,
|
|
47
|
+
});
|
|
48
|
+
|
|
47
49
|
// Map networkId to chain and get network-specific configs
|
|
48
50
|
let chain: string;
|
|
49
51
|
let chainId: string;
|
|
@@ -78,7 +80,7 @@ export async function createUnsignedStakingTx(
|
|
|
78
80
|
|
|
79
81
|
console.log(tag, `Building ${params.type} transaction for ${chain}`);
|
|
80
82
|
|
|
81
|
-
const fromAddress =
|
|
83
|
+
const fromAddress = pubkeyContext.address || pubkeyContext.pubkey;
|
|
82
84
|
|
|
83
85
|
// Get account info
|
|
84
86
|
const accountInfo = (await pioneer.GetAccountInfo({ network: chain, address: fromAddress }))
|
|
@@ -14,7 +14,7 @@ export async function createUnsignedTendermintTx(
|
|
|
14
14
|
memo: string,
|
|
15
15
|
pubkeys: any[],
|
|
16
16
|
pioneer: any,
|
|
17
|
-
|
|
17
|
+
pubkeyContext: any,
|
|
18
18
|
isMax: boolean,
|
|
19
19
|
to?: string,
|
|
20
20
|
): Promise<any> {
|
|
@@ -24,18 +24,22 @@ export async function createUnsignedTendermintTx(
|
|
|
24
24
|
if (!pioneer) throw new Error('Failed to init! pioneer');
|
|
25
25
|
|
|
26
26
|
const networkId = caipToNetworkId(caip);
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
);
|
|
27
|
+
|
|
28
|
+
// Use the passed pubkeyContext directly - it's already been set by Pioneer SDK
|
|
29
|
+
// No need to auto-correct anymore since context management is centralized
|
|
30
|
+
if (!pubkeyContext) {
|
|
31
|
+
throw new Error(`No pubkey context provided for networkId: ${networkId}`);
|
|
33
32
|
}
|
|
34
|
-
|
|
35
|
-
if (!
|
|
36
|
-
throw new Error(`
|
|
33
|
+
|
|
34
|
+
if (!pubkeyContext.networks?.includes(networkId)) {
|
|
35
|
+
throw new Error(`Pubkey context is for wrong network. Expected ${networkId}, got ${pubkeyContext.networks}`);
|
|
37
36
|
}
|
|
38
37
|
|
|
38
|
+
console.log(tag, `✅ Using pubkeyContext for network ${networkId}:`, {
|
|
39
|
+
address: pubkeyContext.address,
|
|
40
|
+
addressNList: pubkeyContext.addressNList || pubkeyContext.addressNListMaster
|
|
41
|
+
});
|
|
42
|
+
|
|
39
43
|
// Map networkId to a human-readable chain
|
|
40
44
|
let chain: string;
|
|
41
45
|
switch (networkId) {
|
|
@@ -57,13 +61,16 @@ export async function createUnsignedTendermintTx(
|
|
|
57
61
|
|
|
58
62
|
//console.log(tag, `Resolved chain: ${chain} for networkId: ${networkId}`);
|
|
59
63
|
|
|
60
|
-
const fromAddress =
|
|
64
|
+
const fromAddress = pubkeyContext.address || pubkeyContext.pubkey;
|
|
61
65
|
let asset = caip.split(':')[1]; // Assuming format is "network:asset"
|
|
66
|
+
|
|
67
|
+
console.log(tag, `🔍 Fetching account info for address: ${fromAddress}`);
|
|
62
68
|
const accountInfo = (await pioneer.GetAccountInfo({ network: chain, address: fromAddress }))
|
|
63
69
|
.data;
|
|
64
|
-
|
|
70
|
+
console.log(tag, '📋 accountInfo:', JSON.stringify(accountInfo, null, 2));
|
|
71
|
+
|
|
65
72
|
let balanceInfo = await pioneer.GetPubkeyBalance({ asset: chain, pubkey: fromAddress });
|
|
66
|
-
|
|
73
|
+
console.log(tag, `💰 balanceInfo:`, balanceInfo);
|
|
67
74
|
|
|
68
75
|
let account_number, sequence;
|
|
69
76
|
if (networkId === 'cosmos:cosmoshub-4' || networkId === 'cosmos:osmosis-1') {
|
|
@@ -77,9 +84,22 @@ export async function createUnsignedTendermintTx(
|
|
|
77
84
|
sequence = accountInfo.result.value.sequence || '0';
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
console.log(tag, `📊 Extracted account_number: ${account_number}, sequence: ${sequence}`);
|
|
88
|
+
|
|
89
|
+
// CRITICAL: Pioneer API may return stale data. The mayachain-network module already
|
|
90
|
+
// queries multiple nodes directly and uses consensus - but we're using the Pioneer API wrapper
|
|
91
|
+
// which adds caching. If we get account_number 0, warn about potential Pioneer API cache issue.
|
|
92
|
+
if (account_number === '0' || account_number === 0) {
|
|
93
|
+
console.log(tag, `⚠️ WARNING: Account number is 0 from Pioneer API`);
|
|
94
|
+
console.log(tag, ` This is likely due to stale Pioneer API cache`);
|
|
95
|
+
console.log(tag, ` The mayachain-network module queries nodes directly but Pioneer API may be cached`);
|
|
96
|
+
console.log(tag, ` Proceeding with account_number: 0 but transaction will likely fail`);
|
|
97
|
+
console.log(tag, ` TODO: Fix Pioneer API to use fresh data from mayachain-network module`);
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
const fees = {
|
|
81
101
|
'cosmos:thorchain-mainnet-v1': 0.02,
|
|
82
|
-
'cosmos:mayachain-mainnet-v1': 0.
|
|
102
|
+
'cosmos:mayachain-mainnet-v1': 0, // Increased to 0.5 CACAO (5000000000 base units) for reliable confirmation
|
|
83
103
|
'cosmos:cosmoshub-4': 0.005,
|
|
84
104
|
'cosmos:osmosis-1': 0.035,
|
|
85
105
|
};
|
|
@@ -147,11 +167,14 @@ export async function createUnsignedTendermintTx(
|
|
|
147
167
|
throw new Error(`Unsupported Maya chain CAIP: ${caip}`);
|
|
148
168
|
}
|
|
149
169
|
|
|
170
|
+
// MAYA token uses 1e4 (4 decimals), CACAO uses 1e10 (10 decimals)
|
|
171
|
+
const decimals = mayaAsset === 'maya' ? 1e4 : 1e10;
|
|
172
|
+
|
|
150
173
|
if (isMax) {
|
|
151
|
-
const fee = Math.floor(fees[networkId] *
|
|
152
|
-
amount = Math.max(0, Math.floor(balanceInfo.data *
|
|
174
|
+
const fee = Math.floor(fees[networkId] * decimals); // Convert fee to smallest unit and floor to int
|
|
175
|
+
amount = Math.max(0, Math.floor(balanceInfo.data * decimals) - fee); // Floor to ensure no decimals
|
|
153
176
|
} else {
|
|
154
|
-
amount = Math.max(Math.floor(amount *
|
|
177
|
+
amount = Math.max(Math.floor(amount * decimals), 0); // Floor the multiplication result
|
|
155
178
|
}
|
|
156
179
|
|
|
157
180
|
//console.log(tag, `amount: ${amount}, isMax: ${isMax}, fee: ${fees[networkId]}, asset: ${mayaAsset}`);
|
|
@@ -174,6 +197,7 @@ export async function createUnsignedTendermintTx(
|
|
|
174
197
|
amount: amount.toString(),
|
|
175
198
|
memo,
|
|
176
199
|
sequence,
|
|
200
|
+
addressNList: pubkeyContext.addressNList || pubkeyContext.addressNListMaster, // CRITICAL: Use correct derivation path for signing
|
|
177
201
|
})
|
|
178
202
|
: mayachainDepositTemplate({
|
|
179
203
|
account_number,
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { caipToNetworkId, NetworkIdToChain } from '@pioneer-platform/pioneer-caip';
|
|
2
|
-
import { bip32ToAddressNList } from '@pioneer-platform/pioneer-coins';
|
|
2
|
+
import { bip32ToAddressNList, SLIP_44_BY_LONG, COIN_MAP_LONG } from '@pioneer-platform/pioneer-coins';
|
|
3
|
+
import coinSelect from 'coinselect';
|
|
4
|
+
import coinSelectSplit from 'coinselect/split';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get SLIP-44 coin type for a given network ID
|
|
8
|
+
* @param networkId - CAIP network identifier
|
|
9
|
+
* @returns SLIP-44 coin type number
|
|
10
|
+
*/
|
|
11
|
+
function getCoinTypeFromNetworkId(networkId: string): number {
|
|
12
|
+
const chain = NetworkIdToChain[networkId];
|
|
13
|
+
if (!chain) {
|
|
14
|
+
console.warn(`No chain mapping found for ${networkId}, defaulting to Bitcoin coin type 0`);
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const blockchainName = COIN_MAP_LONG[chain]?.toLowerCase();
|
|
19
|
+
if (!blockchainName) {
|
|
20
|
+
console.warn(`No blockchain name found for chain ${chain}, defaulting to Bitcoin coin type 0`);
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const coinType = SLIP_44_BY_LONG[blockchainName];
|
|
25
|
+
if (coinType === undefined) {
|
|
26
|
+
console.warn(`No SLIP-44 coin type found for ${blockchainName}, defaulting to Bitcoin coin type 0`);
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return coinType;
|
|
31
|
+
}
|
|
3
32
|
|
|
4
33
|
export async function createUnsignedUxtoTx(
|
|
5
34
|
caip: string,
|
|
@@ -8,7 +37,7 @@ export async function createUnsignedUxtoTx(
|
|
|
8
37
|
memo: string,
|
|
9
38
|
pubkeys: any,
|
|
10
39
|
pioneer: any,
|
|
11
|
-
|
|
40
|
+
pubkeyContext: any,
|
|
12
41
|
isMax: boolean, // Added isMax parameter
|
|
13
42
|
feeLevel: number = 5, // Added feeLevel parameter with default of 5 (average)
|
|
14
43
|
changeScriptType?: string, // Added changeScriptType parameter for user preference
|
|
@@ -20,11 +49,20 @@ export async function createUnsignedUxtoTx(
|
|
|
20
49
|
|
|
21
50
|
const networkId = caipToNetworkId(caip);
|
|
22
51
|
|
|
23
|
-
//
|
|
24
|
-
if (!
|
|
25
|
-
|
|
52
|
+
// Use the passed pubkeyContext directly - it's already been set by Pioneer SDK
|
|
53
|
+
if (!pubkeyContext) {
|
|
54
|
+
throw new Error(`No pubkey context provided for networkId: ${networkId}`);
|
|
26
55
|
}
|
|
27
56
|
|
|
57
|
+
if (!pubkeyContext.networks?.includes(networkId)) {
|
|
58
|
+
throw new Error(`Pubkey context is for wrong network. Expected ${networkId}, got ${pubkeyContext.networks}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(tag, `✅ Using pubkeyContext for network ${networkId}:`, {
|
|
62
|
+
address: pubkeyContext.address,
|
|
63
|
+
scriptType: pubkeyContext.scriptType,
|
|
64
|
+
});
|
|
65
|
+
|
|
28
66
|
// For UTXO, we still need all relevant pubkeys to aggregate UTXOs
|
|
29
67
|
const relevantPubkeys = pubkeys.filter(
|
|
30
68
|
(e) => e.networks && Array.isArray(e.networks) && e.networks.includes(networkId),
|
|
@@ -40,57 +78,11 @@ export async function createUnsignedUxtoTx(
|
|
|
40
78
|
|
|
41
79
|
let chain = NetworkIdToChain[networkId];
|
|
42
80
|
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
relevantPubkeys.find((pk) => pk.scriptType === 'p2wpkh')?.scriptType || // Prefer native segwit if available
|
|
47
|
-
relevantPubkeys[0].scriptType || // Fall back to first available
|
|
48
|
-
'p2wpkh'; // Ultimate default
|
|
49
|
-
console.log(`${tag}: Using change script type: ${actualChangeScriptType}`);
|
|
50
|
-
|
|
51
|
-
// Find the xpub that matches the desired script type
|
|
52
|
-
const changeXpub =
|
|
53
|
-
relevantPubkeys.find((pk) => pk.scriptType === actualChangeScriptType)?.pubkey ||
|
|
54
|
-
relevantPubkeys.find((pk) => pk.scriptType === 'p2wpkh')?.pubkey || // Fall back to native segwit
|
|
55
|
-
relevantPubkeys[0].pubkey; // Last resort: use first available
|
|
56
|
-
|
|
57
|
-
console.log(
|
|
58
|
-
`${tag}: Change xpub selected for ${actualChangeScriptType}:`,
|
|
59
|
-
changeXpub?.substring(0, 10) + '...',
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
let changeAddressIndex = await pioneer.GetChangeAddress({
|
|
63
|
-
network: chain,
|
|
64
|
-
xpub: changeXpub,
|
|
65
|
-
});
|
|
66
|
-
changeAddressIndex = changeAddressIndex.data.changeIndex;
|
|
67
|
-
|
|
68
|
-
// Determine BIP path based on script type
|
|
69
|
-
let bipPath: string;
|
|
70
|
-
switch (actualChangeScriptType) {
|
|
71
|
-
case 'p2pkh':
|
|
72
|
-
bipPath = `m/44'/0'/0'/1/${changeAddressIndex}`; // BIP44 for legacy
|
|
73
|
-
break;
|
|
74
|
-
case 'p2sh-p2wpkh':
|
|
75
|
-
bipPath = `m/49'/0'/0'/1/${changeAddressIndex}`; // BIP49 for wrapped segwit
|
|
76
|
-
break;
|
|
77
|
-
case 'p2wpkh':
|
|
78
|
-
default:
|
|
79
|
-
bipPath = `m/84'/0'/0'/1/${changeAddressIndex}`; // BIP84 for native segwit
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const path = bipPath;
|
|
84
|
-
console.log(`${tag}: Change address path: ${path} (index: ${changeAddressIndex})`);
|
|
85
|
-
|
|
86
|
-
const changeAddress = {
|
|
87
|
-
path: path,
|
|
88
|
-
isChange: true,
|
|
89
|
-
index: changeAddressIndex,
|
|
90
|
-
addressNList: bip32ToAddressNList(path),
|
|
91
|
-
scriptType: actualChangeScriptType, // Store the script type with the change address
|
|
92
|
-
};
|
|
81
|
+
// Get correct coin type for this network
|
|
82
|
+
const coinType = getCoinTypeFromNetworkId(networkId);
|
|
83
|
+
console.log(`${tag}: Using SLIP-44 coin type ${coinType} for ${chain}`);
|
|
93
84
|
|
|
85
|
+
// Collect UTXOs first to determine the appropriate change address type
|
|
94
86
|
const utxos: any[] = [];
|
|
95
87
|
for (const pubkey of relevantPubkeys) {
|
|
96
88
|
//console.log('pubkey: ',pubkey)
|
|
@@ -129,6 +121,81 @@ export async function createUnsignedUxtoTx(
|
|
|
129
121
|
utxo.value = Number(utxo.value);
|
|
130
122
|
}
|
|
131
123
|
|
|
124
|
+
// NOW determine change script type based on collected UTXOs
|
|
125
|
+
// Find the most common script type among input UTXOs to maintain consistency
|
|
126
|
+
const inputScriptTypes = utxos.map((u) => u.scriptType).filter(Boolean);
|
|
127
|
+
const scriptTypeCount: Record<string, number> = inputScriptTypes.reduce((acc, type) => {
|
|
128
|
+
acc[type] = (acc[type] || 0) + 1;
|
|
129
|
+
return acc;
|
|
130
|
+
}, {} as Record<string, number>);
|
|
131
|
+
|
|
132
|
+
// Get the most common input script type
|
|
133
|
+
const mostCommonInputType =
|
|
134
|
+
Object.entries(scriptTypeCount).sort(([, a], [, b]) => b - a)[0]?.[0] || 'p2pkh';
|
|
135
|
+
|
|
136
|
+
// Determine the change script type - priority order:
|
|
137
|
+
// 1. User explicit preference (changeScriptType parameter)
|
|
138
|
+
// 2. Most common input script type (maintain consistency)
|
|
139
|
+
// 3. First available pubkey script type
|
|
140
|
+
// 4. Default to p2pkh (legacy)
|
|
141
|
+
const actualChangeScriptType =
|
|
142
|
+
changeScriptType ||
|
|
143
|
+
mostCommonInputType ||
|
|
144
|
+
relevantPubkeys[0]?.scriptType ||
|
|
145
|
+
'p2pkh';
|
|
146
|
+
|
|
147
|
+
console.log(`${tag}: Input script types:`, scriptTypeCount);
|
|
148
|
+
console.log(`${tag}: Using change script type: ${actualChangeScriptType} (matches inputs: ${mostCommonInputType})`);
|
|
149
|
+
|
|
150
|
+
// Find and validate the xpub that matches the desired script type
|
|
151
|
+
const changeXpubInfo = relevantPubkeys.find((pk) => pk.scriptType === actualChangeScriptType);
|
|
152
|
+
if (!changeXpubInfo) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`No ${actualChangeScriptType} xpub available for change address. ` +
|
|
155
|
+
`Available types: ${relevantPubkeys.map((pk) => pk.scriptType).join(', ')}. ` +
|
|
156
|
+
`Cannot create change output with mismatched script type.`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const changeXpub = changeXpubInfo.pubkey;
|
|
161
|
+
console.log(
|
|
162
|
+
`${tag}: Change xpub selected for ${actualChangeScriptType}:`,
|
|
163
|
+
changeXpub?.substring(0, 10) + '...',
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Get change address index from Pioneer API
|
|
167
|
+
let changeAddressIndex = await pioneer.GetChangeAddress({
|
|
168
|
+
network: chain,
|
|
169
|
+
xpub: changeXpub,
|
|
170
|
+
});
|
|
171
|
+
changeAddressIndex = changeAddressIndex.data.changeIndex;
|
|
172
|
+
|
|
173
|
+
// Determine BIP path based on script type AND coin type
|
|
174
|
+
let bipPath: string;
|
|
175
|
+
switch (actualChangeScriptType) {
|
|
176
|
+
case 'p2pkh':
|
|
177
|
+
bipPath = `m/44'/${coinType}'/0'/1/${changeAddressIndex}`; // BIP44 for legacy
|
|
178
|
+
break;
|
|
179
|
+
case 'p2sh-p2wpkh':
|
|
180
|
+
bipPath = `m/49'/${coinType}'/0'/1/${changeAddressIndex}`; // BIP49 for wrapped segwit
|
|
181
|
+
break;
|
|
182
|
+
case 'p2wpkh':
|
|
183
|
+
default:
|
|
184
|
+
bipPath = `m/84'/${coinType}'/0'/1/${changeAddressIndex}`; // BIP84 for native segwit
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const path = bipPath;
|
|
189
|
+
console.log(`${tag}: Change address path: ${path} (coin type: ${coinType}, index: ${changeAddressIndex})`);
|
|
190
|
+
|
|
191
|
+
const changeAddress = {
|
|
192
|
+
path: path,
|
|
193
|
+
isChange: true,
|
|
194
|
+
index: changeAddressIndex,
|
|
195
|
+
addressNList: bip32ToAddressNList(path),
|
|
196
|
+
scriptType: actualChangeScriptType,
|
|
197
|
+
};
|
|
198
|
+
|
|
132
199
|
let feeRateFromNode: any;
|
|
133
200
|
|
|
134
201
|
// HARDCODE DOGE FEES - API is unreliable for DOGE
|
|
@@ -294,12 +361,10 @@ export async function createUnsignedUxtoTx(
|
|
|
294
361
|
if (isMax) {
|
|
295
362
|
//console.log(tag, 'isMax:', isMax);
|
|
296
363
|
// For max send, use coinSelectSplit
|
|
297
|
-
const { default: coinSelectSplit } = await import('coinselect/split');
|
|
298
364
|
result = coinSelectSplit(utxos, [{ address: to }], effectiveFeeRate);
|
|
299
365
|
} else {
|
|
300
366
|
//console.log(tag, 'isMax:', isMax)
|
|
301
367
|
// Regular send
|
|
302
|
-
const { default: coinSelect } = await import('coinselect');
|
|
303
368
|
result = coinSelect(utxos, [{ address: to, value: amount }], effectiveFeeRate);
|
|
304
369
|
}
|
|
305
370
|
|