@pioneer-platform/pioneer-sdk 8.12.0 → 8.12.4
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 +211 -17
- package/dist/index.es.js +211 -17
- package/dist/index.js +211 -17
- package/package.json +1 -1
- package/src/index.ts +145 -3
- package/src/txbuilder/createUnsignedEvmTx.ts +223 -28
package/dist/index.cjs
CHANGED
|
@@ -1074,17 +1074,17 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1074
1074
|
let unsignedTx;
|
|
1075
1075
|
if (memo === " ")
|
|
1076
1076
|
memo = "";
|
|
1077
|
+
const isThorchainSwap = memo && (memo.startsWith("=") || memo.startsWith("SWAP") || memo.includes(":"));
|
|
1077
1078
|
switch (assetType) {
|
|
1078
1079
|
case "gas": {
|
|
1079
|
-
const isThorchainOperation = memo && (memo.startsWith("=") || memo.startsWith("SWAP") || memo.includes(":"));
|
|
1080
1080
|
let gasLimit;
|
|
1081
|
-
if (
|
|
1082
|
-
gasLimit = BigInt(
|
|
1081
|
+
if (isThorchainSwap) {
|
|
1082
|
+
gasLimit = BigInt(1e5);
|
|
1083
1083
|
console.log(tag, "Using higher gas limit for THORChain swap:", gasLimit.toString());
|
|
1084
1084
|
} else {
|
|
1085
1085
|
gasLimit = chainId === 1 ? BigInt(21000) : BigInt(25000);
|
|
1086
1086
|
}
|
|
1087
|
-
if (memo && memo !== "" && !
|
|
1087
|
+
if (memo && memo !== "" && !isThorchainSwap) {
|
|
1088
1088
|
const memoBytes = Buffer.from(memo, "utf8").length;
|
|
1089
1089
|
gasLimit += BigInt(memoBytes) * 68n;
|
|
1090
1090
|
}
|
|
@@ -1099,11 +1099,30 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1099
1099
|
console.log(tag, "isMax calculation - balance:", balance.toString(), "gasFee:", gasFee.toString(), "buffer:", buffer.toString(), "amountWei:", amountWei.toString());
|
|
1100
1100
|
} else {
|
|
1101
1101
|
amountWei = BigInt(Math.round(amount * 1000000000000000000));
|
|
1102
|
-
|
|
1103
|
-
|
|
1102
|
+
const totalNeeded = amountWei + gasFee;
|
|
1103
|
+
if (totalNeeded > balance) {
|
|
1104
|
+
const availableForSwap = balance > gasFee ? balance - gasFee : 0n;
|
|
1105
|
+
const balanceEth2 = (Number(balance) / 1000000000000000000).toFixed(6);
|
|
1106
|
+
const amountEth = (Number(amountWei) / 1000000000000000000).toFixed(6);
|
|
1107
|
+
const gasFeeEth = (Number(gasFee) / 1000000000000000000).toFixed(6);
|
|
1108
|
+
const availableEth = (Number(availableForSwap) / 1000000000000000000).toFixed(6);
|
|
1109
|
+
const swapType = isThorchainSwap ? "THORChain swap" : "transfer";
|
|
1110
|
+
throw new Error(`Insufficient funds for the transaction amount and gas fees.
|
|
1111
|
+
` + `Balance: ${balanceEth2} ETH
|
|
1112
|
+
` + `Attempting to ${swapType}: ${amountEth} ETH
|
|
1113
|
+
` + `Estimated gas fee: ${gasFeeEth} ETH (${gasLimit.toString()} gas limit)
|
|
1114
|
+
` + `Total needed: ${(Number(totalNeeded) / 1000000000000000000).toFixed(6)} ETH
|
|
1115
|
+
` + `Maximum swappable: ${availableEth} ETH (after gas fees)`);
|
|
1104
1116
|
}
|
|
1105
1117
|
}
|
|
1106
|
-
|
|
1118
|
+
console.log(tag, "Transaction calculation:", {
|
|
1119
|
+
balance: balance.toString() + " wei (" + (Number(balance) / 1000000000000000000).toFixed(6) + " ETH)",
|
|
1120
|
+
amountWei: amountWei.toString() + " wei (" + (Number(amountWei) / 1000000000000000000).toFixed(6) + " ETH)",
|
|
1121
|
+
gasFee: gasFee.toString() + " wei (" + (Number(gasFee) / 1000000000000000000).toFixed(6) + " ETH)",
|
|
1122
|
+
gasLimit: gasLimit.toString(),
|
|
1123
|
+
gasPrice: gasPrice.toString() + " wei (" + (Number(gasPrice) / 1e9).toFixed(2) + " gwei)",
|
|
1124
|
+
isThorchainSwap
|
|
1125
|
+
});
|
|
1107
1126
|
let txData = "0x";
|
|
1108
1127
|
if (isThorchainSwap) {
|
|
1109
1128
|
console.log(tag, "Detected THORChain swap, encoding deposit data for memo:", memo);
|
|
@@ -1125,14 +1144,25 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1125
1144
|
const inboundResponse = await fetch("https://thornode.ninerealms.com/thorchain/inbound_addresses");
|
|
1126
1145
|
if (inboundResponse.ok) {
|
|
1127
1146
|
const inboundData = await inboundResponse.json();
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1147
|
+
const chainIdToThorchain = {
|
|
1148
|
+
1: "ETH",
|
|
1149
|
+
43114: "AVAX",
|
|
1150
|
+
8453: "BASE",
|
|
1151
|
+
56: "BSC"
|
|
1152
|
+
};
|
|
1153
|
+
const thorchainName = chainIdToThorchain[chainId];
|
|
1154
|
+
if (!thorchainName) {
|
|
1155
|
+
throw new Error(`Unsupported chain ID for THORChain swap: ${chainId}`);
|
|
1156
|
+
}
|
|
1157
|
+
console.log(tag, "Looking for THORChain inbound for chain:", thorchainName, "chainId:", chainId);
|
|
1158
|
+
const chainInbound = inboundData.find((inbound) => inbound.chain === thorchainName && !inbound.halted);
|
|
1159
|
+
if (chainInbound) {
|
|
1160
|
+
vaultAddress = chainInbound.address;
|
|
1161
|
+
routerAddress = chainInbound.router || to;
|
|
1132
1162
|
console.log(tag, "Using THORChain inbound addresses - vault:", vaultAddress, "router:", routerAddress);
|
|
1133
1163
|
to = routerAddress;
|
|
1134
1164
|
} else {
|
|
1135
|
-
throw new Error(
|
|
1165
|
+
throw new Error(`${thorchainName} inbound is halted or not found - cannot proceed with swap`);
|
|
1136
1166
|
}
|
|
1137
1167
|
}
|
|
1138
1168
|
} catch (fetchError) {
|
|
@@ -1142,6 +1172,15 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1142
1172
|
if (vaultAddress === "0x0000000000000000000000000000000000000000") {
|
|
1143
1173
|
throw new Error("Cannot proceed with THORChain swap - vault address is invalid (0x0)");
|
|
1144
1174
|
}
|
|
1175
|
+
if (to.toLowerCase() === vaultAddress.toLowerCase()) {
|
|
1176
|
+
console.warn(tag, '⚠️ WARNING: "to" address equals vault address - this should be the router!');
|
|
1177
|
+
console.warn(tag, "⚠️ Using fetched router address instead:", routerAddress);
|
|
1178
|
+
to = routerAddress;
|
|
1179
|
+
}
|
|
1180
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(to)) {
|
|
1181
|
+
throw new Error(`Invalid router address format: ${to}`);
|
|
1182
|
+
}
|
|
1183
|
+
console.log(tag, "✅ Final validation passed - router:", to, "vault:", vaultAddress);
|
|
1145
1184
|
const functionSelector = "44bc937b";
|
|
1146
1185
|
const assetAddress = "0x0000000000000000000000000000000000000000";
|
|
1147
1186
|
const expiryTime = Math.floor(Date.now() / 1000) + 3600;
|
|
@@ -1230,17 +1269,98 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1230
1269
|
if (estimatedGasFee > balance) {
|
|
1231
1270
|
throw new Error("Insufficient ETH balance to cover gas fees");
|
|
1232
1271
|
}
|
|
1233
|
-
|
|
1272
|
+
let data;
|
|
1273
|
+
let finalTo;
|
|
1274
|
+
if (isThorchainSwap) {
|
|
1275
|
+
console.log(tag, "\uD83D\uDD04 ERC20 THORChain swap detected - encoding depositWithExpiry");
|
|
1276
|
+
let vaultAddress = "0x0000000000000000000000000000000000000000";
|
|
1277
|
+
let routerAddress = to;
|
|
1278
|
+
try {
|
|
1279
|
+
const inboundResponse = await fetch("https://thornode.ninerealms.com/thorchain/inbound_addresses");
|
|
1280
|
+
if (inboundResponse.ok) {
|
|
1281
|
+
const inboundData = await inboundResponse.json();
|
|
1282
|
+
const chainIdToThorchain = {
|
|
1283
|
+
1: "ETH",
|
|
1284
|
+
43114: "AVAX",
|
|
1285
|
+
8453: "BASE",
|
|
1286
|
+
56: "BSC"
|
|
1287
|
+
};
|
|
1288
|
+
const thorchainName = chainIdToThorchain[chainId];
|
|
1289
|
+
if (!thorchainName) {
|
|
1290
|
+
throw new Error(`Unsupported chain ID for THORChain swap: ${chainId}`);
|
|
1291
|
+
}
|
|
1292
|
+
console.log(tag, "Looking for THORChain inbound for chain:", thorchainName, "chainId:", chainId);
|
|
1293
|
+
const chainInbound = inboundData.find((inbound) => inbound.chain === thorchainName && !inbound.halted);
|
|
1294
|
+
if (chainInbound) {
|
|
1295
|
+
vaultAddress = chainInbound.address;
|
|
1296
|
+
routerAddress = chainInbound.router || to;
|
|
1297
|
+
console.log(tag, "Using THORChain inbound addresses - vault:", vaultAddress, "router:", routerAddress);
|
|
1298
|
+
to = routerAddress;
|
|
1299
|
+
} else {
|
|
1300
|
+
throw new Error(`${thorchainName} inbound is halted or not found - cannot proceed with swap`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
} catch (fetchError) {
|
|
1304
|
+
console.error(tag, "Failed to fetch inbound addresses:", fetchError);
|
|
1305
|
+
throw new Error(`Cannot proceed with THORChain swap - failed to fetch inbound addresses: ${fetchError.message}`);
|
|
1306
|
+
}
|
|
1307
|
+
if (vaultAddress === "0x0000000000000000000000000000000000000000") {
|
|
1308
|
+
throw new Error("Cannot proceed with THORChain swap - vault address is invalid (0x0)");
|
|
1309
|
+
}
|
|
1310
|
+
if (to.toLowerCase() === vaultAddress.toLowerCase()) {
|
|
1311
|
+
console.warn(tag, '⚠️ WARNING: "to" address equals vault address - this should be the router!');
|
|
1312
|
+
console.warn(tag, "⚠️ Using fetched router address instead:", routerAddress);
|
|
1313
|
+
to = routerAddress;
|
|
1314
|
+
}
|
|
1315
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(to)) {
|
|
1316
|
+
throw new Error(`Invalid router address format: ${to}`);
|
|
1317
|
+
}
|
|
1318
|
+
console.log(tag, "✅ Final validation passed - router:", to, "vault:", vaultAddress);
|
|
1319
|
+
console.log(tag, "✅ ERC20 THORChain swap addresses validated");
|
|
1320
|
+
console.log(tag, " Router:", to);
|
|
1321
|
+
console.log(tag, " Vault:", vaultAddress);
|
|
1322
|
+
console.log(tag, " Token:", contractAddress);
|
|
1323
|
+
const functionSelector = "44bc937b";
|
|
1324
|
+
const expiryTime = Math.floor(Date.now() / 1000) + 3600;
|
|
1325
|
+
const vaultPadded = vaultAddress.toLowerCase().replace(/^0x/, "").padStart(64, "0");
|
|
1326
|
+
const assetPadded = contractAddress.toLowerCase().replace(/^0x/, "").padStart(64, "0");
|
|
1327
|
+
const amountPadded = amountWei.toString(16).padStart(64, "0");
|
|
1328
|
+
const stringOffset = (5 * 32).toString(16).padStart(64, "0");
|
|
1329
|
+
const expiryPadded = expiryTime.toString(16).padStart(64, "0");
|
|
1330
|
+
const fixedMemo = memo || "";
|
|
1331
|
+
const memoBytes = Buffer.from(fixedMemo, "utf8");
|
|
1332
|
+
const memoHex = memoBytes.toString("hex");
|
|
1333
|
+
const stringLength = memoBytes.length.toString(16).padStart(64, "0");
|
|
1334
|
+
const paddingLength = (32 - memoBytes.length % 32) % 32;
|
|
1335
|
+
const memoPadded = memoHex + "0".repeat(paddingLength * 2);
|
|
1336
|
+
data = "0x" + functionSelector + vaultPadded + assetPadded + amountPadded + stringOffset + expiryPadded + stringLength + memoPadded;
|
|
1337
|
+
finalTo = to;
|
|
1338
|
+
console.log(tag, "✅ Encoded ERC20 depositWithExpiry:", {
|
|
1339
|
+
functionSelector: "0x" + functionSelector,
|
|
1340
|
+
vault: vaultAddress,
|
|
1341
|
+
asset: contractAddress,
|
|
1342
|
+
amount: amountWei.toString(),
|
|
1343
|
+
memo: fixedMemo,
|
|
1344
|
+
expiry: expiryTime,
|
|
1345
|
+
dataLength: data.length
|
|
1346
|
+
});
|
|
1347
|
+
gasLimit = BigInt(200000);
|
|
1348
|
+
} else {
|
|
1349
|
+
data = encodeTransferData(to, amountWei);
|
|
1350
|
+
finalTo = contractAddress;
|
|
1351
|
+
}
|
|
1234
1352
|
const ethPriceInUsd = await fetchEthPriceInUsd(pioneer, networkId);
|
|
1235
|
-
const
|
|
1353
|
+
const finalGasFee = gasPrice * gasLimit;
|
|
1354
|
+
const gasFeeUsd = Number(finalGasFee) / 1000000000000000000 * ethPriceInUsd;
|
|
1236
1355
|
const tokenPriceInUsd = await fetchTokenPriceInUsd(pioneer, caip);
|
|
1237
1356
|
const amountUsd = Number(amountWei) / Number(tokenMultiplier) * tokenPriceInUsd;
|
|
1238
1357
|
unsignedTx = {
|
|
1239
1358
|
chainId,
|
|
1359
|
+
from: address,
|
|
1240
1360
|
nonce: toHex(nonce),
|
|
1241
1361
|
gas: toHex(gasLimit),
|
|
1242
1362
|
gasPrice: toHex(gasPrice),
|
|
1243
|
-
to:
|
|
1363
|
+
to: finalTo,
|
|
1244
1364
|
value: "0x0",
|
|
1245
1365
|
data,
|
|
1246
1366
|
gasFeeUsd,
|
|
@@ -4100,8 +4220,58 @@ class SDK {
|
|
|
4100
4220
|
if (tx.type === "deposit") {
|
|
4101
4221
|
unsignedTx = await createUnsignedTendermintTx(caip, tx.type, tx.txParams.amount, tx.txParams.memo, this.pubkeys, this.pioneer, this.pubkeyContext, false, undefined);
|
|
4102
4222
|
} else if (tx.type === "EVM" || tx.type === "evm") {
|
|
4103
|
-
console.log(tag, "
|
|
4104
|
-
unsignedTx = tx.txParams;
|
|
4223
|
+
console.log(tag, "Building EVM swap transaction with createUnsignedEvmTx");
|
|
4224
|
+
unsignedTx = await createUnsignedEvmTx(caip, tx.txParams.recipientAddress, parseFloat(tx.txParams.amount), tx.txParams.memo, this.pubkeys, this.pioneer, this.pubkeyContext, false);
|
|
4225
|
+
console.log(tag, "✅ Built complete EVM transaction:", {
|
|
4226
|
+
to: unsignedTx.to,
|
|
4227
|
+
from: this.pubkeyContext?.address,
|
|
4228
|
+
value: unsignedTx.value,
|
|
4229
|
+
chainId: unsignedTx.chainId,
|
|
4230
|
+
hasData: !!unsignedTx.data
|
|
4231
|
+
});
|
|
4232
|
+
console.log(tag, "\uD83D\uDEE1️ Running Tenderly simulation for EVM swap...");
|
|
4233
|
+
try {
|
|
4234
|
+
const insightResult = await this.pioneer.Insight({
|
|
4235
|
+
tx: unsignedTx,
|
|
4236
|
+
source: "swap",
|
|
4237
|
+
isThorchainSwap: tx.txParams.isThorchainSwap || false
|
|
4238
|
+
});
|
|
4239
|
+
const insight = insightResult.body;
|
|
4240
|
+
console.log(tag, "Simulation result:", insight?.simulation);
|
|
4241
|
+
if (!insight || !insight.simulation) {
|
|
4242
|
+
console.warn(tag, "⚠️ WARNING: Tenderly simulation unavailable - proceeding without validation");
|
|
4243
|
+
} else if (!insight.simulation.success) {
|
|
4244
|
+
console.warn(tag, `⚠️ WARNING: Swap simulation FAILED - ${insight.simulation.error || "Transaction may revert"}`);
|
|
4245
|
+
console.warn(tag, "⚠️ Proceeding anyway - USE CAUTION");
|
|
4246
|
+
} else {
|
|
4247
|
+
if (tx.txParams.isThorchainSwap) {
|
|
4248
|
+
console.log(tag, "\uD83D\uDD0D Verifying THORChain swap parameters...");
|
|
4249
|
+
const method = insight.simulation.method;
|
|
4250
|
+
if (!method || !method.toLowerCase().includes("deposit")) {
|
|
4251
|
+
throw new Error(`❌ CRITICAL: Invalid THORChain swap method: ${method} - expected depositWithExpiry`);
|
|
4252
|
+
}
|
|
4253
|
+
const routerAddress = unsignedTx.to;
|
|
4254
|
+
const vaultAddress = tx.txParams.vaultAddress;
|
|
4255
|
+
if (routerAddress && vaultAddress) {
|
|
4256
|
+
if (routerAddress.toLowerCase() === vaultAddress.toLowerCase()) {
|
|
4257
|
+
throw new Error(`❌ CRITICAL: Sending directly to vault ${vaultAddress} instead of router!`);
|
|
4258
|
+
}
|
|
4259
|
+
console.log(tag, `✅ Router: ${routerAddress}`);
|
|
4260
|
+
console.log(tag, `✅ Vault: ${vaultAddress}`);
|
|
4261
|
+
}
|
|
4262
|
+
if (insight.simulation.addresses && insight.simulation.addresses.length > 0) {
|
|
4263
|
+
console.log(tag, `✅ Addresses involved: ${insight.simulation.addresses.length}`);
|
|
4264
|
+
} else {
|
|
4265
|
+
console.log(tag, "⚠️ WARNING: No addresses detected in simulation");
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
console.log(tag, `✅ Simulation PASSED - Gas used: ${insight.simulation.gasUsed}`);
|
|
4269
|
+
console.log(tag, `✅ Method: ${insight.simulation.method}`);
|
|
4270
|
+
}
|
|
4271
|
+
} catch (e) {
|
|
4272
|
+
console.error(tag, "❌ Simulation validation failed:", e.message);
|
|
4273
|
+
throw new Error(`Swap blocked by simulation failure: ${e.message}`);
|
|
4274
|
+
}
|
|
4105
4275
|
} else {
|
|
4106
4276
|
if (!tx.txParams.memo)
|
|
4107
4277
|
throw Error("memo required on swaps!");
|
|
@@ -4971,5 +5141,29 @@ class SDK {
|
|
|
4971
5141
|
}
|
|
4972
5142
|
};
|
|
4973
5143
|
}
|
|
5144
|
+
CheckERC20Allowance = async (params) => {
|
|
5145
|
+
const tag = TAG9 + " | CheckERC20Allowance | ";
|
|
5146
|
+
try {
|
|
5147
|
+
console.log(tag, "Checking ERC20 allowance:", params);
|
|
5148
|
+
const result = await this.pioneer.GetTokenAllowance(params);
|
|
5149
|
+
console.log(tag, "Allowance result:", result);
|
|
5150
|
+
return result.data;
|
|
5151
|
+
} catch (e) {
|
|
5152
|
+
console.error(tag, "Error checking ERC20 allowance:", e);
|
|
5153
|
+
throw e;
|
|
5154
|
+
}
|
|
5155
|
+
};
|
|
5156
|
+
BuildERC20ApprovalTx = async (params) => {
|
|
5157
|
+
const tag = TAG9 + " | BuildERC20ApprovalTx | ";
|
|
5158
|
+
try {
|
|
5159
|
+
console.log(tag, "Building ERC20 approval transaction:", params);
|
|
5160
|
+
const result = await this.pioneer.BuildApprovalTransaction(params);
|
|
5161
|
+
console.log(tag, "Approval tx built:", result);
|
|
5162
|
+
return result.data;
|
|
5163
|
+
} catch (e) {
|
|
5164
|
+
console.error(tag, "Error building approval transaction:", e);
|
|
5165
|
+
throw e;
|
|
5166
|
+
}
|
|
5167
|
+
};
|
|
4974
5168
|
}
|
|
4975
5169
|
var src_default = SDK;
|
package/dist/index.es.js
CHANGED
|
@@ -1250,17 +1250,17 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1250
1250
|
let unsignedTx;
|
|
1251
1251
|
if (memo === " ")
|
|
1252
1252
|
memo = "";
|
|
1253
|
+
const isThorchainSwap = memo && (memo.startsWith("=") || memo.startsWith("SWAP") || memo.includes(":"));
|
|
1253
1254
|
switch (assetType) {
|
|
1254
1255
|
case "gas": {
|
|
1255
|
-
const isThorchainOperation = memo && (memo.startsWith("=") || memo.startsWith("SWAP") || memo.includes(":"));
|
|
1256
1256
|
let gasLimit;
|
|
1257
|
-
if (
|
|
1258
|
-
gasLimit = BigInt(
|
|
1257
|
+
if (isThorchainSwap) {
|
|
1258
|
+
gasLimit = BigInt(1e5);
|
|
1259
1259
|
console.log(tag, "Using higher gas limit for THORChain swap:", gasLimit.toString());
|
|
1260
1260
|
} else {
|
|
1261
1261
|
gasLimit = chainId === 1 ? BigInt(21000) : BigInt(25000);
|
|
1262
1262
|
}
|
|
1263
|
-
if (memo && memo !== "" && !
|
|
1263
|
+
if (memo && memo !== "" && !isThorchainSwap) {
|
|
1264
1264
|
const memoBytes = Buffer.from(memo, "utf8").length;
|
|
1265
1265
|
gasLimit += BigInt(memoBytes) * 68n;
|
|
1266
1266
|
}
|
|
@@ -1275,11 +1275,30 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1275
1275
|
console.log(tag, "isMax calculation - balance:", balance.toString(), "gasFee:", gasFee.toString(), "buffer:", buffer.toString(), "amountWei:", amountWei.toString());
|
|
1276
1276
|
} else {
|
|
1277
1277
|
amountWei = BigInt(Math.round(amount * 1000000000000000000));
|
|
1278
|
-
|
|
1279
|
-
|
|
1278
|
+
const totalNeeded = amountWei + gasFee;
|
|
1279
|
+
if (totalNeeded > balance) {
|
|
1280
|
+
const availableForSwap = balance > gasFee ? balance - gasFee : 0n;
|
|
1281
|
+
const balanceEth2 = (Number(balance) / 1000000000000000000).toFixed(6);
|
|
1282
|
+
const amountEth = (Number(amountWei) / 1000000000000000000).toFixed(6);
|
|
1283
|
+
const gasFeeEth = (Number(gasFee) / 1000000000000000000).toFixed(6);
|
|
1284
|
+
const availableEth = (Number(availableForSwap) / 1000000000000000000).toFixed(6);
|
|
1285
|
+
const swapType = isThorchainSwap ? "THORChain swap" : "transfer";
|
|
1286
|
+
throw new Error(`Insufficient funds for the transaction amount and gas fees.
|
|
1287
|
+
` + `Balance: ${balanceEth2} ETH
|
|
1288
|
+
` + `Attempting to ${swapType}: ${amountEth} ETH
|
|
1289
|
+
` + `Estimated gas fee: ${gasFeeEth} ETH (${gasLimit.toString()} gas limit)
|
|
1290
|
+
` + `Total needed: ${(Number(totalNeeded) / 1000000000000000000).toFixed(6)} ETH
|
|
1291
|
+
` + `Maximum swappable: ${availableEth} ETH (after gas fees)`);
|
|
1280
1292
|
}
|
|
1281
1293
|
}
|
|
1282
|
-
|
|
1294
|
+
console.log(tag, "Transaction calculation:", {
|
|
1295
|
+
balance: balance.toString() + " wei (" + (Number(balance) / 1000000000000000000).toFixed(6) + " ETH)",
|
|
1296
|
+
amountWei: amountWei.toString() + " wei (" + (Number(amountWei) / 1000000000000000000).toFixed(6) + " ETH)",
|
|
1297
|
+
gasFee: gasFee.toString() + " wei (" + (Number(gasFee) / 1000000000000000000).toFixed(6) + " ETH)",
|
|
1298
|
+
gasLimit: gasLimit.toString(),
|
|
1299
|
+
gasPrice: gasPrice.toString() + " wei (" + (Number(gasPrice) / 1e9).toFixed(2) + " gwei)",
|
|
1300
|
+
isThorchainSwap
|
|
1301
|
+
});
|
|
1283
1302
|
let txData = "0x";
|
|
1284
1303
|
if (isThorchainSwap) {
|
|
1285
1304
|
console.log(tag, "Detected THORChain swap, encoding deposit data for memo:", memo);
|
|
@@ -1301,14 +1320,25 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1301
1320
|
const inboundResponse = await fetch("https://thornode.ninerealms.com/thorchain/inbound_addresses");
|
|
1302
1321
|
if (inboundResponse.ok) {
|
|
1303
1322
|
const inboundData = await inboundResponse.json();
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1323
|
+
const chainIdToThorchain = {
|
|
1324
|
+
1: "ETH",
|
|
1325
|
+
43114: "AVAX",
|
|
1326
|
+
8453: "BASE",
|
|
1327
|
+
56: "BSC"
|
|
1328
|
+
};
|
|
1329
|
+
const thorchainName = chainIdToThorchain[chainId];
|
|
1330
|
+
if (!thorchainName) {
|
|
1331
|
+
throw new Error(`Unsupported chain ID for THORChain swap: ${chainId}`);
|
|
1332
|
+
}
|
|
1333
|
+
console.log(tag, "Looking for THORChain inbound for chain:", thorchainName, "chainId:", chainId);
|
|
1334
|
+
const chainInbound = inboundData.find((inbound) => inbound.chain === thorchainName && !inbound.halted);
|
|
1335
|
+
if (chainInbound) {
|
|
1336
|
+
vaultAddress = chainInbound.address;
|
|
1337
|
+
routerAddress = chainInbound.router || to;
|
|
1308
1338
|
console.log(tag, "Using THORChain inbound addresses - vault:", vaultAddress, "router:", routerAddress);
|
|
1309
1339
|
to = routerAddress;
|
|
1310
1340
|
} else {
|
|
1311
|
-
throw new Error(
|
|
1341
|
+
throw new Error(`${thorchainName} inbound is halted or not found - cannot proceed with swap`);
|
|
1312
1342
|
}
|
|
1313
1343
|
}
|
|
1314
1344
|
} catch (fetchError) {
|
|
@@ -1318,6 +1348,15 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1318
1348
|
if (vaultAddress === "0x0000000000000000000000000000000000000000") {
|
|
1319
1349
|
throw new Error("Cannot proceed with THORChain swap - vault address is invalid (0x0)");
|
|
1320
1350
|
}
|
|
1351
|
+
if (to.toLowerCase() === vaultAddress.toLowerCase()) {
|
|
1352
|
+
console.warn(tag, '⚠️ WARNING: "to" address equals vault address - this should be the router!');
|
|
1353
|
+
console.warn(tag, "⚠️ Using fetched router address instead:", routerAddress);
|
|
1354
|
+
to = routerAddress;
|
|
1355
|
+
}
|
|
1356
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(to)) {
|
|
1357
|
+
throw new Error(`Invalid router address format: ${to}`);
|
|
1358
|
+
}
|
|
1359
|
+
console.log(tag, "✅ Final validation passed - router:", to, "vault:", vaultAddress);
|
|
1321
1360
|
const functionSelector = "44bc937b";
|
|
1322
1361
|
const assetAddress = "0x0000000000000000000000000000000000000000";
|
|
1323
1362
|
const expiryTime = Math.floor(Date.now() / 1000) + 3600;
|
|
@@ -1406,17 +1445,98 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1406
1445
|
if (estimatedGasFee > balance) {
|
|
1407
1446
|
throw new Error("Insufficient ETH balance to cover gas fees");
|
|
1408
1447
|
}
|
|
1409
|
-
|
|
1448
|
+
let data;
|
|
1449
|
+
let finalTo;
|
|
1450
|
+
if (isThorchainSwap) {
|
|
1451
|
+
console.log(tag, "\uD83D\uDD04 ERC20 THORChain swap detected - encoding depositWithExpiry");
|
|
1452
|
+
let vaultAddress = "0x0000000000000000000000000000000000000000";
|
|
1453
|
+
let routerAddress = to;
|
|
1454
|
+
try {
|
|
1455
|
+
const inboundResponse = await fetch("https://thornode.ninerealms.com/thorchain/inbound_addresses");
|
|
1456
|
+
if (inboundResponse.ok) {
|
|
1457
|
+
const inboundData = await inboundResponse.json();
|
|
1458
|
+
const chainIdToThorchain = {
|
|
1459
|
+
1: "ETH",
|
|
1460
|
+
43114: "AVAX",
|
|
1461
|
+
8453: "BASE",
|
|
1462
|
+
56: "BSC"
|
|
1463
|
+
};
|
|
1464
|
+
const thorchainName = chainIdToThorchain[chainId];
|
|
1465
|
+
if (!thorchainName) {
|
|
1466
|
+
throw new Error(`Unsupported chain ID for THORChain swap: ${chainId}`);
|
|
1467
|
+
}
|
|
1468
|
+
console.log(tag, "Looking for THORChain inbound for chain:", thorchainName, "chainId:", chainId);
|
|
1469
|
+
const chainInbound = inboundData.find((inbound) => inbound.chain === thorchainName && !inbound.halted);
|
|
1470
|
+
if (chainInbound) {
|
|
1471
|
+
vaultAddress = chainInbound.address;
|
|
1472
|
+
routerAddress = chainInbound.router || to;
|
|
1473
|
+
console.log(tag, "Using THORChain inbound addresses - vault:", vaultAddress, "router:", routerAddress);
|
|
1474
|
+
to = routerAddress;
|
|
1475
|
+
} else {
|
|
1476
|
+
throw new Error(`${thorchainName} inbound is halted or not found - cannot proceed with swap`);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
} catch (fetchError) {
|
|
1480
|
+
console.error(tag, "Failed to fetch inbound addresses:", fetchError);
|
|
1481
|
+
throw new Error(`Cannot proceed with THORChain swap - failed to fetch inbound addresses: ${fetchError.message}`);
|
|
1482
|
+
}
|
|
1483
|
+
if (vaultAddress === "0x0000000000000000000000000000000000000000") {
|
|
1484
|
+
throw new Error("Cannot proceed with THORChain swap - vault address is invalid (0x0)");
|
|
1485
|
+
}
|
|
1486
|
+
if (to.toLowerCase() === vaultAddress.toLowerCase()) {
|
|
1487
|
+
console.warn(tag, '⚠️ WARNING: "to" address equals vault address - this should be the router!');
|
|
1488
|
+
console.warn(tag, "⚠️ Using fetched router address instead:", routerAddress);
|
|
1489
|
+
to = routerAddress;
|
|
1490
|
+
}
|
|
1491
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(to)) {
|
|
1492
|
+
throw new Error(`Invalid router address format: ${to}`);
|
|
1493
|
+
}
|
|
1494
|
+
console.log(tag, "✅ Final validation passed - router:", to, "vault:", vaultAddress);
|
|
1495
|
+
console.log(tag, "✅ ERC20 THORChain swap addresses validated");
|
|
1496
|
+
console.log(tag, " Router:", to);
|
|
1497
|
+
console.log(tag, " Vault:", vaultAddress);
|
|
1498
|
+
console.log(tag, " Token:", contractAddress);
|
|
1499
|
+
const functionSelector = "44bc937b";
|
|
1500
|
+
const expiryTime = Math.floor(Date.now() / 1000) + 3600;
|
|
1501
|
+
const vaultPadded = vaultAddress.toLowerCase().replace(/^0x/, "").padStart(64, "0");
|
|
1502
|
+
const assetPadded = contractAddress.toLowerCase().replace(/^0x/, "").padStart(64, "0");
|
|
1503
|
+
const amountPadded = amountWei.toString(16).padStart(64, "0");
|
|
1504
|
+
const stringOffset = (5 * 32).toString(16).padStart(64, "0");
|
|
1505
|
+
const expiryPadded = expiryTime.toString(16).padStart(64, "0");
|
|
1506
|
+
const fixedMemo = memo || "";
|
|
1507
|
+
const memoBytes = Buffer.from(fixedMemo, "utf8");
|
|
1508
|
+
const memoHex = memoBytes.toString("hex");
|
|
1509
|
+
const stringLength = memoBytes.length.toString(16).padStart(64, "0");
|
|
1510
|
+
const paddingLength = (32 - memoBytes.length % 32) % 32;
|
|
1511
|
+
const memoPadded = memoHex + "0".repeat(paddingLength * 2);
|
|
1512
|
+
data = "0x" + functionSelector + vaultPadded + assetPadded + amountPadded + stringOffset + expiryPadded + stringLength + memoPadded;
|
|
1513
|
+
finalTo = to;
|
|
1514
|
+
console.log(tag, "✅ Encoded ERC20 depositWithExpiry:", {
|
|
1515
|
+
functionSelector: "0x" + functionSelector,
|
|
1516
|
+
vault: vaultAddress,
|
|
1517
|
+
asset: contractAddress,
|
|
1518
|
+
amount: amountWei.toString(),
|
|
1519
|
+
memo: fixedMemo,
|
|
1520
|
+
expiry: expiryTime,
|
|
1521
|
+
dataLength: data.length
|
|
1522
|
+
});
|
|
1523
|
+
gasLimit = BigInt(200000);
|
|
1524
|
+
} else {
|
|
1525
|
+
data = encodeTransferData(to, amountWei);
|
|
1526
|
+
finalTo = contractAddress;
|
|
1527
|
+
}
|
|
1410
1528
|
const ethPriceInUsd = await fetchEthPriceInUsd(pioneer, networkId);
|
|
1411
|
-
const
|
|
1529
|
+
const finalGasFee = gasPrice * gasLimit;
|
|
1530
|
+
const gasFeeUsd = Number(finalGasFee) / 1000000000000000000 * ethPriceInUsd;
|
|
1412
1531
|
const tokenPriceInUsd = await fetchTokenPriceInUsd(pioneer, caip);
|
|
1413
1532
|
const amountUsd = Number(amountWei) / Number(tokenMultiplier) * tokenPriceInUsd;
|
|
1414
1533
|
unsignedTx = {
|
|
1415
1534
|
chainId,
|
|
1535
|
+
from: address,
|
|
1416
1536
|
nonce: toHex(nonce),
|
|
1417
1537
|
gas: toHex(gasLimit),
|
|
1418
1538
|
gasPrice: toHex(gasPrice),
|
|
1419
|
-
to:
|
|
1539
|
+
to: finalTo,
|
|
1420
1540
|
value: "0x0",
|
|
1421
1541
|
data,
|
|
1422
1542
|
gasFeeUsd,
|
|
@@ -4276,8 +4396,58 @@ class SDK {
|
|
|
4276
4396
|
if (tx.type === "deposit") {
|
|
4277
4397
|
unsignedTx = await createUnsignedTendermintTx(caip, tx.type, tx.txParams.amount, tx.txParams.memo, this.pubkeys, this.pioneer, this.pubkeyContext, false, undefined);
|
|
4278
4398
|
} else if (tx.type === "EVM" || tx.type === "evm") {
|
|
4279
|
-
console.log(tag, "
|
|
4280
|
-
unsignedTx = tx.txParams;
|
|
4399
|
+
console.log(tag, "Building EVM swap transaction with createUnsignedEvmTx");
|
|
4400
|
+
unsignedTx = await createUnsignedEvmTx(caip, tx.txParams.recipientAddress, parseFloat(tx.txParams.amount), tx.txParams.memo, this.pubkeys, this.pioneer, this.pubkeyContext, false);
|
|
4401
|
+
console.log(tag, "✅ Built complete EVM transaction:", {
|
|
4402
|
+
to: unsignedTx.to,
|
|
4403
|
+
from: this.pubkeyContext?.address,
|
|
4404
|
+
value: unsignedTx.value,
|
|
4405
|
+
chainId: unsignedTx.chainId,
|
|
4406
|
+
hasData: !!unsignedTx.data
|
|
4407
|
+
});
|
|
4408
|
+
console.log(tag, "\uD83D\uDEE1️ Running Tenderly simulation for EVM swap...");
|
|
4409
|
+
try {
|
|
4410
|
+
const insightResult = await this.pioneer.Insight({
|
|
4411
|
+
tx: unsignedTx,
|
|
4412
|
+
source: "swap",
|
|
4413
|
+
isThorchainSwap: tx.txParams.isThorchainSwap || false
|
|
4414
|
+
});
|
|
4415
|
+
const insight = insightResult.body;
|
|
4416
|
+
console.log(tag, "Simulation result:", insight?.simulation);
|
|
4417
|
+
if (!insight || !insight.simulation) {
|
|
4418
|
+
console.warn(tag, "⚠️ WARNING: Tenderly simulation unavailable - proceeding without validation");
|
|
4419
|
+
} else if (!insight.simulation.success) {
|
|
4420
|
+
console.warn(tag, `⚠️ WARNING: Swap simulation FAILED - ${insight.simulation.error || "Transaction may revert"}`);
|
|
4421
|
+
console.warn(tag, "⚠️ Proceeding anyway - USE CAUTION");
|
|
4422
|
+
} else {
|
|
4423
|
+
if (tx.txParams.isThorchainSwap) {
|
|
4424
|
+
console.log(tag, "\uD83D\uDD0D Verifying THORChain swap parameters...");
|
|
4425
|
+
const method = insight.simulation.method;
|
|
4426
|
+
if (!method || !method.toLowerCase().includes("deposit")) {
|
|
4427
|
+
throw new Error(`❌ CRITICAL: Invalid THORChain swap method: ${method} - expected depositWithExpiry`);
|
|
4428
|
+
}
|
|
4429
|
+
const routerAddress = unsignedTx.to;
|
|
4430
|
+
const vaultAddress = tx.txParams.vaultAddress;
|
|
4431
|
+
if (routerAddress && vaultAddress) {
|
|
4432
|
+
if (routerAddress.toLowerCase() === vaultAddress.toLowerCase()) {
|
|
4433
|
+
throw new Error(`❌ CRITICAL: Sending directly to vault ${vaultAddress} instead of router!`);
|
|
4434
|
+
}
|
|
4435
|
+
console.log(tag, `✅ Router: ${routerAddress}`);
|
|
4436
|
+
console.log(tag, `✅ Vault: ${vaultAddress}`);
|
|
4437
|
+
}
|
|
4438
|
+
if (insight.simulation.addresses && insight.simulation.addresses.length > 0) {
|
|
4439
|
+
console.log(tag, `✅ Addresses involved: ${insight.simulation.addresses.length}`);
|
|
4440
|
+
} else {
|
|
4441
|
+
console.log(tag, "⚠️ WARNING: No addresses detected in simulation");
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
console.log(tag, `✅ Simulation PASSED - Gas used: ${insight.simulation.gasUsed}`);
|
|
4445
|
+
console.log(tag, `✅ Method: ${insight.simulation.method}`);
|
|
4446
|
+
}
|
|
4447
|
+
} catch (e) {
|
|
4448
|
+
console.error(tag, "❌ Simulation validation failed:", e.message);
|
|
4449
|
+
throw new Error(`Swap blocked by simulation failure: ${e.message}`);
|
|
4450
|
+
}
|
|
4281
4451
|
} else {
|
|
4282
4452
|
if (!tx.txParams.memo)
|
|
4283
4453
|
throw Error("memo required on swaps!");
|
|
@@ -5147,6 +5317,30 @@ class SDK {
|
|
|
5147
5317
|
}
|
|
5148
5318
|
};
|
|
5149
5319
|
}
|
|
5320
|
+
CheckERC20Allowance = async (params) => {
|
|
5321
|
+
const tag = TAG9 + " | CheckERC20Allowance | ";
|
|
5322
|
+
try {
|
|
5323
|
+
console.log(tag, "Checking ERC20 allowance:", params);
|
|
5324
|
+
const result = await this.pioneer.GetTokenAllowance(params);
|
|
5325
|
+
console.log(tag, "Allowance result:", result);
|
|
5326
|
+
return result.data;
|
|
5327
|
+
} catch (e) {
|
|
5328
|
+
console.error(tag, "Error checking ERC20 allowance:", e);
|
|
5329
|
+
throw e;
|
|
5330
|
+
}
|
|
5331
|
+
};
|
|
5332
|
+
BuildERC20ApprovalTx = async (params) => {
|
|
5333
|
+
const tag = TAG9 + " | BuildERC20ApprovalTx | ";
|
|
5334
|
+
try {
|
|
5335
|
+
console.log(tag, "Building ERC20 approval transaction:", params);
|
|
5336
|
+
const result = await this.pioneer.BuildApprovalTransaction(params);
|
|
5337
|
+
console.log(tag, "Approval tx built:", result);
|
|
5338
|
+
return result.data;
|
|
5339
|
+
} catch (e) {
|
|
5340
|
+
console.error(tag, "Error building approval transaction:", e);
|
|
5341
|
+
throw e;
|
|
5342
|
+
}
|
|
5343
|
+
};
|
|
5150
5344
|
}
|
|
5151
5345
|
var src_default = SDK;
|
|
5152
5346
|
export {
|