@pioneer-platform/pioneer-sdk 4.21.0 → 4.21.2
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 +423 -330
- package/dist/index.es.js +423 -330
- package/dist/index.js +423 -330
- package/package.json +1 -1
- package/src/charts/custom-tokens.ts +144 -0
- package/src/charts/evm.ts +5 -0
- package/src/index.ts +19 -10
- package/src/txbuilder/createUnsignedEvmTx.ts +9 -4
- package/src/utils/build-dashboard.ts +10 -0
package/package.json
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Tokens Chart Module
|
|
3
|
+
*
|
|
4
|
+
* Fetches user-defined custom token balances from MongoDB-backed server endpoint
|
|
5
|
+
* Follows the stablecoin chart pattern for network-specific token discovery
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ChartBalance, ChartParams, PortfolioToken } from './types';
|
|
9
|
+
import { hydrateAssetData, checkDuplicateBalance, createBalanceIdentifier } from './utils';
|
|
10
|
+
|
|
11
|
+
const tag = '| charts-custom-tokens |';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fetch custom tokens for a user address across all EVM networks
|
|
15
|
+
*
|
|
16
|
+
* This function queries the server for user-defined custom tokens and fetches their balances.
|
|
17
|
+
* Custom tokens are stored in MongoDB per user address with network-specific configuration.
|
|
18
|
+
*
|
|
19
|
+
* @param params Chart parameters including pioneer client, pubkeys, and blockchains
|
|
20
|
+
* @param balances Existing balances array (to check for duplicates)
|
|
21
|
+
* @returns Promise that resolves when custom tokens are added to balances array
|
|
22
|
+
*/
|
|
23
|
+
export async function fetchCustomTokens(
|
|
24
|
+
params: ChartParams,
|
|
25
|
+
balances: ChartBalance[]
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const { pioneer, pubkeys, blockchains, context } = params;
|
|
28
|
+
|
|
29
|
+
console.log(tag, 'Fetching custom tokens...');
|
|
30
|
+
|
|
31
|
+
// Find the primary EVM address
|
|
32
|
+
const evmPubkey = pubkeys.find(
|
|
33
|
+
(e: any) => e.networks && Array.isArray(e.networks) && e.networks.includes('eip155:*')
|
|
34
|
+
);
|
|
35
|
+
const primaryAddress = evmPubkey?.address || evmPubkey?.master;
|
|
36
|
+
|
|
37
|
+
if (!primaryAddress) {
|
|
38
|
+
console.log(tag, 'No EVM address found, skipping custom tokens');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(tag, 'Using EVM address for custom tokens:', primaryAddress);
|
|
43
|
+
|
|
44
|
+
// Networks that support custom tokens endpoint (all EVM networks)
|
|
45
|
+
const supportedNetworks = blockchains.filter((net: string) => net.startsWith('eip155:'));
|
|
46
|
+
|
|
47
|
+
if (supportedNetworks.length === 0) {
|
|
48
|
+
console.log(tag, 'No EVM networks for custom tokens');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(tag, `Checking custom tokens on ${supportedNetworks.length} networks`);
|
|
53
|
+
|
|
54
|
+
// Fetch custom tokens for each network
|
|
55
|
+
for (const networkId of supportedNetworks) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await pioneer.GetCustomTokens({ networkId, address: primaryAddress });
|
|
58
|
+
const customTokens = response?.data?.tokens || [];
|
|
59
|
+
|
|
60
|
+
console.log(tag, `Found ${customTokens.length} custom tokens on ${networkId}`);
|
|
61
|
+
|
|
62
|
+
// Process each custom token
|
|
63
|
+
for (const token of customTokens) {
|
|
64
|
+
// Convert to ChartBalance format
|
|
65
|
+
const chartBalance = processCustomToken(token, primaryAddress, context, blockchains);
|
|
66
|
+
|
|
67
|
+
// Add if not already in balances (avoid duplicates)
|
|
68
|
+
if (chartBalance && !checkDuplicateBalance(balances, chartBalance.caip, chartBalance.pubkey)) {
|
|
69
|
+
balances.push(chartBalance);
|
|
70
|
+
console.log(tag, `Added custom token: ${chartBalance.symbol} = ${chartBalance.balance}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (error: any) {
|
|
74
|
+
console.error(tag, `Error fetching custom tokens for ${networkId}:`, error.message);
|
|
75
|
+
// Continue with other networks even if one fails
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(tag, `Custom tokens complete. Total balances: ${balances.length}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Process a custom token into ChartBalance format
|
|
84
|
+
* Follows the same pattern as processPortfolioToken in evm.ts
|
|
85
|
+
*/
|
|
86
|
+
function processCustomToken(
|
|
87
|
+
token: PortfolioToken,
|
|
88
|
+
primaryAddress: string,
|
|
89
|
+
context: string,
|
|
90
|
+
blockchains: string[]
|
|
91
|
+
): ChartBalance | null {
|
|
92
|
+
if (!token.assetCaip || !token.networkId) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Extract the networkId from the assetCaip
|
|
97
|
+
let extractedNetworkId = token.networkId;
|
|
98
|
+
|
|
99
|
+
// For tokens with special formats, extract network part
|
|
100
|
+
if (token.assetCaip.includes('/denom:')) {
|
|
101
|
+
const parts = token.assetCaip.split('/denom:');
|
|
102
|
+
if (parts.length > 0) {
|
|
103
|
+
extractedNetworkId = parts[0];
|
|
104
|
+
}
|
|
105
|
+
} else if (token.networkId && token.networkId.includes('/')) {
|
|
106
|
+
extractedNetworkId = token.networkId.split('/')[0];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Accept ALL EIP155 chains (since we have eip155:* pubkey)
|
|
110
|
+
const isEip155 = extractedNetworkId.startsWith('eip155:');
|
|
111
|
+
if (!isEip155 && !blockchains.includes(extractedNetworkId)) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Hydrate token with assetData
|
|
116
|
+
const tokenAssetInfo = hydrateAssetData(token.assetCaip);
|
|
117
|
+
|
|
118
|
+
// Use consistent pubkey
|
|
119
|
+
const tokenPubkey = token.pubkey || primaryAddress;
|
|
120
|
+
|
|
121
|
+
const chartBalance: ChartBalance = {
|
|
122
|
+
context,
|
|
123
|
+
chart: 'pioneer',
|
|
124
|
+
contextType: context.split(':')[0],
|
|
125
|
+
name: tokenAssetInfo?.name || token.token?.name || 'Unknown Custom Token',
|
|
126
|
+
caip: token.assetCaip,
|
|
127
|
+
icon: tokenAssetInfo?.icon || token.token?.icon || '',
|
|
128
|
+
pubkey: tokenPubkey,
|
|
129
|
+
ticker: tokenAssetInfo?.symbol || token.token?.symbol || 'UNK',
|
|
130
|
+
ref: `${context}${token.assetCaip}`,
|
|
131
|
+
identifier: createBalanceIdentifier(token.assetCaip, tokenPubkey),
|
|
132
|
+
networkId: extractedNetworkId,
|
|
133
|
+
symbol: tokenAssetInfo?.symbol || token.token?.symbol || 'UNK',
|
|
134
|
+
type: tokenAssetInfo?.type || 'erc20',
|
|
135
|
+
token: true, // Custom tokens are always tokens
|
|
136
|
+
decimal: tokenAssetInfo?.decimal || token.token?.decimal,
|
|
137
|
+
balance: token.token?.balance?.toString() || '0',
|
|
138
|
+
priceUsd: token.token?.price || 0,
|
|
139
|
+
valueUsd: token.token?.balanceUSD || 0,
|
|
140
|
+
updated: new Date().getTime(),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return chartBalance;
|
|
144
|
+
}
|
package/src/charts/evm.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChartBalance, ChartParams, PortfolioBalance, PortfolioToken } from './types';
|
|
2
2
|
import { hydrateAssetData, checkDuplicateBalance, createBalanceIdentifier } from './utils';
|
|
3
|
+
import { fetchCustomTokens } from './custom-tokens';
|
|
3
4
|
|
|
4
5
|
const tag = '| charts-evm |';
|
|
5
6
|
|
|
@@ -32,6 +33,10 @@ export async function getEvmCharts(params: ChartParams): Promise<ChartBalance[]>
|
|
|
32
33
|
// This ensures USDC/USDT balances are always available even if Zapper fails
|
|
33
34
|
await fetchStableCoins(pioneer, primaryAddress, blockchains, balances, context);
|
|
34
35
|
|
|
36
|
+
// CUSTOM TOKENS: Fetch user-defined custom tokens from MongoDB-backed endpoint
|
|
37
|
+
// This ensures user's custom tokens are always included in their portfolio
|
|
38
|
+
await fetchCustomTokens({ blockchains, pioneer, pubkeys, context }, balances);
|
|
39
|
+
|
|
35
40
|
try {
|
|
36
41
|
// NOTE: Zapper API returns portfolio data across ALL networks for a given address
|
|
37
42
|
// We use eip155:1 as the networkId parameter, but Zapper will return data for all EVM chains
|
package/src/index.ts
CHANGED
|
@@ -952,7 +952,7 @@ export class SDK {
|
|
|
952
952
|
throw e;
|
|
953
953
|
}
|
|
954
954
|
};
|
|
955
|
-
this.signTx = async function (unsignedTx: any) {
|
|
955
|
+
this.signTx = async function (caip: string, unsignedTx: any) {
|
|
956
956
|
let tag = TAG + ' | signTx | ';
|
|
957
957
|
try {
|
|
958
958
|
const transactionDependencies = {
|
|
@@ -966,7 +966,7 @@ export class SDK {
|
|
|
966
966
|
keepKeySdk: this.keepKeySdk,
|
|
967
967
|
};
|
|
968
968
|
let txManager = new TransactionManager(transactionDependencies, this.events);
|
|
969
|
-
let signedTx = await txManager.sign(unsignedTx);
|
|
969
|
+
let signedTx = await txManager.sign({ caip, unsignedTx });
|
|
970
970
|
return signedTx;
|
|
971
971
|
} catch (e) {
|
|
972
972
|
console.error(e);
|
|
@@ -1119,13 +1119,12 @@ export class SDK {
|
|
|
1119
1119
|
}
|
|
1120
1120
|
|
|
1121
1121
|
let quote = {
|
|
1122
|
-
|
|
1123
|
-
sellAsset: this.assetContext,
|
|
1122
|
+
sellAsset: this.assetContext.caip, // Send CAIP string, not object
|
|
1124
1123
|
sellAmount: inputAmount.toPrecision(8),
|
|
1125
|
-
buyAsset: this.outboundAssetContext,
|
|
1124
|
+
buyAsset: this.outboundAssetContext.caip, // Send CAIP string, not object
|
|
1126
1125
|
recipientAddress, // Fill this based on your logic
|
|
1127
1126
|
senderAddress, // Fill this based on your logic
|
|
1128
|
-
slippage:
|
|
1127
|
+
slippage: swapPayload.slippagePercentage || 3,
|
|
1129
1128
|
};
|
|
1130
1129
|
|
|
1131
1130
|
let result: any;
|
|
@@ -1134,10 +1133,13 @@ export class SDK {
|
|
|
1134
1133
|
result = result.data;
|
|
1135
1134
|
} catch (e) {
|
|
1136
1135
|
console.error(tag, 'Failed to get quote: ', e);
|
|
1136
|
+
throw Error(
|
|
1137
|
+
'Quote API failed for path: ' + quote.sellAsset + ' -> ' + quote.buyAsset + '. Error: ' + e,
|
|
1138
|
+
);
|
|
1137
1139
|
}
|
|
1138
|
-
if (result.length === 0)
|
|
1140
|
+
if (!result || result.length === 0)
|
|
1139
1141
|
throw Error(
|
|
1140
|
-
'No quotes available! path: ' + quote.sellAsset
|
|
1142
|
+
'No quotes available! path: ' + quote.sellAsset + ' -> ' + quote.buyAsset,
|
|
1141
1143
|
);
|
|
1142
1144
|
//TODO let user handle selecting quote?
|
|
1143
1145
|
let selected = result[0];
|
|
@@ -1145,12 +1147,14 @@ export class SDK {
|
|
|
1145
1147
|
if (!txs) throw Error('invalid quote!');
|
|
1146
1148
|
for (let i = 0; i < txs.length; i++) {
|
|
1147
1149
|
let tx = txs[i];
|
|
1150
|
+
|
|
1148
1151
|
const transactionDependencies = {
|
|
1149
1152
|
context: this.context,
|
|
1150
1153
|
assetContext: this.assetContext,
|
|
1151
1154
|
balances: this.balances,
|
|
1152
1155
|
pioneer: this.pioneer,
|
|
1153
1156
|
pubkeys: this.pubkeys,
|
|
1157
|
+
pubkeyContext: this.pubkeyContext,
|
|
1154
1158
|
nodes: this.nodes,
|
|
1155
1159
|
keepKeySdk: this.keepKeySdk,
|
|
1156
1160
|
};
|
|
@@ -1161,7 +1165,7 @@ export class SDK {
|
|
|
1161
1165
|
|
|
1162
1166
|
let unsignedTx;
|
|
1163
1167
|
if (tx.type === 'deposit') {
|
|
1164
|
-
//build deposit tx
|
|
1168
|
+
//build deposit tx (Tendermint chains)
|
|
1165
1169
|
unsignedTx = await createUnsignedTendermintTx(
|
|
1166
1170
|
caip,
|
|
1167
1171
|
tx.type,
|
|
@@ -1169,11 +1173,16 @@ export class SDK {
|
|
|
1169
1173
|
tx.txParams.memo,
|
|
1170
1174
|
this.pubkeys,
|
|
1171
1175
|
this.pioneer,
|
|
1172
|
-
this.
|
|
1176
|
+
this.pubkeyContext,
|
|
1173
1177
|
false,
|
|
1174
1178
|
undefined,
|
|
1175
1179
|
);
|
|
1180
|
+
} else if (tx.type === 'EVM' || tx.type === 'evm') {
|
|
1181
|
+
//EVM transaction - use the pre-built transaction from integration
|
|
1182
|
+
console.log(tag, 'Using pre-built EVM transaction from integration');
|
|
1183
|
+
unsignedTx = tx.txParams;
|
|
1176
1184
|
} else {
|
|
1185
|
+
//transfer transaction (UTXO chains) - requires memo
|
|
1177
1186
|
if (!tx.txParams.memo) throw Error('memo required on swaps!');
|
|
1178
1187
|
const sendPayload: any = {
|
|
1179
1188
|
caip,
|
|
@@ -269,9 +269,10 @@ export async function createUnsignedEvmTx(
|
|
|
269
269
|
//console.log(tag, 'nonce:', nonce);
|
|
270
270
|
|
|
271
271
|
const balanceData = await pioneer.GetBalanceAddressByNetwork({ networkId, address });
|
|
272
|
-
|
|
272
|
+
// Use nativeBalance for ETH balance (needed for gas fees), fallback to balance
|
|
273
|
+
const balanceEth = parseFloat(balanceData.data.nativeBalance || balanceData.data.balance || '0'); // Native ETH balance
|
|
273
274
|
const balance = BigInt(Math.round(balanceEth * 1e18)); // Convert to wei
|
|
274
|
-
|
|
275
|
+
console.log(tag, 'Native ETH balance from API:', balanceData.data.nativeBalance || balanceData.data.balance, 'ETH (', balance.toString(), 'wei)');
|
|
275
276
|
if (balance <= 0n) throw new Error('Wallet balance is zero');
|
|
276
277
|
|
|
277
278
|
// Classify asset type by CAIP
|
|
@@ -529,8 +530,12 @@ export async function createUnsignedEvmTx(
|
|
|
529
530
|
});
|
|
530
531
|
}
|
|
531
532
|
|
|
532
|
-
//
|
|
533
|
-
|
|
533
|
+
// Check balance using minimal gas estimate (25k)
|
|
534
|
+
// We use higher gasLimit (100k) for safety, but check with lower estimate to allow users to "push it"
|
|
535
|
+
// Most ERC20 transfers use 50-65k gas, so 25k gives users maximum flexibility
|
|
536
|
+
const estimatedGasForCheck = BigInt(25000);
|
|
537
|
+
const estimatedGasFee = gasPrice * estimatedGasForCheck;
|
|
538
|
+
if (estimatedGasFee > balance) {
|
|
534
539
|
throw new Error('Insufficient ETH balance to cover gas fees');
|
|
535
540
|
}
|
|
536
541
|
|
|
@@ -12,6 +12,7 @@ export function buildDashboardFromBalances(balances: any[], blockchains: string[
|
|
|
12
12
|
totalValueUsd: number;
|
|
13
13
|
gasAssetCaip: string | null;
|
|
14
14
|
gasAssetSymbol: string | null;
|
|
15
|
+
gasAssetName: string | null;
|
|
15
16
|
icon: string | null;
|
|
16
17
|
color: string | null;
|
|
17
18
|
totalNativeBalance: string;
|
|
@@ -30,6 +31,7 @@ export function buildDashboardFromBalances(balances: any[], blockchains: string[
|
|
|
30
31
|
totalValueUsd: number;
|
|
31
32
|
gasAssetCaip: string | null;
|
|
32
33
|
gasAssetSymbol: string | null;
|
|
34
|
+
gasAssetName: string | null;
|
|
33
35
|
icon: string | null;
|
|
34
36
|
color: string | null;
|
|
35
37
|
totalNativeBalance: string;
|
|
@@ -144,11 +146,19 @@ export function buildDashboardFromBalances(balances: any[], blockchains: string[
|
|
|
144
146
|
// Get colors from assetMap since balances don't have them
|
|
145
147
|
const assetInfo = nativeAssetCaip ? assetsMap.get(nativeAssetCaip) : null;
|
|
146
148
|
|
|
149
|
+
// Debug logging
|
|
150
|
+
console.log(TAG, `[DEBUG] Network: ${blockchain}`);
|
|
151
|
+
console.log(TAG, `[DEBUG] nativeAssetCaip: ${nativeAssetCaip}`);
|
|
152
|
+
console.log(TAG, `[DEBUG] assetInfo:`, assetInfo);
|
|
153
|
+
console.log(TAG, `[DEBUG] gasAsset:`, gasAsset);
|
|
154
|
+
console.log(TAG, `[DEBUG] Resolved name: ${gasAsset?.name || assetInfo?.name || 'NO NAME'}`);
|
|
155
|
+
|
|
147
156
|
networksTemp.push({
|
|
148
157
|
networkId: blockchain,
|
|
149
158
|
totalValueUsd: networkTotal,
|
|
150
159
|
gasAssetCaip: nativeAssetCaip || null,
|
|
151
160
|
gasAssetSymbol: gasAsset?.ticker || gasAsset?.symbol || assetInfo?.symbol || null,
|
|
161
|
+
gasAssetName: gasAsset?.name || assetInfo?.name || null,
|
|
152
162
|
icon: gasAsset?.icon || assetInfo?.icon || null,
|
|
153
163
|
color: gasAsset?.color || assetInfo?.color || null,
|
|
154
164
|
totalNativeBalance,
|