@continuumdao/ctm-mpc-defi 0.2.2 → 0.2.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/agent/catalog.cjs +440 -45
- package/dist/agent/catalog.cjs.map +1 -1
- package/dist/agent/catalog.d.ts +836 -1
- package/dist/agent/catalog.js +423 -46
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/skills/aave-v4/SKILL.md +120 -14
- package/dist/agent/skills/curve-dao/SKILL.md +125 -6
- package/dist/agent/skills/uniswap-v4/SKILL.md +187 -11
- package/dist/index.cjs +790 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +791 -19
- package/dist/index.js.map +1 -1
- package/dist/protocols/evm/curve-dao/index.cjs +53 -19
- package/dist/protocols/evm/curve-dao/index.cjs.map +1 -1
- package/dist/protocols/evm/curve-dao/index.d.ts +9 -9
- package/dist/protocols/evm/curve-dao/index.js +53 -19
- package/dist/protocols/evm/curve-dao/index.js.map +1 -1
- package/dist/protocols/evm/uniswap-v4/index.cjs +1007 -1
- package/dist/protocols/evm/uniswap-v4/index.cjs.map +1 -1
- package/dist/protocols/evm/uniswap-v4/index.d.ts +304 -4
- package/dist/protocols/evm/uniswap-v4/index.js +965 -3
- package/dist/protocols/evm/uniswap-v4/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getClientIdFromKeyGenResult, gasLimitFromEstimateAndChainConfig, fetchChainFeeParams, gweiToDecimalString, proposalTxParamsToFeeSnapshot, alignEip1559FeesWithLatestBase, nodeFetchWithReadAuth } from '@continuumdao/continuum-node-sdk';
|
|
2
2
|
export { getClientIdFromKeyGenResult as firstClientIdFromKeyGen } from '@continuumdao/continuum-node-sdk';
|
|
3
|
-
import { getAddress, zeroAddress, defineChain, createPublicClient, http, parseGwei, serializeTransaction, keccak256, parseUnits, formatUnits, encodeFunctionData, erc20Abi,
|
|
3
|
+
import { parseAbi, parseAbiItem, getAddress, zeroAddress, defineChain, createPublicClient, http, parseGwei, serializeTransaction, keccak256, parseUnits, formatUnits, encodeFunctionData, erc20Abi, decodeFunctionData, isAddress } from 'viem';
|
|
4
4
|
|
|
5
5
|
// src/core/index.ts
|
|
6
6
|
|
|
@@ -472,6 +472,44 @@ var UNISWAP_UNIVERSAL_ROUTER_DEFAULT_GAS_UNITS = 1500000n;
|
|
|
472
472
|
var UNISWAP_TRADE_BASE_DEFAULT = "https://trade-api.gateway.uniswap.org/v1";
|
|
473
473
|
var UNISWAP_UNIVERSAL_ROUTER_VERSION_DEFAULT = "2.0";
|
|
474
474
|
var UNISWAP_SWAP_DEFAULT_EXPIRY_MINUTES = 30;
|
|
475
|
+
var UNISWAP_LP_DEFAULT_EXPIRY_MINUTES = 30;
|
|
476
|
+
var UNISWAP_LP_DEFAULT_DEADLINE_SEC_OFFSET = UNISWAP_LP_DEFAULT_EXPIRY_MINUTES * 60;
|
|
477
|
+
var UNISWAP_LP_DEFAULT_SLIPPAGE_PERCENT = 0.5;
|
|
478
|
+
var POSITION_MANAGER_BY_CHAIN = {
|
|
479
|
+
1: "0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e",
|
|
480
|
+
10: "0x3c3ea4b57a46241e54610e5f022e5c45859a1017",
|
|
481
|
+
137: "0x1ec2ebf4f37e7363fdfe3551602425af0b3ceef9",
|
|
482
|
+
42161: "0xd88f38f930b7952f2db2432cb002e7abbf3dd869",
|
|
483
|
+
8453: "0x7C5f5A4bBd8fD63184577525326123B519429bDc",
|
|
484
|
+
11155111: "0x4B2C77d209D3405F41a037Ec6c77F7F5b8e2ca80",
|
|
485
|
+
130: "0x0d97dc33264bfc1c226207428a79b26757fb9dc3",
|
|
486
|
+
1868: "0x0e2850543f69f678257266e0907ff9a58b3f13de",
|
|
487
|
+
59144: "0x661e93cca42afacb172121ef892830ca3b70f08d"
|
|
488
|
+
};
|
|
489
|
+
function getUniswapV4PositionManagerOrThrow(chainId) {
|
|
490
|
+
const raw = POSITION_MANAGER_BY_CHAIN[chainId];
|
|
491
|
+
if (!raw || !raw.startsWith("0x") || raw.length !== 42) {
|
|
492
|
+
throw new Error(
|
|
493
|
+
`No Uniswap V4 PositionManager is configured for chainId ${chainId}. Add this chain to uniswap-v4/constants or pass position manager from LP API response.`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return getAddress(raw);
|
|
497
|
+
}
|
|
498
|
+
var POSITION_MANAGER_DEPLOY_BLOCK_BY_CHAIN = {
|
|
499
|
+
/** Ethereum mainnet — Uniswap v4 launch (Jan 2025). */
|
|
500
|
+
1: 22938741n
|
|
501
|
+
};
|
|
502
|
+
var UNISWAP_V4_LP_LOG_CHUNK_SIZE_DEFAULT = 200n;
|
|
503
|
+
var UNISWAP_V4_LP_LOG_CHUNK_SIZE_MIN = 10n;
|
|
504
|
+
function getUniswapV4PositionManagerDeployBlock(chainId) {
|
|
505
|
+
return POSITION_MANAGER_DEPLOY_BLOCK_BY_CHAIN[chainId];
|
|
506
|
+
}
|
|
507
|
+
var UNISWAP_V4_LP_MINT_DEFAULT_GAS_UNITS = 1800000n;
|
|
508
|
+
var UNISWAP_V4_LP_INCREASE_DEFAULT_GAS_UNITS = 1500000n;
|
|
509
|
+
var UNISWAP_V4_LP_DECREASE_DEFAULT_GAS_UNITS = 1200000n;
|
|
510
|
+
var UNISWAP_V4_LP_COLLECT_DEFAULT_GAS_UNITS = 900000n;
|
|
511
|
+
var UNISWAP_V4_LP_ERC20_APPROVE_FALLBACK = 100000n;
|
|
512
|
+
var UNISWAP_V4_LP_WETH_DEPOSIT_FALLBACK = 120000n;
|
|
475
513
|
var DEFAULT_TRADE_BASE = "https://trade-api.gateway.uniswap.org/v1";
|
|
476
514
|
var UNISWAP_QUOTE_HEADERS_BASE = {
|
|
477
515
|
"Content-Type": "application/json",
|
|
@@ -721,7 +759,16 @@ function parseUniswapQuoteClassicInOut(res) {
|
|
|
721
759
|
const input = q.input;
|
|
722
760
|
const output = q.output;
|
|
723
761
|
const inAm = input?.amount;
|
|
724
|
-
|
|
762
|
+
let outAm = output?.amount;
|
|
763
|
+
if (typeof outAm !== "string" || !outAm) {
|
|
764
|
+
const agg = q.aggregatedOutputs;
|
|
765
|
+
if (Array.isArray(agg) && agg.length > 0) {
|
|
766
|
+
const first = agg[0];
|
|
767
|
+
if (typeof first?.amount === "string" && first.amount) {
|
|
768
|
+
outAm = first.amount;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
725
772
|
if (typeof inAm !== "string" || !inAm || typeof outAm !== "string" || !outAm) return null;
|
|
726
773
|
try {
|
|
727
774
|
return { inputWei: BigInt(inAm), outputWei: BigInt(outAm) };
|
|
@@ -1289,6 +1336,656 @@ async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
|
|
|
1289
1336
|
});
|
|
1290
1337
|
}
|
|
1291
1338
|
|
|
1339
|
+
// src/protocols/evm/uniswap-v4/liquidityApi.ts
|
|
1340
|
+
var LP_HEADERS_BASE = {
|
|
1341
|
+
"Content-Type": "application/json",
|
|
1342
|
+
Accept: "application/json",
|
|
1343
|
+
"User-Agent": "ctm-mpc-defi uniswapLpApi/1.0 (TS)"
|
|
1344
|
+
};
|
|
1345
|
+
function trimAddr2(a) {
|
|
1346
|
+
return a.trim();
|
|
1347
|
+
}
|
|
1348
|
+
function lpDeadlineUnix(nowSec = Math.floor(Date.now() / 1e3)) {
|
|
1349
|
+
return nowSec + UNISWAP_LP_DEFAULT_DEADLINE_SEC_OFFSET;
|
|
1350
|
+
}
|
|
1351
|
+
function resolveLpBaseUrl(baseUrl) {
|
|
1352
|
+
const raw = (baseUrl ?? UNISWAP_TRADE_BASE_DEFAULT).replace(/\/$/, "");
|
|
1353
|
+
return raw;
|
|
1354
|
+
}
|
|
1355
|
+
function resolveLpPath(baseUrl, action) {
|
|
1356
|
+
const normalized = baseUrl.replace(/\/$/, "");
|
|
1357
|
+
if (normalized.includes("api.uniswap.org")) {
|
|
1358
|
+
return `${normalized}/lp/${action}`;
|
|
1359
|
+
}
|
|
1360
|
+
return `${normalized}/lp/${action}`;
|
|
1361
|
+
}
|
|
1362
|
+
async function postUniswapLpApi(args) {
|
|
1363
|
+
const useProxy = args.useServerProxy !== false && typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
1364
|
+
if (useProxy && args.proxyPath) {
|
|
1365
|
+
const fn2 = args.fetchImpl ?? globalThis.fetch;
|
|
1366
|
+
const res2 = await fn2(args.proxyPath, {
|
|
1367
|
+
method: "POST",
|
|
1368
|
+
headers: { "Content-Type": "application/json" },
|
|
1369
|
+
body: JSON.stringify({
|
|
1370
|
+
uniswapApiKey: args.uniswapApiKey,
|
|
1371
|
+
baseUrl: args.baseUrl,
|
|
1372
|
+
body: args.body
|
|
1373
|
+
}),
|
|
1374
|
+
credentials: "same-origin"
|
|
1375
|
+
});
|
|
1376
|
+
const text2 = await res2.text();
|
|
1377
|
+
if (!res2.ok) {
|
|
1378
|
+
let errMsg = res2.statusText;
|
|
1379
|
+
try {
|
|
1380
|
+
const j = JSON.parse(text2);
|
|
1381
|
+
if (j?.error?.trim()) errMsg = j.error.trim();
|
|
1382
|
+
} catch {
|
|
1383
|
+
if (text2.trim()) errMsg = text2.slice(0, 500);
|
|
1384
|
+
}
|
|
1385
|
+
throw new Error(`Uniswap LP proxy failed: ${errMsg}`);
|
|
1386
|
+
}
|
|
1387
|
+
return JSON.parse(text2);
|
|
1388
|
+
}
|
|
1389
|
+
const base = resolveLpBaseUrl(args.baseUrl);
|
|
1390
|
+
const url = resolveLpPath(base, args.path);
|
|
1391
|
+
const fn = args.fetchImpl ?? globalThis.fetch;
|
|
1392
|
+
const res = await fn(url, {
|
|
1393
|
+
method: "POST",
|
|
1394
|
+
headers: {
|
|
1395
|
+
...LP_HEADERS_BASE,
|
|
1396
|
+
"x-api-key": args.uniswapApiKey.trim()
|
|
1397
|
+
},
|
|
1398
|
+
body: JSON.stringify(args.body)
|
|
1399
|
+
});
|
|
1400
|
+
const text = await res.text();
|
|
1401
|
+
if (!res.ok) {
|
|
1402
|
+
throw new Error(
|
|
1403
|
+
`Uniswap LP POST /${args.path} failed: ${messageFromUniswapHttpResponseBody(text, res.status, res.statusText)}`
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
return JSON.parse(text);
|
|
1407
|
+
}
|
|
1408
|
+
function extractUniswapLpTransaction(response, field) {
|
|
1409
|
+
const tx = response[field];
|
|
1410
|
+
if (!tx || typeof tx !== "object" || Array.isArray(tx)) {
|
|
1411
|
+
throw new Error(`Uniswap LP response missing \`${field}\` transaction object.`);
|
|
1412
|
+
}
|
|
1413
|
+
const o = tx;
|
|
1414
|
+
const to = String(o.to ?? "").trim();
|
|
1415
|
+
const data = String(o.data ?? "").trim();
|
|
1416
|
+
if (!to.startsWith("0x") || !data.startsWith("0x") || data === "0x") {
|
|
1417
|
+
throw new Error(`Uniswap LP \`${field}\` transaction has invalid to/data.`);
|
|
1418
|
+
}
|
|
1419
|
+
return {
|
|
1420
|
+
to,
|
|
1421
|
+
from: o.from != null ? String(o.from) : void 0,
|
|
1422
|
+
data,
|
|
1423
|
+
value: String(o.value ?? "0"),
|
|
1424
|
+
chainId: typeof o.chainId === "number" ? o.chainId : void 0,
|
|
1425
|
+
gasLimit: o.gasLimit != null ? String(o.gasLimit) : void 0,
|
|
1426
|
+
gasPrice: o.gasPrice != null ? String(o.gasPrice) : void 0,
|
|
1427
|
+
maxFeePerGas: o.maxFeePerGas != null ? String(o.maxFeePerGas) : void 0,
|
|
1428
|
+
maxPriorityFeePerGas: o.maxPriorityFeePerGas != null ? String(o.maxPriorityFeePerGas) : void 0
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function extractUniswapLpTokenAmounts(response) {
|
|
1432
|
+
const read = (key) => {
|
|
1433
|
+
const raw = response[key];
|
|
1434
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
1435
|
+
const o = raw;
|
|
1436
|
+
const tokenAddress = String(o.tokenAddress ?? o.token ?? "").trim();
|
|
1437
|
+
const amount = String(o.amount ?? "").trim();
|
|
1438
|
+
if (!tokenAddress.startsWith("0x") || !amount) return void 0;
|
|
1439
|
+
return { tokenAddress, amount };
|
|
1440
|
+
};
|
|
1441
|
+
return { token0: read("token0"), token1: read("token1") };
|
|
1442
|
+
}
|
|
1443
|
+
async function uniswapLpCreatePosition(args) {
|
|
1444
|
+
const body = {
|
|
1445
|
+
protocol: "V4",
|
|
1446
|
+
walletAddress: trimAddr2(args.walletAddress),
|
|
1447
|
+
chainId: args.chainId,
|
|
1448
|
+
independentToken: {
|
|
1449
|
+
tokenAddress: trimAddr2(args.independentToken.tokenAddress),
|
|
1450
|
+
amount: String(args.independentToken.amount).trim()
|
|
1451
|
+
},
|
|
1452
|
+
slippageTolerance: args.slippageTolerance ?? UNISWAP_LP_DEFAULT_SLIPPAGE_PERCENT,
|
|
1453
|
+
deadline: args.deadline ?? lpDeadlineUnix(),
|
|
1454
|
+
simulateTransaction: args.simulateTransaction ?? false
|
|
1455
|
+
};
|
|
1456
|
+
if (args.existingPool) {
|
|
1457
|
+
body.existingPool = {
|
|
1458
|
+
token0Address: trimAddr2(args.existingPool.token0Address),
|
|
1459
|
+
token1Address: trimAddr2(args.existingPool.token1Address),
|
|
1460
|
+
poolReference: trimAddr2(args.existingPool.poolReference)
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
if (args.newPool) {
|
|
1464
|
+
body.newPool = {
|
|
1465
|
+
token0Address: trimAddr2(args.newPool.token0Address),
|
|
1466
|
+
token1Address: trimAddr2(args.newPool.token1Address),
|
|
1467
|
+
fee: args.newPool.fee,
|
|
1468
|
+
tickSpacing: args.newPool.tickSpacing,
|
|
1469
|
+
initialPrice: String(args.newPool.initialPrice).trim(),
|
|
1470
|
+
...args.newPool.hooks ? { hooks: trimAddr2(args.newPool.hooks) } : {}
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
if (!args.existingPool && !args.newPool) {
|
|
1474
|
+
throw new Error("Provide existingPool or newPool for LP create.");
|
|
1475
|
+
}
|
|
1476
|
+
if (args.priceBounds) {
|
|
1477
|
+
body.priceBounds = {
|
|
1478
|
+
minPrice: String(args.priceBounds.minPrice).trim(),
|
|
1479
|
+
maxPrice: String(args.priceBounds.maxPrice).trim()
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
if (args.tickBounds) {
|
|
1483
|
+
body.tickBounds = {
|
|
1484
|
+
tickLower: args.tickBounds.tickLower,
|
|
1485
|
+
tickUpper: args.tickBounds.tickUpper
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
if (!args.priceBounds && !args.tickBounds) {
|
|
1489
|
+
throw new Error("Provide priceBounds or tickBounds for LP create.");
|
|
1490
|
+
}
|
|
1491
|
+
if (args.batchPermitData) body.batchPermitData = args.batchPermitData;
|
|
1492
|
+
if (args.signature) body.signature = args.signature;
|
|
1493
|
+
return postUniswapLpApi({
|
|
1494
|
+
uniswapApiKey: args.uniswapApiKey,
|
|
1495
|
+
path: "create",
|
|
1496
|
+
body,
|
|
1497
|
+
baseUrl: args.baseUrl,
|
|
1498
|
+
fetchImpl: args.fetchImpl,
|
|
1499
|
+
useServerProxy: args.useServerProxy,
|
|
1500
|
+
proxyPath: "/api/uniswap/liquidity/create"
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
async function uniswapLpIncreasePosition(args) {
|
|
1504
|
+
const body = {
|
|
1505
|
+
protocol: "V4",
|
|
1506
|
+
walletAddress: trimAddr2(args.walletAddress),
|
|
1507
|
+
chainId: args.chainId,
|
|
1508
|
+
token0Address: trimAddr2(args.token0Address),
|
|
1509
|
+
token1Address: trimAddr2(args.token1Address),
|
|
1510
|
+
nftTokenId: String(args.nftTokenId),
|
|
1511
|
+
independentToken: {
|
|
1512
|
+
tokenAddress: trimAddr2(args.independentToken.tokenAddress),
|
|
1513
|
+
amount: String(args.independentToken.amount).trim()
|
|
1514
|
+
},
|
|
1515
|
+
slippageTolerance: args.slippageTolerance ?? UNISWAP_LP_DEFAULT_SLIPPAGE_PERCENT,
|
|
1516
|
+
deadline: args.deadline ?? lpDeadlineUnix(),
|
|
1517
|
+
simulateTransaction: args.simulateTransaction ?? false
|
|
1518
|
+
};
|
|
1519
|
+
if (args.v4BatchPermitData) body.v4BatchPermitData = args.v4BatchPermitData;
|
|
1520
|
+
if (args.signature) body.signature = args.signature;
|
|
1521
|
+
return postUniswapLpApi({
|
|
1522
|
+
uniswapApiKey: args.uniswapApiKey,
|
|
1523
|
+
path: "increase",
|
|
1524
|
+
body,
|
|
1525
|
+
baseUrl: args.baseUrl,
|
|
1526
|
+
fetchImpl: args.fetchImpl,
|
|
1527
|
+
useServerProxy: args.useServerProxy,
|
|
1528
|
+
proxyPath: "/api/uniswap/liquidity/increase"
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
async function uniswapLpDecreasePosition(args) {
|
|
1532
|
+
const pct = Math.trunc(args.liquidityPercentageToDecrease);
|
|
1533
|
+
if (!Number.isFinite(pct) || pct < 1 || pct > 100) {
|
|
1534
|
+
throw new Error("liquidityPercentageToDecrease must be an integer from 1 to 100.");
|
|
1535
|
+
}
|
|
1536
|
+
const body = {
|
|
1537
|
+
protocol: "V4",
|
|
1538
|
+
walletAddress: trimAddr2(args.walletAddress),
|
|
1539
|
+
chainId: args.chainId,
|
|
1540
|
+
token0Address: trimAddr2(args.token0Address),
|
|
1541
|
+
token1Address: trimAddr2(args.token1Address),
|
|
1542
|
+
nftTokenId: String(args.nftTokenId),
|
|
1543
|
+
liquidityPercentageToDecrease: pct,
|
|
1544
|
+
slippageTolerance: args.slippageTolerance ?? UNISWAP_LP_DEFAULT_SLIPPAGE_PERCENT,
|
|
1545
|
+
deadline: args.deadline ?? lpDeadlineUnix(),
|
|
1546
|
+
simulateTransaction: args.simulateTransaction ?? false
|
|
1547
|
+
};
|
|
1548
|
+
return postUniswapLpApi({
|
|
1549
|
+
uniswapApiKey: args.uniswapApiKey,
|
|
1550
|
+
path: "decrease",
|
|
1551
|
+
body,
|
|
1552
|
+
baseUrl: args.baseUrl,
|
|
1553
|
+
fetchImpl: args.fetchImpl,
|
|
1554
|
+
useServerProxy: args.useServerProxy,
|
|
1555
|
+
proxyPath: "/api/uniswap/liquidity/decrease"
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
async function uniswapLpClaimFees(args) {
|
|
1559
|
+
const body = {
|
|
1560
|
+
protocol: "V4",
|
|
1561
|
+
walletAddress: trimAddr2(args.walletAddress),
|
|
1562
|
+
chainId: args.chainId,
|
|
1563
|
+
tokenId: String(args.tokenId),
|
|
1564
|
+
simulateTransaction: args.simulateTransaction ?? false
|
|
1565
|
+
};
|
|
1566
|
+
return postUniswapLpApi({
|
|
1567
|
+
uniswapApiKey: args.uniswapApiKey,
|
|
1568
|
+
path: "claim",
|
|
1569
|
+
body,
|
|
1570
|
+
baseUrl: args.baseUrl,
|
|
1571
|
+
fetchImpl: args.fetchImpl,
|
|
1572
|
+
useServerProxy: args.useServerProxy,
|
|
1573
|
+
proxyPath: "/api/uniswap/liquidity/claim"
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
var TX_FIELD_BY_ACTION = {
|
|
1577
|
+
mint: "create",
|
|
1578
|
+
increase: "increase",
|
|
1579
|
+
decrease: "decrease",
|
|
1580
|
+
collect: "claim"
|
|
1581
|
+
};
|
|
1582
|
+
function parseUniswapLpApiSnapshot(args) {
|
|
1583
|
+
const field = TX_FIELD_BY_ACTION[args.action];
|
|
1584
|
+
const transaction = extractUniswapLpTransaction(args.lpResponse, field);
|
|
1585
|
+
const amounts = extractUniswapLpTokenAmounts(args.lpResponse);
|
|
1586
|
+
const tickLower = typeof args.lpResponse.tickLower === "number" ? args.lpResponse.tickLower : void 0;
|
|
1587
|
+
const tickUpper = typeof args.lpResponse.tickUpper === "number" ? args.lpResponse.tickUpper : void 0;
|
|
1588
|
+
const minPrice = typeof args.lpResponse.minPrice === "string" ? args.lpResponse.minPrice : void 0;
|
|
1589
|
+
const maxPrice = typeof args.lpResponse.maxPrice === "string" ? args.lpResponse.maxPrice : void 0;
|
|
1590
|
+
return {
|
|
1591
|
+
transaction,
|
|
1592
|
+
token0: amounts.token0,
|
|
1593
|
+
token1: amounts.token1,
|
|
1594
|
+
tickLower,
|
|
1595
|
+
tickUpper,
|
|
1596
|
+
minPrice,
|
|
1597
|
+
maxPrice
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
var erc721BalanceAbi = parseAbi([
|
|
1601
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
1602
|
+
"function ownerOf(uint256 tokenId) view returns (address)"
|
|
1603
|
+
]);
|
|
1604
|
+
parseAbi([
|
|
1605
|
+
"function positionInfo(uint256 tokenId) view returns (uint256 info)"
|
|
1606
|
+
]);
|
|
1607
|
+
parseAbiItem(
|
|
1608
|
+
"event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
|
|
1609
|
+
);
|
|
1610
|
+
function throwIfScanAborted(signal) {
|
|
1611
|
+
if (signal?.aborted) {
|
|
1612
|
+
throw new DOMException("Position scan aborted.", "AbortError");
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
var TRANSFER_EVENT = {
|
|
1616
|
+
type: "event",
|
|
1617
|
+
name: "Transfer",
|
|
1618
|
+
inputs: [
|
|
1619
|
+
{ indexed: true, name: "from", type: "address" },
|
|
1620
|
+
{ indexed: true, name: "to", type: "address" },
|
|
1621
|
+
{ indexed: true, name: "tokenId", type: "uint256" }
|
|
1622
|
+
]
|
|
1623
|
+
};
|
|
1624
|
+
function isEthGetLogsBlockRangeTooLargeError(err) {
|
|
1625
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
1626
|
+
return msg.includes("block range too large") || msg.includes("maximum allowed") || msg.includes("query returned more than") || msg.includes("exceed maximum block range") || msg.includes("block range is too large");
|
|
1627
|
+
}
|
|
1628
|
+
async function getTransferLogsForWalletInRange(args) {
|
|
1629
|
+
const { client, positionManager, wallet, fromBlock, toBlock } = args;
|
|
1630
|
+
let chunkSize = args.chunkSize < UNISWAP_V4_LP_LOG_CHUNK_SIZE_MIN ? UNISWAP_V4_LP_LOG_CHUNK_SIZE_MIN : args.chunkSize;
|
|
1631
|
+
const out = [];
|
|
1632
|
+
let from = fromBlock;
|
|
1633
|
+
while (from <= toBlock) {
|
|
1634
|
+
let to = from + chunkSize - 1n > toBlock ? toBlock : from + chunkSize - 1n;
|
|
1635
|
+
for (; ; ) {
|
|
1636
|
+
try {
|
|
1637
|
+
const [toLogs, fromLogs] = await Promise.all([
|
|
1638
|
+
client.getLogs({
|
|
1639
|
+
address: positionManager,
|
|
1640
|
+
event: TRANSFER_EVENT,
|
|
1641
|
+
args: { to: wallet },
|
|
1642
|
+
fromBlock: from,
|
|
1643
|
+
toBlock: to
|
|
1644
|
+
}),
|
|
1645
|
+
client.getLogs({
|
|
1646
|
+
address: positionManager,
|
|
1647
|
+
event: TRANSFER_EVENT,
|
|
1648
|
+
args: { from: wallet },
|
|
1649
|
+
fromBlock: from,
|
|
1650
|
+
toBlock: to
|
|
1651
|
+
})
|
|
1652
|
+
]);
|
|
1653
|
+
out.push(...toLogs, ...fromLogs);
|
|
1654
|
+
from = to + 1n;
|
|
1655
|
+
break;
|
|
1656
|
+
} catch (err) {
|
|
1657
|
+
if (!isEthGetLogsBlockRangeTooLargeError(err) || chunkSize <= UNISWAP_V4_LP_LOG_CHUNK_SIZE_MIN) {
|
|
1658
|
+
throw err;
|
|
1659
|
+
}
|
|
1660
|
+
chunkSize = chunkSize / 2n;
|
|
1661
|
+
if (chunkSize < UNISWAP_V4_LP_LOG_CHUNK_SIZE_MIN) {
|
|
1662
|
+
chunkSize = UNISWAP_V4_LP_LOG_CHUNK_SIZE_MIN;
|
|
1663
|
+
}
|
|
1664
|
+
to = from + chunkSize - 1n > toBlock ? toBlock : from + chunkSize - 1n;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
return out;
|
|
1669
|
+
}
|
|
1670
|
+
function defaultScanFromBlock(args) {
|
|
1671
|
+
if (args.fromBlock != null) return args.fromBlock;
|
|
1672
|
+
const deploy = getUniswapV4PositionManagerDeployBlock(args.chainId);
|
|
1673
|
+
if (deploy != null) return deploy;
|
|
1674
|
+
return args.latest > args.maxBlocksToScan ? args.latest - args.maxBlocksToScan : 0n;
|
|
1675
|
+
}
|
|
1676
|
+
async function listUniswapV4PositionsForWallet(args) {
|
|
1677
|
+
throwIfScanAborted(args.signal);
|
|
1678
|
+
const wallet = getAddress(args.walletAddress);
|
|
1679
|
+
const pm = getAddress(
|
|
1680
|
+
args.positionManagerAddress ? String(args.positionManagerAddress) : getUniswapV4PositionManagerOrThrow(args.chainId)
|
|
1681
|
+
);
|
|
1682
|
+
const chain = defineChain({
|
|
1683
|
+
id: args.chainId,
|
|
1684
|
+
name: `uniswap-v4-${args.chainId}`,
|
|
1685
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
1686
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1687
|
+
});
|
|
1688
|
+
const client = createPublicClient({ chain, transport: http(args.rpcUrl) });
|
|
1689
|
+
const latest = await client.getBlockNumber();
|
|
1690
|
+
const maxScan = args.maxBlocksToScan ?? 500000n;
|
|
1691
|
+
const chunkSize = args.chunkSize ?? UNISWAP_V4_LP_LOG_CHUNK_SIZE_DEFAULT;
|
|
1692
|
+
const scanFrom = defaultScanFromBlock({
|
|
1693
|
+
chainId: args.chainId,
|
|
1694
|
+
latest,
|
|
1695
|
+
fromBlock: args.fromBlock,
|
|
1696
|
+
maxBlocksToScan: maxScan
|
|
1697
|
+
});
|
|
1698
|
+
const balance = await client.readContract({
|
|
1699
|
+
address: pm,
|
|
1700
|
+
abi: erc721BalanceAbi,
|
|
1701
|
+
functionName: "balanceOf",
|
|
1702
|
+
args: [wallet]
|
|
1703
|
+
});
|
|
1704
|
+
const targetCount = Number(balance);
|
|
1705
|
+
const emitProgress = (scanningFromBlock, scanningToBlock, foundCount) => {
|
|
1706
|
+
args.onProgress?.({
|
|
1707
|
+
latestBlock: latest.toString(),
|
|
1708
|
+
scanFromBlock: scanFrom.toString(),
|
|
1709
|
+
scanningFromBlock: scanningFromBlock.toString(),
|
|
1710
|
+
scanningToBlock: scanningToBlock.toString(),
|
|
1711
|
+
foundCount,
|
|
1712
|
+
targetCount
|
|
1713
|
+
});
|
|
1714
|
+
};
|
|
1715
|
+
if (balance === 0n) {
|
|
1716
|
+
emitProgress(latest, latest, 0);
|
|
1717
|
+
return [];
|
|
1718
|
+
}
|
|
1719
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1720
|
+
const verified = /* @__PURE__ */ new Map();
|
|
1721
|
+
let toBlock = latest;
|
|
1722
|
+
while (toBlock >= scanFrom && verified.size < targetCount) {
|
|
1723
|
+
throwIfScanAborted(args.signal);
|
|
1724
|
+
let fromBlock = toBlock >= chunkSize - 1n ? toBlock - chunkSize + 1n : 0n;
|
|
1725
|
+
if (fromBlock < scanFrom) fromBlock = scanFrom;
|
|
1726
|
+
emitProgress(fromBlock, toBlock, verified.size);
|
|
1727
|
+
const logs = await getTransferLogsForWalletInRange({
|
|
1728
|
+
client,
|
|
1729
|
+
positionManager: pm,
|
|
1730
|
+
wallet,
|
|
1731
|
+
fromBlock,
|
|
1732
|
+
toBlock,
|
|
1733
|
+
chunkSize
|
|
1734
|
+
});
|
|
1735
|
+
for (const log of logs) {
|
|
1736
|
+
const tokenId = log.args.tokenId?.toString();
|
|
1737
|
+
if (!tokenId) continue;
|
|
1738
|
+
const to = log.args.to != null ? getAddress(log.args.to) : null;
|
|
1739
|
+
const from = log.args.from != null ? getAddress(log.args.from) : null;
|
|
1740
|
+
if (to === wallet) candidates.add(tokenId);
|
|
1741
|
+
if (from === wallet) candidates.delete(tokenId);
|
|
1742
|
+
}
|
|
1743
|
+
for (const tokenId of candidates) {
|
|
1744
|
+
if (verified.has(tokenId)) continue;
|
|
1745
|
+
try {
|
|
1746
|
+
const owner = await client.readContract({
|
|
1747
|
+
address: pm,
|
|
1748
|
+
abi: erc721BalanceAbi,
|
|
1749
|
+
functionName: "ownerOf",
|
|
1750
|
+
args: [BigInt(tokenId)]
|
|
1751
|
+
});
|
|
1752
|
+
if (getAddress(owner) !== wallet) continue;
|
|
1753
|
+
verified.set(tokenId, { tokenId, positionManager: pm, owner: wallet });
|
|
1754
|
+
if (verified.size >= targetCount) break;
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
if (verified.size >= targetCount) break;
|
|
1759
|
+
if (fromBlock <= scanFrom) break;
|
|
1760
|
+
toBlock = fromBlock - 1n;
|
|
1761
|
+
}
|
|
1762
|
+
const out = [...verified.values()];
|
|
1763
|
+
out.sort((a, b) => BigInt(a.tokenId) < BigInt(b.tokenId) ? -1 : 1);
|
|
1764
|
+
return out;
|
|
1765
|
+
}
|
|
1766
|
+
function isNativeUniswapLpTokenAddress(token) {
|
|
1767
|
+
try {
|
|
1768
|
+
return getAddress(token) === zeroAddress;
|
|
1769
|
+
} catch {
|
|
1770
|
+
return token.toLowerCase() === zeroAddress.toLowerCase();
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// src/protocols/evm/uniswap-v4/liquidityMultisign.ts
|
|
1775
|
+
var wethDepositAbi = parseAbi(["function deposit() payable"]);
|
|
1776
|
+
var erc20AllowanceAbi = parseAbi([
|
|
1777
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1778
|
+
"function decimals() view returns (uint8)"
|
|
1779
|
+
]);
|
|
1780
|
+
function parseOptionalGasLimitString2(raw) {
|
|
1781
|
+
if (raw == null) return null;
|
|
1782
|
+
const s = String(raw).trim();
|
|
1783
|
+
if (!s) return null;
|
|
1784
|
+
try {
|
|
1785
|
+
if (/^0x[0-9a-fA-F]+$/.test(s)) return BigInt(s);
|
|
1786
|
+
return BigInt(s);
|
|
1787
|
+
} catch {
|
|
1788
|
+
return null;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
function parseTxValueWei(tx) {
|
|
1792
|
+
const raw = tx.value ?? "0";
|
|
1793
|
+
try {
|
|
1794
|
+
if (typeof raw === "string" && /^0x[0-9a-fA-F]+$/.test(raw.trim())) return BigInt(raw.trim());
|
|
1795
|
+
return BigInt(String(raw).trim() || "0");
|
|
1796
|
+
} catch {
|
|
1797
|
+
return 0n;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
function dataHexFromTx(tx) {
|
|
1801
|
+
const d = (tx.data ?? "0x").toString().trim();
|
|
1802
|
+
return d.startsWith("0x") ? d : `0x${d}`;
|
|
1803
|
+
}
|
|
1804
|
+
function defaultGasForAction(action) {
|
|
1805
|
+
switch (action) {
|
|
1806
|
+
case "mint":
|
|
1807
|
+
return UNISWAP_V4_LP_MINT_DEFAULT_GAS_UNITS;
|
|
1808
|
+
case "increase":
|
|
1809
|
+
return UNISWAP_V4_LP_INCREASE_DEFAULT_GAS_UNITS;
|
|
1810
|
+
case "decrease":
|
|
1811
|
+
return UNISWAP_V4_LP_DECREASE_DEFAULT_GAS_UNITS;
|
|
1812
|
+
case "collect":
|
|
1813
|
+
return UNISWAP_V4_LP_COLLECT_DEFAULT_GAS_UNITS;
|
|
1814
|
+
default:
|
|
1815
|
+
return UNISWAP_V4_LP_MINT_DEFAULT_GAS_UNITS;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
async function resolveApproveTargets(args) {
|
|
1819
|
+
const out = [];
|
|
1820
|
+
const pairs = [args.token0, args.token1].filter(Boolean);
|
|
1821
|
+
for (const row of pairs) {
|
|
1822
|
+
const amt = BigInt(row.amount);
|
|
1823
|
+
if (amt <= 0n) continue;
|
|
1824
|
+
if (isNativeUniswapLpTokenAddress(row.tokenAddress)) {
|
|
1825
|
+
if (!args.nativeWrapped) {
|
|
1826
|
+
throw new Error("nativeWrapped is required when LP uses native ETH (0x0) token.");
|
|
1827
|
+
}
|
|
1828
|
+
out.push({ token: getAddress(args.nativeWrapped), amount: amt, kind: "weth_deposit" });
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
out.push({ token: getAddress(row.tokenAddress), amount: amt, kind: "approve" });
|
|
1832
|
+
}
|
|
1833
|
+
const deduped = [];
|
|
1834
|
+
for (const row of out) {
|
|
1835
|
+
const existing = deduped.find((d) => d.token === row.token && d.kind === row.kind);
|
|
1836
|
+
if (existing) {
|
|
1837
|
+
if (row.amount > existing.amount) existing.amount = row.amount;
|
|
1838
|
+
} else {
|
|
1839
|
+
deduped.push({ ...row });
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
const steps = [];
|
|
1843
|
+
for (const row of deduped) {
|
|
1844
|
+
if (row.kind === "weth_deposit") {
|
|
1845
|
+
steps.push(row);
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
const allowance = await args.publicClient.readContract({
|
|
1849
|
+
address: row.token,
|
|
1850
|
+
abi: erc20AllowanceAbi,
|
|
1851
|
+
functionName: "allowance",
|
|
1852
|
+
args: [args.executor, args.spender]
|
|
1853
|
+
});
|
|
1854
|
+
if (allowance < row.amount) {
|
|
1855
|
+
steps.push(row);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
return steps;
|
|
1859
|
+
}
|
|
1860
|
+
async function buildEvmMultisignBodyUniswapV4LiquidityBatchInternal(args) {
|
|
1861
|
+
const parsed = parseUniswapLpApiSnapshot({ action: args.action, lpResponse: args.lpResponse });
|
|
1862
|
+
const tx = parsed.transaction;
|
|
1863
|
+
const to = getAddress(tx.to);
|
|
1864
|
+
const dataHex = dataHexFromTx(tx);
|
|
1865
|
+
const valueWei = parseTxValueWei(tx);
|
|
1866
|
+
const executor = getAddress(args.executorAddress);
|
|
1867
|
+
const spender = to;
|
|
1868
|
+
const chain = defineChain({
|
|
1869
|
+
id: args.chainId,
|
|
1870
|
+
name: `uniswap-v4-lp-${args.chainId}`,
|
|
1871
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
1872
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1873
|
+
});
|
|
1874
|
+
const publicClient = createPublicClient({ chain, transport: http(args.rpcUrl) });
|
|
1875
|
+
const approveTargets = await resolveApproveTargets({
|
|
1876
|
+
publicClient,
|
|
1877
|
+
executor,
|
|
1878
|
+
spender,
|
|
1879
|
+
token0: parsed.token0,
|
|
1880
|
+
token1: parsed.token1,
|
|
1881
|
+
nativeWrapped: args.nativeWrapped
|
|
1882
|
+
});
|
|
1883
|
+
const steps = [];
|
|
1884
|
+
for (const target of approveTargets) {
|
|
1885
|
+
if (target.kind === "weth_deposit") {
|
|
1886
|
+
steps.push({
|
|
1887
|
+
to: target.token,
|
|
1888
|
+
data: encodeFunctionData({ abi: wethDepositAbi, functionName: "deposit" }),
|
|
1889
|
+
value: target.amount,
|
|
1890
|
+
fallbackGas: UNISWAP_V4_LP_WETH_DEPOSIT_FALLBACK
|
|
1891
|
+
});
|
|
1892
|
+
steps.push({
|
|
1893
|
+
to: target.token,
|
|
1894
|
+
data: encodeFunctionData({
|
|
1895
|
+
abi: erc20Abi,
|
|
1896
|
+
functionName: "approve",
|
|
1897
|
+
args: [spender, target.amount]
|
|
1898
|
+
}),
|
|
1899
|
+
value: 0n,
|
|
1900
|
+
fallbackGas: UNISWAP_V4_LP_ERC20_APPROVE_FALLBACK
|
|
1901
|
+
});
|
|
1902
|
+
} else {
|
|
1903
|
+
steps.push({
|
|
1904
|
+
to: target.token,
|
|
1905
|
+
data: encodeFunctionData({
|
|
1906
|
+
abi: erc20Abi,
|
|
1907
|
+
functionName: "approve",
|
|
1908
|
+
args: [spender, target.amount]
|
|
1909
|
+
}),
|
|
1910
|
+
value: 0n,
|
|
1911
|
+
fallbackGas: UNISWAP_V4_LP_ERC20_APPROVE_FALLBACK
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
const lpFallbackGas = defaultGasForAction(args.action);
|
|
1916
|
+
const fromTradeApi = parseOptionalGasLimitString2(tx.gasLimit) ?? parseOptionalGasLimitString2(tx.gas);
|
|
1917
|
+
steps.push({
|
|
1918
|
+
to,
|
|
1919
|
+
data: dataHex,
|
|
1920
|
+
value: valueWei,
|
|
1921
|
+
fallbackGas: fromTradeApi != null && fromTradeApi > 0n ? fromTradeApi : lpFallbackGas
|
|
1922
|
+
});
|
|
1923
|
+
const actionLabel = args.action === "mint" ? "mint liquidity" : args.action === "increase" ? "increase liquidity" : args.action === "decrease" ? "decrease liquidity" : "collect fees";
|
|
1924
|
+
const dataNo0x = dataHex.startsWith("0x") ? dataHex.slice(2) : dataHex;
|
|
1925
|
+
const lpIndex = steps.length - 1;
|
|
1926
|
+
return buildEvmMultisignBatch({
|
|
1927
|
+
context: {
|
|
1928
|
+
chainCategory: "evm",
|
|
1929
|
+
keyGen: args.keyGen,
|
|
1930
|
+
purposeText: args.purposeText,
|
|
1931
|
+
chainId: args.chainId,
|
|
1932
|
+
rpcUrl: args.rpcUrl,
|
|
1933
|
+
executorAddress: args.executorAddress,
|
|
1934
|
+
chainDetail: args.chainDetail,
|
|
1935
|
+
useCustomGas: args.useCustomGas,
|
|
1936
|
+
customGasChainDetails: args.customGasChainDetails
|
|
1937
|
+
},
|
|
1938
|
+
steps,
|
|
1939
|
+
purposeSuffix: `Uniswap V4: ${steps.length}-tx batch \u2014 ${actionLabel} (classic ERC-20 approve + Position Manager).`,
|
|
1940
|
+
firstMsgRawNo0x: dataNo0x,
|
|
1941
|
+
destinationAddress: to,
|
|
1942
|
+
buildBatchMeta: ({ index }) => {
|
|
1943
|
+
const isLpStep = index === lpIndex;
|
|
1944
|
+
return {
|
|
1945
|
+
signatureText: JSON.stringify({
|
|
1946
|
+
kind: "UniswapV4Liquidity",
|
|
1947
|
+
action: args.action,
|
|
1948
|
+
step: index,
|
|
1949
|
+
lpTx: isLpStep
|
|
1950
|
+
}),
|
|
1951
|
+
...isLpStep ? {
|
|
1952
|
+
evm: { type: "uniswap_v4_liquidity_tx", version: 1, chainId: String(args.chainId) },
|
|
1953
|
+
uniswapV4Liquidity: {
|
|
1954
|
+
action: args.action,
|
|
1955
|
+
skipPermit2Batch: true,
|
|
1956
|
+
nftTokenId: args.nftTokenId != null ? String(args.nftTokenId) : void 0,
|
|
1957
|
+
poolReference: args.poolReference,
|
|
1958
|
+
lpResponseSnapshot: args.lpResponse,
|
|
1959
|
+
token0: parsed.token0,
|
|
1960
|
+
token1: parsed.token1,
|
|
1961
|
+
tickLower: parsed.tickLower,
|
|
1962
|
+
tickUpper: parsed.tickUpper,
|
|
1963
|
+
transaction: {
|
|
1964
|
+
to: tx.to,
|
|
1965
|
+
value: tx.value,
|
|
1966
|
+
dataNibbles: dataNo0x.length,
|
|
1967
|
+
gasLimit: tx.gasLimit
|
|
1968
|
+
},
|
|
1969
|
+
originalPurpose: args.purposeText
|
|
1970
|
+
}
|
|
1971
|
+
} : {}
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
async function buildEvmMultisignBodyUniswapV4MintLiquidityBatch(args) {
|
|
1977
|
+
return buildEvmMultisignBodyUniswapV4LiquidityBatchInternal({ ...args, action: "mint" });
|
|
1978
|
+
}
|
|
1979
|
+
async function buildEvmMultisignBodyUniswapV4IncreaseLiquidityBatch(args) {
|
|
1980
|
+
return buildEvmMultisignBodyUniswapV4LiquidityBatchInternal({ ...args, action: "increase" });
|
|
1981
|
+
}
|
|
1982
|
+
async function buildEvmMultisignBodyUniswapV4DecreaseLiquidityBatch(args) {
|
|
1983
|
+
return buildEvmMultisignBodyUniswapV4LiquidityBatchInternal({ ...args, action: "decrease" });
|
|
1984
|
+
}
|
|
1985
|
+
async function buildEvmMultisignBodyUniswapV4CollectFeesBatch(args) {
|
|
1986
|
+
return buildEvmMultisignBodyUniswapV4LiquidityBatchInternal({ ...args, action: "collect" });
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1292
1989
|
// src/protocols/evm/uniswap-v4/swap.ts
|
|
1293
1990
|
async function swapFromQuote(args) {
|
|
1294
1991
|
const deadline = args.swapTransactionDeadlineUnix ?? swapTransactionDeadlineUnixFromExpiryMinutes(30);
|
|
@@ -1371,6 +2068,52 @@ var uniswapV4ProtocolModule = {
|
|
|
1371
2068
|
amount: { type: "string", required: true, description: "Amount for quote" },
|
|
1372
2069
|
type: { type: "EXACT_INPUT | EXACT_OUTPUT", required: true, description: "Trade type" }
|
|
1373
2070
|
}
|
|
2071
|
+
},
|
|
2072
|
+
{
|
|
2073
|
+
id: "uniswap-v4.mint-liquidity",
|
|
2074
|
+
protocolId: UNISWAP_V4_PROTOCOL_ID,
|
|
2075
|
+
chainCategory: "evm",
|
|
2076
|
+
description: "Mint a new Uniswap V4 concentrated liquidity position (Position Manager NFT)",
|
|
2077
|
+
commonParams: ["keyGen", "purposeText", "useCustomGas"],
|
|
2078
|
+
params: {
|
|
2079
|
+
lpResponse: { type: "object", required: true, description: "Full LP API create response" },
|
|
2080
|
+
nativeWrapped: { type: "address", required: false, description: "WETH when pool uses native ETH" },
|
|
2081
|
+
poolReference: { type: "string", required: false, description: "V4 pool id" },
|
|
2082
|
+
uniswapApiKey: { type: "string", required: true, description: "Uniswap API key" }
|
|
2083
|
+
}
|
|
2084
|
+
},
|
|
2085
|
+
{
|
|
2086
|
+
id: "uniswap-v4.increase-liquidity",
|
|
2087
|
+
protocolId: UNISWAP_V4_PROTOCOL_ID,
|
|
2088
|
+
chainCategory: "evm",
|
|
2089
|
+
description: "Increase liquidity on an existing Uniswap V4 position NFT",
|
|
2090
|
+
commonParams: ["keyGen", "purposeText", "useCustomGas"],
|
|
2091
|
+
params: {
|
|
2092
|
+
nftTokenId: { type: "string", required: true, description: "Position NFT token id" },
|
|
2093
|
+
lpResponse: { type: "object", required: true, description: "Full LP API increase response" }
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
{
|
|
2097
|
+
id: "uniswap-v4.decrease-liquidity",
|
|
2098
|
+
protocolId: UNISWAP_V4_PROTOCOL_ID,
|
|
2099
|
+
chainCategory: "evm",
|
|
2100
|
+
description: "Decrease liquidity on an existing Uniswap V4 position NFT",
|
|
2101
|
+
commonParams: ["keyGen", "purposeText", "useCustomGas"],
|
|
2102
|
+
params: {
|
|
2103
|
+
nftTokenId: { type: "string", required: true, description: "Position NFT token id" },
|
|
2104
|
+
lpResponse: { type: "object", required: true, description: "Full LP API decrease response" }
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
id: "uniswap-v4.collect-fees",
|
|
2109
|
+
protocolId: UNISWAP_V4_PROTOCOL_ID,
|
|
2110
|
+
chainCategory: "evm",
|
|
2111
|
+
description: "Collect accrued fees from a Uniswap V4 position NFT",
|
|
2112
|
+
commonParams: ["keyGen", "purposeText", "useCustomGas"],
|
|
2113
|
+
params: {
|
|
2114
|
+
nftTokenId: { type: "string", required: true, description: "Position NFT token id" },
|
|
2115
|
+
lpResponse: { type: "object", required: true, description: "Full LP API claim response" }
|
|
2116
|
+
}
|
|
1374
2117
|
}
|
|
1375
2118
|
]
|
|
1376
2119
|
};
|
|
@@ -1382,6 +2125,15 @@ var uniswapV4 = {
|
|
|
1382
2125
|
swapFromQuote,
|
|
1383
2126
|
buildSwapMultisignBody: buildEvmMultisignBodyUniswapV4SkipPermit2Batch,
|
|
1384
2127
|
quote: uniswapTradeQuote,
|
|
2128
|
+
createLiquidityPosition: uniswapLpCreatePosition,
|
|
2129
|
+
increaseLiquidityPosition: uniswapLpIncreasePosition,
|
|
2130
|
+
decreaseLiquidityPosition: uniswapLpDecreasePosition,
|
|
2131
|
+
claimLiquidityFees: uniswapLpClaimFees,
|
|
2132
|
+
buildMintLiquidityMultisignBody: buildEvmMultisignBodyUniswapV4MintLiquidityBatch,
|
|
2133
|
+
buildIncreaseLiquidityMultisignBody: buildEvmMultisignBodyUniswapV4IncreaseLiquidityBatch,
|
|
2134
|
+
buildDecreaseLiquidityMultisignBody: buildEvmMultisignBodyUniswapV4DecreaseLiquidityBatch,
|
|
2135
|
+
buildCollectFeesMultisignBody: buildEvmMultisignBodyUniswapV4CollectFeesBatch,
|
|
2136
|
+
listPositions: listUniswapV4PositionsForWallet,
|
|
1385
2137
|
isChainSupported: isUniswapV4ChainSupported
|
|
1386
2138
|
};
|
|
1387
2139
|
|
|
@@ -1476,44 +2228,64 @@ function swappableCurveGraphNodeKeys(adj) {
|
|
|
1476
2228
|
}
|
|
1477
2229
|
|
|
1478
2230
|
// src/protocols/evm/curve-dao/apiSession.ts
|
|
2231
|
+
var CURVE_SESSION_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2232
|
+
var curveSessionCache = /* @__PURE__ */ new Map();
|
|
1479
2233
|
async function fetchAllCurvePools(curve) {
|
|
1480
2234
|
const run = (p) => p.catch(() => void 0);
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
2235
|
+
const tasks = [];
|
|
2236
|
+
for (const key of [
|
|
2237
|
+
"factory",
|
|
2238
|
+
"crvUSDFactory",
|
|
2239
|
+
"EYWAFactory",
|
|
2240
|
+
"cryptoFactory",
|
|
2241
|
+
"twocryptoFactory",
|
|
2242
|
+
"tricryptoFactory",
|
|
2243
|
+
"stableNgFactory"
|
|
2244
|
+
]) {
|
|
2245
|
+
const f = curve[key];
|
|
2246
|
+
if (f?.fetchPools) {
|
|
2247
|
+
tasks.push(run(f.fetchPools()));
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
await Promise.all(tasks);
|
|
1490
2251
|
}
|
|
1491
2252
|
async function loadFullCurveSessionForRpc(rpcUrl) {
|
|
1492
2253
|
const url = (rpcUrl ?? "").trim();
|
|
1493
|
-
if (!url)
|
|
2254
|
+
if (!url) {
|
|
2255
|
+
throw new Error("rpcUrl is required.");
|
|
2256
|
+
}
|
|
2257
|
+
const cached = curveSessionCache.get(url);
|
|
2258
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
2259
|
+
return cached.session;
|
|
2260
|
+
}
|
|
1494
2261
|
try {
|
|
1495
2262
|
const { default: curve } = await import('@curvefi/api');
|
|
1496
2263
|
await curve.init("JsonRpc", { url }, {});
|
|
1497
2264
|
const wrapped = curve.getNetworkConstants().NATIVE_COIN?.wrappedAddress;
|
|
1498
2265
|
if (!curve.hasRouter || !curve.hasRouter()) {
|
|
1499
|
-
|
|
2266
|
+
const session2 = {
|
|
1500
2267
|
curve,
|
|
1501
2268
|
adj: /* @__PURE__ */ new Map(),
|
|
1502
2269
|
swappableNodeKeys: /* @__PURE__ */ new Set(),
|
|
1503
2270
|
wrappedNative: wrapped
|
|
1504
2271
|
};
|
|
2272
|
+
curveSessionCache.set(url, { session: session2, expiresAt: Date.now() + CURVE_SESSION_CACHE_TTL_MS });
|
|
2273
|
+
return session2;
|
|
1505
2274
|
}
|
|
1506
2275
|
await fetchAllCurvePools(curve);
|
|
1507
2276
|
const adj = buildCurveLiquidityGraphFromApi(curve);
|
|
1508
2277
|
addNativeWethBridge(adj, wrapped);
|
|
1509
|
-
|
|
2278
|
+
const session = {
|
|
1510
2279
|
curve,
|
|
1511
2280
|
adj,
|
|
1512
2281
|
swappableNodeKeys: swappableCurveGraphNodeKeys(adj),
|
|
1513
2282
|
wrappedNative: wrapped
|
|
1514
2283
|
};
|
|
1515
|
-
|
|
1516
|
-
return
|
|
2284
|
+
curveSessionCache.set(url, { session, expiresAt: Date.now() + CURVE_SESSION_CACHE_TTL_MS });
|
|
2285
|
+
return session;
|
|
2286
|
+
} catch (e) {
|
|
2287
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2288
|
+
throw new Error(`Curve session init failed for RPC: ${msg}`);
|
|
1517
2289
|
}
|
|
1518
2290
|
}
|
|
1519
2291
|
|
|
@@ -1566,7 +2338,7 @@ async function buildEvmMultisignBodyCurveDaoBatch(args) {
|
|
|
1566
2338
|
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1567
2339
|
});
|
|
1568
2340
|
const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
1569
|
-
const
|
|
2341
|
+
const erc20AllowanceAbi2 = parseAbi([
|
|
1570
2342
|
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1571
2343
|
"function decimals() view returns (uint8)"
|
|
1572
2344
|
]);
|
|
@@ -1575,13 +2347,13 @@ async function buildEvmMultisignBodyCurveDaoBatch(args) {
|
|
|
1575
2347
|
if (!isNativeIn) {
|
|
1576
2348
|
const decimalsN = await publicClient.readContract({
|
|
1577
2349
|
address: tokenIn,
|
|
1578
|
-
abi:
|
|
2350
|
+
abi: erc20AllowanceAbi2,
|
|
1579
2351
|
functionName: "decimals"
|
|
1580
2352
|
});
|
|
1581
2353
|
const amountWei = parseUnits(args.amountHuman, Number(decimalsN));
|
|
1582
2354
|
const currentAllowance = await publicClient.readContract({
|
|
1583
2355
|
address: tokenIn,
|
|
1584
|
-
abi:
|
|
2356
|
+
abi: erc20AllowanceAbi2,
|
|
1585
2357
|
functionName: "allowance",
|
|
1586
2358
|
args: [executor, routerAddr]
|
|
1587
2359
|
});
|