diff --git a/projects/camelot-v3/README.md b/projects/camelot-v3/README.md new file mode 100644 index 00000000..7b672b72 --- /dev/null +++ b/projects/camelot-v3/README.md @@ -0,0 +1,58 @@ +# camelot-v3 + +Camelot V3 DEX is a decentralized exchange built on the Arbitrum network, enabling users to swap tokens and manage liquidity. +It offers a flexible Automated Market Maker (AMM) model, and supports customizable liquidity provisioning. + +## Supported Networks + +- ARBITRUM + +## Common Tasks + +1. Basic Operations + - "Swap 100 USDT to ETH in @camelot-v3 on Arbitrum network" + - "Swap 100 USDT to ETH in @camelot-v3 while receiving at least 0.1 ETH on Arbitrum network" + - "Swap 100 USDT to ETH in @camelot-v3 and sent them to 0x33128fA08f5E0545f4714434b53bDb5E98F62474 on Arbitrum network" + + - "Swap USDT to exactly 0.1 ETH in @camelot-v3 on Arbitrum network" + - "Swap USDT to exactly 0.1 ETH in @camelot-v3 while paying at most 100 USDT on Arbitrum network" + - "Swap USDT to exactly 0.1 ETH in @camelot-v3 and sent them to 0x33128fA08f5E0545f4714434b53bDb5E98F62474 on Arbitrum network" + + - "Add 100 USDT and 0.1 ETH liquidity in @camelot-v3 on Arbitrum network" + - "Add 100 USDT and 0.1 ETH liquidity in @camelot-v3 while providing at least 90 USDT and 0.09 ETH on Arbitrum network" + - "Add 100 USDT and 0.1 ETH liquidity in @camelot-v3 and sent the position NFT to 0x0419959C9ffF74FEaC47e51D5869fabcA61FFF15 on Arbitrum network" + - "Add 100 USDT and 0.1 ETH liquidity between price range 3150.12 USDT/ETH and 3793.65 USDT/ETH in @camelot-v3 on Arbitrum network" + - "Add 100 USDT and 0.1 ETH liquidity between price range -20% and +20% from current price in @camelot-v3 on Arbitrum network" + - "Place a limit order to swap 0.1 ETH to USDT at price 3562.93 USDT/ETH in @camelot-v3 on Arbitrum network" + - "Place a limit order to DCA swap 0.1 ETH to USDT between price range 3150.12 USDT/ETH and 3793.65 USDT/ETH in @camelot-v3 on Arbitrum network" + + - "Increase liquidity in ETH/USDT pool by 0.1 ETH and 100 USDT in @camelot-v3 on Arbitrum network" + - "Increase liquidity in ETH/USDT pool by 0.1 ETH and 100 USDT in @camelot-v3 while providing at least 0.09 ETH and 90 USDT on Arbitrum network" + - "Increase liquidity in ETH/USDT pool where tokenId is 123456 by 0.1ETH and 100 USDT in @camelot-v3 on Arbitrum network" + + - "Decrease liquidity in ETH/USDT pool by 10% in @camelot-v3 on Arbitrum network" + - "Decrease liquidity in ETH/USDT pool by 10% in @camelot-v3 while receiving at least 0.1 ETH and 100 USDT on Arbitrum network" + - "Decrease liquidity in ETH/USDT pool where tokenId is 123456 by 10% in @camelot-v3 on Arbitrum network" + + - "Collect fees from ETH/USDT pool in @camelot-v3 on Arbitrum network" + - "Collect fees from ETH/USDT pool and sent them to 0x33128fA08f5E0545f4714434b53bDb5E98F62474 on Arbitrum network" + - "Collect 50% of fees from ETH/USDT pool in @camelot-v3 on Arbitrum network" + - "Collect 0.05 ETH and 25 USDT fees from ETH/USDT pool in @camelot-v3 on Arbitrum network" + - "Collect fees from ETH/USDT pool in @camelot-v3 where tokenId is 123456 on Arbitrum network" + +2. Information Queries + - "Get my LP position in @camelot-v3 on Arbitrum network" + - "Quote 100 USDT to ETH in @camelot-v3 on Arbitrum network" + - "Quote USDT to exactly 0.1 ETH in @camelot-v3 on Arbitrum network" + +## Available Functions + +- **Swap**: Swap tokens on the DEX. +- **Add Liquidity**: Add liquidity to the DEX. +- **Manage Liquidity**: Increase / Decrease / Remove liquidity on the DEX. + +## Installation + +```bash +yarn add @heyanon/camelot-v3 +``` diff --git a/projects/camelot-v3/abis/algebraFactoryAbi.ts b/projects/camelot-v3/abis/algebraFactoryAbi.ts new file mode 100644 index 00000000..c97e1d0b --- /dev/null +++ b/projects/camelot-v3/abis/algebraFactoryAbi.ts @@ -0,0 +1,177 @@ +export const algebraFactoryAbi = [ + { + inputs: [ + { internalType: 'address', name: '_poolDeployer', type: 'address' }, + { internalType: 'address', name: '_vaultAddress', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'uint8', name: 'newDefaultCommunityFee', type: 'uint8' }], + name: 'DefaultCommunityFee', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: true, internalType: 'address', name: 'newFarmingAddress', type: 'address' }], + name: 'FarmingAddress', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint16', name: 'alpha1', type: 'uint16' }, + { indexed: false, internalType: 'uint16', name: 'alpha2', type: 'uint16' }, + { indexed: false, internalType: 'uint32', name: 'beta1', type: 'uint32' }, + { indexed: false, internalType: 'uint32', name: 'beta2', type: 'uint32' }, + { indexed: false, internalType: 'uint16', name: 'gamma1', type: 'uint16' }, + { indexed: false, internalType: 'uint16', name: 'gamma2', type: 'uint16' }, + { indexed: false, internalType: 'uint32', name: 'volumeBeta', type: 'uint32' }, + { indexed: false, internalType: 'uint16', name: 'volumeGamma', type: 'uint16' }, + { indexed: false, internalType: 'uint16', name: 'baseFee', type: 'uint16' }, + ], + name: 'FeeConfiguration', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'Owner', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'token0', type: 'address' }, + { indexed: true, internalType: 'address', name: 'token1', type: 'address' }, + { indexed: false, internalType: 'address', name: 'pool', type: 'address' }, + ], + name: 'Pool', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: true, internalType: 'address', name: 'newVaultAddress', type: 'address' }], + name: 'VaultAddress', + type: 'event', + }, + { + inputs: [], + name: 'baseFeeConfiguration', + outputs: [ + { internalType: 'uint16', name: 'alpha1', type: 'uint16' }, + { internalType: 'uint16', name: 'alpha2', type: 'uint16' }, + { internalType: 'uint32', name: 'beta1', type: 'uint32' }, + { internalType: 'uint32', name: 'beta2', type: 'uint32' }, + { internalType: 'uint16', name: 'gamma1', type: 'uint16' }, + { internalType: 'uint16', name: 'gamma2', type: 'uint16' }, + { internalType: 'uint32', name: 'volumeBeta', type: 'uint32' }, + { internalType: 'uint16', name: 'volumeGamma', type: 'uint16' }, + { internalType: 'uint16', name: 'baseFee', type: 'uint16' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'tokenA', type: 'address' }, + { internalType: 'address', name: 'tokenB', type: 'address' }, + ], + name: 'createPool', + outputs: [{ internalType: 'address', name: 'pool', type: 'address' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'defaultCommunityFee', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'farmingAddress', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'address', name: '', type: 'address' }, + ], + name: 'poolByPair', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'poolDeployer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint16', name: 'alpha1', type: 'uint16' }, + { internalType: 'uint16', name: 'alpha2', type: 'uint16' }, + { internalType: 'uint32', name: 'beta1', type: 'uint32' }, + { internalType: 'uint32', name: 'beta2', type: 'uint32' }, + { internalType: 'uint16', name: 'gamma1', type: 'uint16' }, + { internalType: 'uint16', name: 'gamma2', type: 'uint16' }, + { internalType: 'uint32', name: 'volumeBeta', type: 'uint32' }, + { internalType: 'uint16', name: 'volumeGamma', type: 'uint16' }, + { internalType: 'uint16', name: 'baseFee', type: 'uint16' }, + ], + name: 'setBaseFeeConfiguration', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint8', name: 'newDefaultCommunityFee', type: 'uint8' }], + name: 'setDefaultCommunityFee', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_farmingAddress', type: 'address' }], + name: 'setFarmingAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_owner', type: 'address' }], + name: 'setOwner', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '_vaultAddress', type: 'address' }], + name: 'setVaultAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'vaultAddress', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/projects/camelot-v3/abis/algebraPoolAbi.ts b/projects/camelot-v3/abis/algebraPoolAbi.ts new file mode 100644 index 00000000..84834fc4 --- /dev/null +++ b/projects/camelot-v3/abis/algebraPoolAbi.ts @@ -0,0 +1,393 @@ +export const algebraPoolAbi = [ + { + inputs: [], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, + { indexed: true, internalType: 'int24', name: 'bottomTick', type: 'int24' }, + { indexed: true, internalType: 'int24', name: 'topTick', type: 'int24' }, + { indexed: false, internalType: 'uint128', name: 'liquidityAmount', type: 'uint128' }, + { indexed: false, internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + name: 'Burn', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, + { indexed: false, internalType: 'address', name: 'recipient', type: 'address' }, + { indexed: true, internalType: 'int24', name: 'bottomTick', type: 'int24' }, + { indexed: true, internalType: 'int24', name: 'topTick', type: 'int24' }, + { indexed: false, internalType: 'uint128', name: 'amount0', type: 'uint128' }, + { indexed: false, internalType: 'uint128', name: 'amount1', type: 'uint128' }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint8', name: 'communityFee0New', type: 'uint8' }, + { indexed: false, internalType: 'uint8', name: 'communityFee1New', type: 'uint8' }, + ], + name: 'CommunityFee', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint16', name: 'feeZto', type: 'uint16' }, + { indexed: false, internalType: 'uint16', name: 'feeOtz', type: 'uint16' }, + ], + name: 'Fee', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'sender', type: 'address' }, + { indexed: true, internalType: 'address', name: 'recipient', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'amount1', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'paid0', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'paid1', type: 'uint256' }, + ], + name: 'Flash', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: true, internalType: 'address', name: 'virtualPoolAddress', type: 'address' }], + name: 'Incentive', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'uint160', name: 'price', type: 'uint160' }, + { indexed: false, internalType: 'int24', name: 'tick', type: 'int24' }, + ], + name: 'Initialize', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'uint32', name: 'liquidityCooldown', type: 'uint32' }], + name: 'LiquidityCooldown', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'int24', name: 'newTickSpacing', type: 'int24' }], + name: 'TickSpacing', + type: 'event', + }, + { + inputs: [], + name: 'activeIncentive', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'int24', name: 'bottomTick', type: 'int24' }, + { internalType: 'int24', name: 'topTick', type: 'int24' }, + { internalType: 'uint128', name: 'amount', type: 'uint128' }, + ], + name: 'burn', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'int24', name: 'bottomTick', type: 'int24' }, + { internalType: 'int24', name: 'topTick', type: 'int24' }, + { internalType: 'uint128', name: 'amount0Requested', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1Requested', type: 'uint128' }, + ], + name: 'collect', + outputs: [ + { internalType: 'uint128', name: 'amount0', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1', type: 'uint128' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'dataStorageOperator', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'flash', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'int24', name: 'bottomTick', type: 'int24' }, + { internalType: 'int24', name: 'topTick', type: 'int24' }, + ], + name: 'getInnerCumulatives', + outputs: [ + { internalType: 'int56', name: 'innerTickCumulative', type: 'int56' }, + { internalType: 'uint160', name: 'innerSecondsSpentPerLiquidity', type: 'uint160' }, + { internalType: 'uint32', name: 'innerSecondsSpent', type: 'uint32' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint32[]', name: 'secondsAgos', type: 'uint32[]' }], + name: 'getTimepoints', + outputs: [ + { internalType: 'int56[]', name: 'tickCumulatives', type: 'int56[]' }, + { internalType: 'uint160[]', name: 'secondsPerLiquidityCumulatives', type: 'uint160[]' }, + { internalType: 'uint112[]', name: 'volatilityCumulatives', type: 'uint112[]' }, + { internalType: 'uint256[]', name: 'volumePerAvgLiquiditys', type: 'uint256[]' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'globalState', + outputs: [ + { internalType: 'uint160', name: 'price', type: 'uint160' }, + { internalType: 'int24', name: 'tick', type: 'int24' }, + { internalType: 'uint16', name: 'feeZto', type: 'uint16' }, + { internalType: 'uint16', name: 'feeOtz', type: 'uint16' }, + { internalType: 'uint16', name: 'timepointIndex', type: 'uint16' }, + { internalType: 'uint8', name: 'communityFeeToken0', type: 'uint8' }, + { internalType: 'uint8', name: 'communityFeeToken1', type: 'uint8' }, + { internalType: 'bool', name: 'unlocked', type: 'bool' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint160', name: 'initialPrice', type: 'uint160' }], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'liquidity', + outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'liquidityCooldown', + outputs: [{ internalType: 'uint32', name: '', type: 'uint32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'maxLiquidityPerTick', + outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'int24', name: 'bottomTick', type: 'int24' }, + { internalType: 'int24', name: 'topTick', type: 'int24' }, + { internalType: 'uint128', name: 'liquidityDesired', type: 'uint128' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'mint', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + { internalType: 'uint128', name: 'liquidityActual', type: 'uint128' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + name: 'positions', + outputs: [ + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint32', name: 'lastLiquidityAddTimestamp', type: 'uint32' }, + { internalType: 'uint256', name: 'innerFeeGrowth0Token', type: 'uint256' }, + { internalType: 'uint256', name: 'innerFeeGrowth1Token', type: 'uint256' }, + { internalType: 'uint128', name: 'fees0', type: 'uint128' }, + { internalType: 'uint128', name: 'fees1', type: 'uint128' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint8', name: 'communityFee0', type: 'uint8' }, + { internalType: 'uint8', name: 'communityFee1', type: 'uint8' }, + ], + name: 'setCommunityFee', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'virtualPoolAddress', type: 'address' }], + name: 'setIncentive', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint32', name: 'newLiquidityCooldown', type: 'uint32' }], + name: 'setLiquidityCooldown', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'int24', name: 'newTickSpacing', type: 'int24' }], + name: 'setTickSpacing', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'bool', name: 'zeroToOne', type: 'bool' }, + { internalType: 'int256', name: 'amountRequired', type: 'int256' }, + { internalType: 'uint160', name: 'limitSqrtPrice', type: 'uint160' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'swap', + outputs: [ + { internalType: 'int256', name: 'amount0', type: 'int256' }, + { internalType: 'int256', name: 'amount1', type: 'int256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'bool', name: 'zeroToOne', type: 'bool' }, + { internalType: 'int256', name: 'amountRequired', type: 'int256' }, + { internalType: 'uint160', name: 'limitSqrtPrice', type: 'uint160' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'swapSupportingFeeOnInputTokens', + outputs: [ + { internalType: 'int256', name: 'amount0', type: 'int256' }, + { internalType: 'int256', name: 'amount1', type: 'int256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'tickSpacing', + outputs: [{ internalType: 'int24', name: '', type: 'int24' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'int16', name: '', type: 'int16' }], + name: 'tickTable', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'int24', name: '', type: 'int24' }], + name: 'ticks', + outputs: [ + { internalType: 'uint128', name: 'liquidityTotal', type: 'uint128' }, + { internalType: 'int128', name: 'liquidityDelta', type: 'int128' }, + { internalType: 'uint256', name: 'outerFeeGrowth0Token', type: 'uint256' }, + { internalType: 'uint256', name: 'outerFeeGrowth1Token', type: 'uint256' }, + { internalType: 'int56', name: 'outerTickCumulative', type: 'int56' }, + { internalType: 'uint160', name: 'outerSecondsPerLiquidity', type: 'uint160' }, + { internalType: 'uint32', name: 'outerSecondsSpent', type: 'uint32' }, + { internalType: 'bool', name: 'initialized', type: 'bool' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }], + name: 'timepoints', + outputs: [ + { internalType: 'bool', name: 'initialized', type: 'bool' }, + { internalType: 'uint32', name: 'blockTimestamp', type: 'uint32' }, + { internalType: 'int56', name: 'tickCumulative', type: 'int56' }, + { internalType: 'uint160', name: 'secondsPerLiquidityCumulative', type: 'uint160' }, + { internalType: 'uint88', name: 'volatilityCumulative', type: 'uint88' }, + { internalType: 'int24', name: 'averageTick', type: 'int24' }, + { internalType: 'uint144', name: 'volumePerLiquidityCumulative', type: 'uint144' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'token0', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'token1', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalFeeGrowth0Token', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalFeeGrowth1Token', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/projects/camelot-v3/abis/index.ts b/projects/camelot-v3/abis/index.ts new file mode 100644 index 00000000..20704f36 --- /dev/null +++ b/projects/camelot-v3/abis/index.ts @@ -0,0 +1,6 @@ +export * from './algebraFactoryAbi'; +export * from './algebraPoolAbi'; +export * from './nonFungiblePositionManagerAbi'; +export * from './quoterAbi'; +export * from './swapRouterAbi'; + diff --git a/projects/camelot-v3/abis/nonFungiblePositionManagerAbi.ts b/projects/camelot-v3/abis/nonFungiblePositionManagerAbi.ts new file mode 100644 index 00000000..f2b09579 --- /dev/null +++ b/projects/camelot-v3/abis/nonFungiblePositionManagerAbi.ts @@ -0,0 +1,513 @@ +export const nonFungiblePositionManagerAbi = [ + { + inputs: [ + { internalType: 'address', name: '_factory', type: 'address' }, + { internalType: 'address', name: '_WNativeToken', type: 'address' }, + { internalType: 'address', name: '_tokenDescriptor_', type: 'address' }, + { internalType: 'address', name: '_poolDeployer', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'approved', type: 'address' }, + { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'operator', type: 'address' }, + { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { indexed: false, internalType: 'address', name: 'recipient', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { indexed: false, internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { indexed: false, internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + name: 'DecreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { indexed: false, internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { indexed: false, internalType: 'uint128', name: 'actualLiquidity', type: 'uint128' }, + { indexed: false, internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { indexed: false, internalType: 'uint256', name: 'amount1', type: 'uint256' }, + { indexed: false, internalType: 'address', name: 'pool', type: 'address' }, + ], + name: 'IncreaseLiquidity', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PERMIT_TYPEHASH', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'WNativeToken', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amount0Owed', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Owed', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'algebraMintCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'baseURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'burn', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint128', name: 'amount0Max', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1Max', type: 'uint128' }, + ], + internalType: 'struct INonfungiblePositionManager.CollectParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'collect', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, + ], + name: 'createAndInitializePoolIfNecessary', + outputs: [{ internalType: 'address', name: 'pool', type: 'address' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'decreaseLiquidity', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: 'struct INonfungiblePositionManager.IncreaseLiquidityParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'increaseLiquidity', + outputs: [ + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'address', name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint256', name: 'amount0Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Desired', type: 'uint256' }, + { internalType: 'uint256', name: 'amount0Min', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1Min', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + ], + internalType: 'struct INonfungiblePositionManager.MintParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'mint', + outputs: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }], + name: 'multicall', + outputs: [{ internalType: 'bytes[]', name: 'results', type: 'bytes[]' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'poolDeployer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'positions', + outputs: [ + { internalType: 'uint96', name: 'nonce', type: 'uint96' }, + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'address', name: 'token0', type: 'address' }, + { internalType: 'address', name: 'token1', type: 'address' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { internalType: 'uint256', name: 'feeGrowthInside0LastX128', type: 'uint256' }, + { internalType: 'uint256', name: 'feeGrowthInside1LastX128', type: 'uint256' }, + { internalType: 'uint128', name: 'tokensOwed0', type: 'uint128' }, + { internalType: 'uint128', name: 'tokensOwed1', type: 'uint128' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'refundNativeToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowed', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowedIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'bool', name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'sweepToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }], + name: 'tokenByIndex', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'uint256', name: 'index', type: 'uint256' }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'unwrapWNativeToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const; diff --git a/projects/camelot-v3/abis/quoterAbi.ts b/projects/camelot-v3/abis/quoterAbi.ts new file mode 100644 index 00000000..817cb5c7 --- /dev/null +++ b/projects/camelot-v3/abis/quoterAbi.ts @@ -0,0 +1,99 @@ +export const quoterAbi = [ + { + inputs: [ + { internalType: 'address', name: '_factory', type: 'address' }, + { internalType: 'address', name: '_WNativeToken', type: 'address' }, + { internalType: 'address', name: '_poolDeployer', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'WNativeToken', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'int256', name: 'amount0Delta', type: 'int256' }, + { internalType: 'int256', name: 'amount1Delta', type: 'int256' }, + { internalType: 'bytes', name: 'path', type: 'bytes' }, + ], + name: 'algebraSwapCallback', + outputs: [], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'poolDeployer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + ], + name: 'quoteExactInput', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint16[]', name: 'fees', type: 'uint16[]' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint160', name: 'limitSqrtPrice', type: 'uint160' }, + ], + name: 'quoteExactInputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint16', name: 'fee', type: 'uint16' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + ], + name: 'quoteExactOutput', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint16[]', name: 'fees', type: 'uint16[]' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint160', name: 'limitSqrtPrice', type: 'uint160' }, + ], + name: 'quoteExactOutputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint16', name: 'fee', type: 'uint16' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/projects/camelot-v3/abis/swapRouterAbi.ts b/projects/camelot-v3/abis/swapRouterAbi.ts new file mode 100644 index 00000000..1168e97c --- /dev/null +++ b/projects/camelot-v3/abis/swapRouterAbi.ts @@ -0,0 +1,270 @@ +export const swapRouterAbi = [ + { + inputs: [ + { internalType: 'address', name: '_factory', type: 'address' }, + { internalType: 'address', name: '_WNativeToken', type: 'address' }, + { internalType: 'address', name: '_poolDeployer', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'WNativeToken', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'int256', name: 'amount0Delta', type: 'int256' }, + { internalType: 'int256', name: 'amount1Delta', type: 'int256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' }, + ], + name: 'algebraSwapCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'amountOutMinimum', type: 'uint256' }, + ], + internalType: 'struct ISwapRouter.ExactInputParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactInput', + outputs: [{ internalType: 'uint256', name: 'amountOut', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'amountOutMinimum', type: 'uint256' }, + { internalType: 'uint160', name: 'limitSqrtPrice', type: 'uint160' }, + ], + internalType: 'struct ISwapRouter.ExactInputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactInputSingle', + outputs: [{ internalType: 'uint256', name: 'amountOut', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'amountOutMinimum', type: 'uint256' }, + { internalType: 'uint160', name: 'limitSqrtPrice', type: 'uint160' }, + ], + internalType: 'struct ISwapRouter.ExactInputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactInputSingleSupportingFeeOnTransferTokens', + outputs: [{ internalType: 'uint256', name: 'amountOut', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'amountInMaximum', type: 'uint256' }, + ], + internalType: 'struct ISwapRouter.ExactOutputParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactOutput', + outputs: [{ internalType: 'uint256', name: 'amountIn', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint256', name: 'amountInMaximum', type: 'uint256' }, + { internalType: 'uint160', name: 'limitSqrtPrice', type: 'uint160' }, + ], + internalType: 'struct ISwapRouter.ExactOutputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactOutputSingle', + outputs: [{ internalType: 'uint256', name: 'amountIn', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' }], + name: 'multicall', + outputs: [{ internalType: 'bytes[]', name: 'results', type: 'bytes[]' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'poolDeployer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'refundNativeToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowed', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'uint256', name: 'expiry', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitAllowedIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint8', name: 'v', type: 'uint8' }, + { internalType: 'bytes32', name: 'r', type: 'bytes32' }, + { internalType: 'bytes32', name: 's', type: 'bytes32' }, + ], + name: 'selfPermitIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'sweepToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'feeBips', type: 'uint256' }, + { internalType: 'address', name: 'feeRecipient', type: 'address' }, + ], + name: 'sweepTokenWithFee', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + ], + name: 'unwrapWNativeToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'amountMinimum', type: 'uint256' }, + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'feeBips', type: 'uint256' }, + { internalType: 'address', name: 'feeRecipient', type: 'address' }, + ], + name: 'unwrapWNativeTokenWithFee', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const; diff --git a/projects/camelot-v3/constants.ts b/projects/camelot-v3/constants.ts new file mode 100644 index 00000000..6a37e1ae --- /dev/null +++ b/projects/camelot-v3/constants.ts @@ -0,0 +1,26 @@ +import { ChainId } from '@heyanon/sdk'; +import { Address } from 'viem'; + +export const SUPPORTED_CHAINS = [ChainId.ARBITRUM]; + +export const GRAPH_API_KEY = '939d894fb6b87b1222a450d9aad926f8'; +export const GRAPH_URLS: Record = { + [ChainId.ARBITRUM]: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/3utanEBA9nqMjPnuQP1vMCCys6enSM3EawBpKTVwnUw2`, +}; + +export const PERCENTAGE_BASE = 10000n; +export const DEFAULT_SLIPPAGE = 2n; +export const MIN_TICK = -887272; +export const MAX_TICK = 887272; +export const MAX_UINT128 = 2n ** 128n - 1n; + +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as Address; +export const ADDRESSES: Record = + { + [ChainId.ARBITRUM]: { + ALGEBRA_FACTORY_ADDRESS: '0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B' as Address, + QUOTER_ADDRESS: '0x0Fc73040b26E9bC8514fA028D998E73A254Fa76E' as Address, + SWAP_ROUTER_ADDRESS: '0x1F721E2E82F6676FCE4eA07A5958cF098D339e18' as Address, + NONFUNGIBLE_POSITION_MANAGER_ADDRESS: '0x00c7f3082833e796A5b3e4Bd59f6642FF44DCD15' as Address, + }, + }; diff --git a/projects/camelot-v3/functions/collect.ts b/projects/camelot-v3/functions/collect.ts new file mode 100644 index 00000000..1cc5264d --- /dev/null +++ b/projects/camelot-v3/functions/collect.ts @@ -0,0 +1,170 @@ +import { FunctionOptions, FunctionReturn, getChainFromName, toResult, TransactionParams } from '@heyanon/sdk'; +import { Address, encodeFunctionData, PublicClient } from 'viem'; +import { ADDRESSES, MAX_UINT128, PERCENTAGE_BASE, SUPPORTED_CHAINS, ZERO_ADDRESS } from '../constants'; +import { amountToWei } from '../utils'; +import { nonFungiblePositionManagerAbi } from '../abis'; +import { queryLPPositions } from './getLPPositions'; + +interface Props { + chainName: string; + account: Address; + tokenA: Address; + tokenB: Address; + tokenId?: number; + collectPercentage?: number; + amountAMax?: string; + amountBMax?: string; + recipient?: Address; +} + +export async function collect( + { chainName, account, tokenA, tokenB, tokenId, collectPercentage, amountAMax, amountBMax, recipient }: Props, + { sendTransactions, notify, getProvider }: FunctionOptions, +): Promise { + try { + // Check wallet connection + if (!account) return toResult('Wallet not connected', true); + + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + // Validate tokenId + if(tokenId && (!Number.isInteger(tokenId) || tokenId < 0)) { + return toResult(`Invalid token ID: ${tokenId}, please provide a whole non-negative number`, true); + } + + // Validate collectPercentage + if(collectPercentage && (!Number.isInteger(collectPercentage) || collectPercentage < 0)) { + return toResult(`Invalid collect percentage: ${collectPercentage}, please provide a whole non-negative number`, true); + } + + await notify(`Collecting fees on Camelot V3...`); + + // Determine position ID + let positionId: bigint; + if (!tokenId) { + const positions = await queryLPPositions(chainId, account, tokenA, tokenB); + + // Ensure we are collecting fees from a specific position + if (positions.length > 1) { + return toResult(`There are multiple LP positions, please provide a specific position ID to collect fees from`, true); + } + + positionId = BigInt(positions[0].id); + } else { + positionId = BigInt(tokenId); + } + + const provider = getProvider(chainId); + + // Get LP position data + const positionData = await getPositionData(chainId, provider, positionId); + if (!positionData) return toResult(`Position with ID ${positionId} not found`, true); + + const transactions: TransactionParams[] = []; + const collectTxData = await prepareCollectTxData( + chainId, + provider, + positionId, + positionData[2], + positionData[3], + recipient ?? account, + collectPercentage, + amountAMax, + amountBMax, + ); + transactions.push(collectTxData); + + await notify('Waiting for collect fees transaction confirmation...'); + + const result = await sendTransactions({ chainId, account, transactions }); + const collectFees = result.data[result.data.length - 1]; + + return toResult(result.isMultisig ? collectFees.message : `Successfully collected fees on Camelot V3. ${collectFees.message}`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return toResult(errorMessage, true); + } +} + +async function getPositionData(chainId: number, provider: PublicClient, positionId: bigint): Promise { + try { + return await provider.readContract({ + address: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + abi: nonFungiblePositionManagerAbi, + functionName: 'positions', + args: [positionId], + }); + } catch (error) { + return undefined; + } +} + +export async function prepareCollectTxData( + chainId: number, + provider: PublicClient, + positionId: bigint, + token0: Address, + token1: Address, + recipient: Address, + collectPercentage?: number, + amountAMax?: string, + amountBMax?: string, +): Promise { + // Convert amounts to wei + let [amountAMaxWei, amountBMaxWei] = await Promise.all([amountToWei(provider, token0, amountAMax), amountToWei(provider, token1, amountBMax)]); + + // Recalculate amounts if fee percentage is provided + if (collectPercentage) { + let collectPercentageBigInt = BigInt(collectPercentage); + + [amountAMaxWei, amountBMaxWei] = await collectPercentageToMaxAmountOut(chainId, provider, positionId, collectPercentageBigInt); + } + + // Collect 100% of fees if no percentage and amounts are provided + if (!collectPercentage && !amountAMax && !amountBMax) { + [amountAMaxWei, amountBMaxWei] = await collectPercentageToMaxAmountOut(chainId, provider, positionId, PERCENTAGE_BASE); + } + + // Validate amounts + if (amountAMaxWei === 0n && amountBMaxWei === 0n) throw Error(`Nothing to collect, since both amounts are 0`); + + // Prepare collect transaction + return { + target: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + data: encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'collect', + args: [ + { + tokenId: positionId, + amount0Max: amountAMaxWei, + amount1Max: amountBMaxWei, + recipient: recipient, + }, + ], + }), + }; +} + +async function collectPercentageToMaxAmountOut(chainId: number, provider: PublicClient, positionId: bigint, collectPercentage: bigint): Promise { + const collectData = await provider.simulateContract({ + address: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + abi: nonFungiblePositionManagerAbi, + functionName: 'collect', + args: [ + { + tokenId: positionId, + amount0Max: MAX_UINT128, + amount1Max: MAX_UINT128, + recipient: ZERO_ADDRESS, + }, + ], + }); + + const [amount0Max, amount1Max] = collectData.result; + + return [(amount0Max * collectPercentage) / PERCENTAGE_BASE, (amount1Max * collectPercentage) / PERCENTAGE_BASE]; +} diff --git a/projects/camelot-v3/functions/decreaseLiquidity.ts b/projects/camelot-v3/functions/decreaseLiquidity.ts new file mode 100644 index 00000000..578546f0 --- /dev/null +++ b/projects/camelot-v3/functions/decreaseLiquidity.ts @@ -0,0 +1,250 @@ +import { FunctionOptions, FunctionReturn, getChainFromName, toResult, TransactionParams } from '@heyanon/sdk'; +import { Address, encodeFunctionData, Hex, PublicClient } from 'viem'; +import { ADDRESSES, PERCENTAGE_BASE, SUPPORTED_CHAINS, ZERO_ADDRESS } from '../constants'; +import { algebraFactoryAbi, algebraPoolAbi, nonFungiblePositionManagerAbi } from '../abis'; +import { amountToWei } from '../utils'; +import { queryLPPositions } from './getLPPositions'; +import { prepareCollectTxData } from './collect'; + +interface Props { + chainName: string; + account: Address; + tokenA: Address; + tokenB: Address; + decreasePercentage: number; + tokenId?: number; + amountAMin?: string; + amountBMin?: string; +} + +export async function decreaseLiquidity( + { chainName, account, tokenA, tokenB, decreasePercentage, tokenId, amountAMin, amountBMin }: Props, + { sendTransactions, notify, getProvider }: FunctionOptions, +): Promise { + // Check wallet connection + if (!account) return toResult('Wallet not connected', true); + + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + // Validate tokenId + if(tokenId && (!Number.isInteger(tokenId) || tokenId < 0)) { + return toResult(`Invalid token ID: ${tokenId}, please provide a whole non-negative number`, true); + } + + // Validate decreasePercentage + if(decreasePercentage && (!Number.isInteger(decreasePercentage) || decreasePercentage < 0)) { + return toResult(`Invalid decrease percentage: ${decreasePercentage}, please provide a whole non-negative number`, true); + } + + await notify(`Preparing to decrease liquidity on Camelot V3...`); + + // Determine position ID + let positionId: bigint; + if (!tokenId) { + const positions = await queryLPPositions(chainId, account, tokenA, tokenB); + + // Ensure we are collecting fees from a specific position + if (positions.length > 1) { + return toResult(`There are multiple LP positions, please provide a specific position ID to collect fees from`, true); + } + + positionId = BigInt(positions[0].id); + } else { + positionId = BigInt(tokenId); + } + + const provider = getProvider(chainId); + + // Get LP position data + const positionData = await getPositionData(chainId, provider, positionId); + if (!positionData) return toResult(`Position with ID ${positionId} not found`, true); + + let decreasePercentageBigInt = BigInt(decreasePercentage); + const liquidityToRemove = (BigInt(positionData[6]) * decreasePercentageBigInt) / PERCENTAGE_BASE; + + // Remap tokenAB to token01 + let [[token0, token0Symbol, amount0MinWei], [token1, token1Symbol, amount1MinWei]] = await tokenABToToken01( + provider, + positionData[2], + positionData[3], + tokenA, + amountAMin, + tokenB, + amountBMin, + ); + + // Simulate decrease liquidity amounts if amount0Min and/or amount1Min are not provided + if (amount0MinWei == 0n || amount1MinWei == 0n) { + const [simulatedAmount0, simulatedAmount1] = await simulateDecreaseLiquidityAmounts(chainId, provider, positionId, liquidityToRemove); + + // Set 0.2% slippage tolerance + if (amount0MinWei == 0n) { + amount0MinWei = (simulatedAmount0 * 9998n) / 10000n; + } + + if (amount1MinWei == 0n) { + amount1MinWei = (simulatedAmount1 * 9998n) / 10000n; + } + } + + // Validate amounts + const tickLower = positionData[4]; + const tickUpper = positionData[5]; + + const pool = await getPool(chainId, provider, token0, token1); + if (!pool || pool === ZERO_ADDRESS) return toResult(`Pool not found for ${token0} and ${token1}`, true); + + const currentTick = await getCurrentTick(provider, pool!); + + if (currentTick >= tickLower && currentTick <= tickUpper) { + // Two-sided liquidity position + if (amount0MinWei === 0n) return toResult(`Amount ${token0Symbol} MIN must be greater than 0`, true); + + if (amount1MinWei === 0n) return toResult(`Amount ${token1Symbol} MIN must be greater than 0`, true); + } else if (currentTick < tickLower) { + // Single-sided liquidity position - token0 + if (amount0MinWei === 0n) return toResult(`Amount ${token0Symbol} MIN must be greater than 0`, true); + } else if (currentTick > tickUpper) { + // Single-sided liquidity position - token1 + if (amount1MinWei === 0n) return toResult(`Amount ${token1Symbol} MIN must be greater than 0`, true); + } + + const transactions: TransactionParams[] = []; + + // Prepare decrease liquidity transaction + const multicallTransactionsTxData: Hex[] = []; + + const decreaseLiquidityTxData = encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'decreaseLiquidity', + args: [ + { + tokenId: positionId, + liquidity: liquidityToRemove, + amount0Min: amount0MinWei, + amount1Min: amount1MinWei, + deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + }, + ], + }); + multicallTransactionsTxData.push(decreaseLiquidityTxData); + + if (decreasePercentageBigInt == PERCENTAGE_BASE) { + const collectTxData = await prepareCollectTxData(chainId, provider, positionId, token0, token1, account, Number(PERCENTAGE_BASE)); + + const burnTxData = encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'burn', + args: [positionId], + }); + + multicallTransactionsTxData.push(collectTxData.data); + multicallTransactionsTxData.push(burnTxData); + } + + // Wrap into multicall + const multicallTx: TransactionParams = { + target: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + data: encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'multicall', + args: [multicallTransactionsTxData], + }), + }; + + transactions.push(multicallTx); + + await notify('Waiting for decrease liquidity transaction confirmation...'); + + const result = await sendTransactions({ chainId, account, transactions }); + const decreaseLiquidity = result.data[result.data.length - 1]; + + return toResult(result.isMultisig ? decreaseLiquidity.message : `Successfully decreased liquidity on Camelot V3. ${decreaseLiquidity.message}`); +} + +async function getPositionData(chainId: number, provider: PublicClient, positionId: bigint): Promise { + try { + return await provider.readContract({ + address: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + abi: nonFungiblePositionManagerAbi, + functionName: 'positions', + args: [positionId], + }); + } catch (error) { + return undefined; + } +} + +async function tokenABToToken01( + provider: PublicClient, + token0: Address, + token1: Address, + tokenA: Address, + amountAMin: string | undefined, + tokenB: Address, + amountBMin: string | undefined, +): Promise<[Address, string, bigint][]> { + // Convert amounts to wei + let [amountAMinWei, amountBMinWei] = await Promise.all([amountToWei(provider, tokenA, amountAMin), amountToWei(provider, tokenB, amountBMin)]); + + // Remap tokenAB to token01 + if (token0 === tokenA && token1 === tokenB) { + return [ + [tokenA, 'A', amountAMinWei], + [tokenB, 'B', amountBMinWei], + ]; + } else if (token0 === tokenB && token1 === tokenA) { + return [ + [tokenB, 'B', amountBMinWei], + [tokenA, 'A', amountAMinWei], + ]; + } + + throw new Error(`Invalid token pair: ${tokenA} and ${tokenB}`); +} + +async function getPool(chainId: number, provider: PublicClient, tokenA: Address, tokenB: Address): Promise
{ + try { + return await provider.readContract({ + address: ADDRESSES[chainId].ALGEBRA_FACTORY_ADDRESS, + abi: algebraFactoryAbi, + functionName: 'poolByPair', + args: [tokenA, tokenB], + }); + } catch (error) { + return undefined; + } +} + +async function getCurrentTick(provider: PublicClient, pool: Address) { + const poolState = await provider.readContract({ + address: pool, + abi: algebraPoolAbi, + functionName: 'globalState', + args: [], + }); + + return BigInt(poolState[1]); +} + +async function simulateDecreaseLiquidityAmounts(chainId: number, provider: PublicClient, positionId: bigint, liquidityToRemove: bigint) { + const decreaseLiquidity = await provider.simulateContract({ + address: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + abi: nonFungiblePositionManagerAbi, + functionName: 'decreaseLiquidity', + args: [ + { + tokenId: positionId, + liquidity: liquidityToRemove, + amount0Min: 0n, + amount1Min: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + }, + ], + }); + + return decreaseLiquidity.result; +} diff --git a/projects/camelot-v3/functions/exactInputSingle.ts b/projects/camelot-v3/functions/exactInputSingle.ts new file mode 100644 index 00000000..fa841b10 --- /dev/null +++ b/projects/camelot-v3/functions/exactInputSingle.ts @@ -0,0 +1,117 @@ +import { Address, encodeFunctionData } from 'viem'; +import { checkToApprove, FunctionOptions, FunctionReturn, getChainFromName, toResult, TransactionParams } from '@heyanon/sdk'; +import { ADDRESSES, DEFAULT_SLIPPAGE, PERCENTAGE_BASE, SUPPORTED_CHAINS } from '../constants'; +import { swapRouterAbi } from '../abis'; +import { amountToWei } from '../utils'; +import { callQuoteExactInputSingle } from './quoteExactInputSingle'; + +interface Props { + chainName: string; + account: Address; + tokenIn: Address; + tokenOut: Address; + amountIn: string; + amountOutMin?: string; + recipient?: Address; + slippage?: number; +} + +// Questions: +// How to check if user wants to swap native ETH? +// TODO: Support FeeOnTransferTokens? +export async function exactInputSingle( + { chainName, account, tokenIn, tokenOut, amountIn, amountOutMin, recipient, slippage }: Props, + { sendTransactions, notify, getProvider }: FunctionOptions, +): Promise { + try { + // Check wallet connection + if (!account) return toResult('Wallet not connected', true); + + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + // Validate slippage + if (slippage && (!Number.isInteger(slippage) || slippage < 0 || slippage > 300)) { + return toResult(`Invalid slippage tolerance: ${slippage}, please provide a whole non-negative number, max 3% got ${slippage / 100} %`, true); + } + + await notify(`Preparing to swap tokens on Camelot V3...`); + + const provider = getProvider(chainId); + + // Convert amounts to wei + let [amountInWei, amountOutMinWei] = await Promise.all([amountToWei(provider, tokenIn, amountIn), amountToWei(provider, tokenOut, amountOutMin)]); + + // Simulate swap if amountOutMin is not provided to protect from front-running + if (!amountOutMin) { + const quoteResult = await callQuoteExactInputSingle(chainId, provider, tokenIn, tokenOut, amountInWei); + amountOutMinWei = quoteResult.result[0]; + } + + // Set slippage tolerance + let slippageMultiplier + if(slippage) { + slippageMultiplier = PERCENTAGE_BASE - BigInt(slippage); + } else { + // Set default 0.2% slippage tolerance + slippageMultiplier = PERCENTAGE_BASE - DEFAULT_SLIPPAGE; + } + + amountOutMinWei = (amountOutMinWei * slippageMultiplier) / PERCENTAGE_BASE; + + // Validate amounts + if (amountInWei === 0n) return toResult('Amount IN must be greater than 0', true); + + if (amountOutMinWei === 0n) return toResult('Amount OUT MIN must be greater than 0', true); + + // Prepare transactions + const transactions: TransactionParams[] = []; + + // Approvals + await checkToApprove({ + args: { + account, + target: tokenIn, + spender: ADDRESSES[chainId].SWAP_ROUTER_ADDRESS, + amount: amountInWei, + }, + provider, + transactions, + }); + + // Swap transaction + const tx: TransactionParams = { + target: ADDRESSES[chainId].SWAP_ROUTER_ADDRESS, + data: encodeFunctionData({ + abi: swapRouterAbi, + functionName: 'exactInputSingle', + args: [ + { + tokenIn, + tokenOut, + recipient: recipient ?? account, + deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + amountIn: amountInWei, + amountOutMinimum: amountOutMinWei, + limitSqrtPrice: 0n, + }, + ], + }), + }; + transactions.push(tx); + + await notify('Waiting for swap transaction confirmation...'); + + // Execute transactions + const result = await sendTransactions({ chainId, account, transactions }); + const swapMessage = result.data[result.data.length - 1]; + + return toResult(result.isMultisig ? swapMessage.message : `Successfully swapped tokens on Camelot V3. ${swapMessage.message}`); + } catch (error) { + console.error(error); + const errorMessage = error instanceof Error ? error.message : String(error); + return toResult(errorMessage, true); + } +} diff --git a/projects/camelot-v3/functions/exactOutputSingle.ts b/projects/camelot-v3/functions/exactOutputSingle.ts new file mode 100644 index 00000000..3f2b79da --- /dev/null +++ b/projects/camelot-v3/functions/exactOutputSingle.ts @@ -0,0 +1,117 @@ +import { Address, encodeFunctionData } from 'viem'; +import { checkToApprove, FunctionOptions, FunctionReturn, getChainFromName, toResult, TransactionParams } from '@heyanon/sdk'; +import { ADDRESSES, DEFAULT_SLIPPAGE, PERCENTAGE_BASE, SUPPORTED_CHAINS } from '../constants'; +import { swapRouterAbi } from '../abis'; +import { callQuoteExactOutputSingle } from './quoteExactOutputSingle'; +import { amountToWei } from '../utils'; + +interface Props { + chainName: string; + account: Address; + tokenIn: Address; + tokenOut: Address; + amountOut: string; + amountInMax?: string; + recipient?: Address; + slippage?: number; +} + +// Questions: +// How to check if user wants to swap native ETH? +// TODO: Support FeeOnTransferTokens? +export async function exactOutputSingle( + { chainName, account, tokenIn, tokenOut, amountOut, amountInMax, recipient, slippage }: Props, + { sendTransactions, notify, getProvider }: FunctionOptions, +): Promise { + try { + // Check wallet connection + if (!account) return toResult('Wallet not connected', true); + + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + // Validate slippage + if (slippage && (!Number.isInteger(slippage) || slippage < 0 || slippage > 300)) { + return toResult(`Invalid slippage tolerance: ${slippage}, please provide a whole non-negative number, max 3% got ${slippage / 100} %`, true); + } + + await notify(`Preparing to swap tokens on Camelot V3...`); + + const provider = getProvider(chainId); + + // Convert amounts to wei + let [amountOutWei, amountInMaxWei] = await Promise.all([amountToWei(provider, tokenOut, amountOut), amountToWei(provider, tokenIn, amountInMax)]); + + // Simulate swap if amountInMax is not provided to protect from front-running + if (!amountInMax) { + const quoteResult = await callQuoteExactOutputSingle(chainId, provider, tokenIn, tokenOut, amountOutWei); + amountInMaxWei = quoteResult.result[0]; + } + + // Set slippage tolerance + let slippageMultiplier + if(slippage) { + slippageMultiplier = PERCENTAGE_BASE + BigInt(slippage); + } else { + // Set default 0.2% slippage tolerance + slippageMultiplier = PERCENTAGE_BASE + DEFAULT_SLIPPAGE; + } + + amountInMaxWei = (amountInMaxWei * slippageMultiplier) / PERCENTAGE_BASE; + + // Validate amounts + if (amountOutWei === 0n) return toResult('Amount OUT must be greater than 0', true); + + if (amountInMaxWei === 0n) return toResult('Amount IN MAX must be greater than 0', true); + + // Prepare transactions + const transactions: TransactionParams[] = []; + + // Approvals + await checkToApprove({ + args: { + account, + target: tokenIn, + spender: ADDRESSES[chainId].SWAP_ROUTER_ADDRESS, + amount: amountInMaxWei, + }, + provider, + transactions, + }); + + // Swap transaction + const tx: TransactionParams = { + target: ADDRESSES[chainId].SWAP_ROUTER_ADDRESS, + data: encodeFunctionData({ + abi: swapRouterAbi, + functionName: 'exactOutputSingle', + args: [ + { + tokenIn, + tokenOut, + fee: 0, + recipient: recipient ?? account, + deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + amountOut: amountOutWei, + amountInMaximum: amountInMaxWei, + limitSqrtPrice: 0n, + }, + ], + }), + }; + transactions.push(tx); + + await notify('Waiting for swap transaction confirmation...'); + + // Execute transactions + const result = await sendTransactions({ chainId, account, transactions }); + const swapMessage = result.data[result.data.length - 1]; + + return toResult(result.isMultisig ? swapMessage.message : `Successfully swapped tokens on Camelot V3. ${swapMessage.message}`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return toResult(errorMessage, true); + } +} diff --git a/projects/camelot-v3/functions/getLPPositions.ts b/projects/camelot-v3/functions/getLPPositions.ts new file mode 100644 index 00000000..06f14ed2 --- /dev/null +++ b/projects/camelot-v3/functions/getLPPositions.ts @@ -0,0 +1,132 @@ +import { FunctionOptions, FunctionReturn, getChainFromName, toResult } from '@heyanon/sdk'; +import { Address } from 'viem'; +import { GRAPH_URLS, SUPPORTED_CHAINS } from '../constants'; +import axios from 'axios'; + +interface Props { + chainName: string; + account: Address; + tokenA?: Address; + tokenB?: Address; +} + +interface Position { + id: string; + pool: { id: string }; + token0: { id: string; name: string }; + token1: { id: string; name: string }; + tickLower: { tickIdx: number }; + tickUpper: { tickIdx: number }; + depositedToken0: string; + depositedToken1: string; +} + +interface GraphQLResponse { + data: { + positions: Position[]; + }; +} + +// TODO: Can user have more than 1000 LP positions? If so, we need to paginate the query +export async function getLPPositions({ chainName, account, tokenA, tokenB }: Props, { notify }: FunctionOptions): Promise { + // Check wallet connection + if (!account) return toResult('Wallet not connected', true); + + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + await notify(`Querying LP positions on Camelot V3...`); + + // Get LP positions + const positions = await queryLPPositions(chainId, account, tokenA, tokenB); + + if (!positions.length) return toResult('No LP positions found', true); + + // TODO: print out the positions to TG? + + return toResult('Successfully queried LP positions', false); +} + +export async function queryLPPositions(chainId: number, account: Address, tokenA?: Address, tokenB?: Address) { + const apiUrl = GRAPH_URLS[chainId]; + + let tokenConditions = ''; + if (tokenA && !tokenB) { + tokenConditions = ` + { + or: [ + { token0: "${tokenA.toLowerCase()}" }, + { token1: "${tokenA.toLowerCase()}" } + ] + } + `; + } else if (!tokenA && tokenB) { + tokenConditions = ` + { + or: [ + { token0: "${tokenB.toLowerCase()}" }, + { token1: "${tokenB.toLowerCase()}" } + ] + } + `; + } else if (!tokenA && !tokenB) { + tokenConditions = ` + { + or: [ + { + and: [ + { token0: "${tokenA!.toLowerCase()}" }, + { token1: "${tokenB!.toLowerCase()}" } + ] + }, + { + and: [ + { token0: "${tokenB!.toLowerCase()}" }, + { token1: "${tokenA!.toLowerCase()}" } + ] + } + ] + } + `; + } + + const query = ` + { + positions( + where: { + and: [ + { owner: "${account.toLowerCase()}" }, + { liquidity_gt: 0 }, + ${tokenConditions} + ] + } + ) { + id + pool { + id + } + token0 { + id + name + } + token1 { + id + name + } + tickLower { + tickIdx + } + tickUpper { + tickIdx + } + depositedToken0 + depositedToken1 + } + } + `; + + const positions = await axios.post(apiUrl, { query }); + return positions.data.data.positions; +} diff --git a/projects/camelot-v3/functions/increaseLiquidity.ts b/projects/camelot-v3/functions/increaseLiquidity.ts new file mode 100644 index 00000000..b7210c8e --- /dev/null +++ b/projects/camelot-v3/functions/increaseLiquidity.ts @@ -0,0 +1,258 @@ +import { FunctionOptions, FunctionReturn, getChainFromName, toResult, TransactionParams } from '@heyanon/sdk'; +import { Address, encodeFunctionData, PublicClient } from 'viem'; +import { ADDRESSES, DEFAULT_SLIPPAGE, PERCENTAGE_BASE, SUPPORTED_CHAINS, ZERO_ADDRESS } from '../constants'; +import { amountToWei } from '../utils'; +import { algebraFactoryAbi, algebraPoolAbi, nonFungiblePositionManagerAbi } from '../abis'; +import { queryLPPositions } from './getLPPositions'; + +interface Props { + chainName: string; + account: Address; + tokenA: Address; + tokenB: Address; + amountA: string; + amountB: string; + tokenId?: number; + amountAMin?: string; + amountBMin?: string; + slippage?: number; +} + +// TODO: If tokenId is not set, display all positions, so user doesn't need to query it again +export async function increaseLiquidity( + { chainName, account, tokenA, tokenB, amountA, amountB, tokenId, amountAMin, amountBMin, slippage }: Props, + { sendTransactions, notify, getProvider }: FunctionOptions, +): Promise { + // Check wallet connection + if (!account) return toResult('Wallet not connected', true); + + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + // Validate tokenId + if(tokenId && (!Number.isInteger(tokenId) || tokenId < 0)) { + return toResult(`Invalid token ID: ${tokenId}, please provide a whole non-negative number`, true); + } + + // Validate slippage + if (slippage && (!Number.isInteger(slippage) || slippage < 0 || slippage > 300)) { + return toResult(`Invalid slippage tolerance: ${slippage}, please provide a whole non-negative number, max 3% got ${slippage / 100} %`, true); + } + + await notify(`Preparing to increase liquidity on Camelot V3...`); + + // Determine position ID + let positionId: bigint; + if (!tokenId) { + const positions = await queryLPPositions(chainId, account, tokenA, tokenB); + + // Ensure we are collecting fees from a specific position + if (positions.length > 1) { + return toResult(`There are multiple LP positions, please provide a specific position ID to collect fees from`, true); + } + + positionId = BigInt(positions[0].id); + } else { + positionId = BigInt(tokenId); + } + + const provider = getProvider(chainId); + + // Get LP position data + const positionData = await getPositionData(chainId, provider, positionId); + if (!positionData) return toResult(`Position with ID ${positionId} not found`, true); + + // Remap tokenAB to token01 + let [[token0, token0Symbol, amount0Wei, amount0MinWei], [token1, token1Symbol, amount1Wei, amount1MinWei]] = await tokenABToToken01( + provider, + positionData[2], + positionData[3], + tokenA, + amountA, + amountAMin, + tokenB, + amountB, + amountBMin, + ); + + // Simulate increase liquidity amounts if amount0Min and/or amount1Min are not provided + if (amount0MinWei == 0n || amount1MinWei == 0n) { + const [simulatedAmount0, simulatedAmount1] = await simulateIncreaseLiquidityAmounts(chainId, provider, positionId, amount0Wei, amount1Wei); + amount0MinWei = simulatedAmount0; + amount1MinWei = simulatedAmount1; + } + + // Set slippage tolerance + let slippageMultiplier + if(slippage) { + slippageMultiplier = PERCENTAGE_BASE - BigInt(slippage); + } else { + // Set default 0.2% slippage tolerance + slippageMultiplier = PERCENTAGE_BASE - DEFAULT_SLIPPAGE; + } + + amount0MinWei = (amount0MinWei * slippageMultiplier) / PERCENTAGE_BASE; + amount1MinWei = (amount1MinWei * slippageMultiplier) / PERCENTAGE_BASE; + + // Validate amounts + const tickLower = positionData[4]; + const tickUpper = positionData[5]; + + const pool = await getPool(chainId, provider, token0, token1); + if (!pool || pool === ZERO_ADDRESS) return toResult(`Pool not found for ${token0} and ${token1}`, true); + + const currentTick = await getCurrentTick(provider, pool!); + + if (currentTick >= tickLower && currentTick <= tickUpper) { + // Two-sided liquidity position + if (amount0Wei === 0n) return toResult(`Amount ${token0Symbol} must be greater than 0`, true); + + if (amount1Wei === 0n) return toResult(`Amount ${token1Symbol} must be greater than 0`, true); + + if (amount0MinWei === 0n) return toResult(`Amount ${token0Symbol} MIN must be greater than 0`, true); + + if (amount1MinWei === 0n) return toResult(`Amount ${token1Symbol} MIN must be greater than 0`, true); + } else if (currentTick < tickLower) { + // Single-sided liquidity position - token0 + if (amount0Wei === 0n) return toResult(`Amount ${token0Symbol} must be greater than 0`, true); + + if (amount0MinWei === 0n) return toResult(`Amount ${token0Symbol} MIN must be greater than 0`, true); + } else if (currentTick > tickUpper) { + // Single-sided liquidity position - token1 + if (amount1Wei === 0n) return toResult(`Amount ${token1Symbol} must be greater than 0`, true); + + if (amount1MinWei === 0n) return toResult(`Amount ${token1Symbol} MIN must be greater than 0`, true); + } + + const transactions: TransactionParams[] = []; + + // Prepare increase liquidity transaction + const increaseLiquidityTxData = encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'increaseLiquidity', + args: [ + { + tokenId: positionId, + amount0Desired: amount0Wei, + amount1Desired: amount1Wei, + amount0Min: amount0MinWei, + amount1Min: amount1MinWei, + deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + }, + ], + }); + + // Wrap into multicall + const multicallTx: TransactionParams = { + target: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + data: encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'multicall', + args: [[increaseLiquidityTxData]], + }), + }; + + transactions.push(multicallTx); + + await notify('Waiting for increase liquidity transaction confirmation...'); + + const result = await sendTransactions({ chainId, account, transactions }); + const increaseLiquidity = result.data[result.data.length - 1]; + + return toResult(result.isMultisig ? increaseLiquidity.message : `Successfully increased liquidity on Camelot V3. ${increaseLiquidity.message}`); +} + +async function getPositionData(chainId: number, provider: PublicClient, positionId: bigint) { + try { + return await provider.readContract({ + address: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + abi: nonFungiblePositionManagerAbi, + functionName: 'positions', + args: [positionId], + }); + } catch (error) { + return undefined; + } +} + +async function tokenABToToken01( + provider: PublicClient, + token0: Address, + token1: Address, + tokenA: Address, + amountA: string, + amountAMin: string | undefined, + tokenB: Address, + amountB: string, + amountBMin: string | undefined, +): Promise<[Address, string, bigint, bigint][]> { + // Convert amounts to wei + let [amountAWei, amountAMinWei, amountBWei, amountBMinWei] = await Promise.all([ + amountToWei(provider, tokenA, amountA), + amountToWei(provider, tokenA, amountAMin), + amountToWei(provider, tokenB, amountB), + amountToWei(provider, tokenB, amountBMin), + ]); + + // Remap tokenAB to token01 + if (token0 === tokenA && token1 === tokenB) { + return [ + [tokenA, 'A', amountAWei, amountAMinWei], + [tokenB, 'B', amountBWei, amountBMinWei], + ]; + } else if (token0 === tokenB && token1 === tokenA) { + return [ + [tokenB, 'B', amountBWei, amountBMinWei], + [tokenA, 'A', amountAWei, amountAMinWei], + ]; + } + + throw new Error(`Invalid token pair: ${tokenA} and ${tokenB}`); +} + +async function getPool(chainId: number, provider: PublicClient, tokenA: Address, tokenB: Address): Promise
{ + try { + return await provider.readContract({ + address: ADDRESSES[chainId].ALGEBRA_FACTORY_ADDRESS, + abi: algebraFactoryAbi, + functionName: 'poolByPair', + args: [tokenA, tokenB], + }); + } catch (error) { + return undefined; + } +} + +async function getCurrentTick(provider: PublicClient, pool: Address) { + const poolState = await provider.readContract({ + address: pool, + abi: algebraPoolAbi, + functionName: 'globalState', + args: [], + }); + + return BigInt(poolState[1]); +} + +async function simulateIncreaseLiquidityAmounts(chainId: number, provider: PublicClient, positionId: bigint, amount0Desired: bigint, amount1Desired: bigint) { + const increaseLiquidity = await provider.simulateContract({ + address: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + abi: nonFungiblePositionManagerAbi, + functionName: 'increaseLiquidity', + args: [ + { + tokenId: positionId, + amount0Desired, + amount1Desired, + amount0Min: 0n, + amount1Min: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + }, + ], + }); + + const [, amount0, amount1] = increaseLiquidity.result; + return [amount0, amount1]; +} diff --git a/projects/camelot-v3/functions/index.ts b/projects/camelot-v3/functions/index.ts new file mode 100644 index 00000000..53afe21c --- /dev/null +++ b/projects/camelot-v3/functions/index.ts @@ -0,0 +1,9 @@ +export { exactInputSingle } from './exactInputSingle'; +export { exactOutputSingle } from './exactOutputSingle'; +export { quoteExactInputSingle } from './quoteExactInputSingle'; +export { quoteExactOutputSingle } from './quoteExactOutputSingle'; +export { mint } from './mint'; +export { increaseLiquidity } from './increaseLiquidity'; +export { decreaseLiquidity } from './decreaseLiquidity'; +export { collect } from './collect'; +export { getLPPositions } from './getLPPositions'; diff --git a/projects/camelot-v3/functions/mint.ts b/projects/camelot-v3/functions/mint.ts new file mode 100644 index 00000000..3e70c10c --- /dev/null +++ b/projects/camelot-v3/functions/mint.ts @@ -0,0 +1,432 @@ +import { + checkToApprove, + FunctionOptions, + FunctionReturn, + getChainFromName, + toResult, + TransactionParams, +} from '@heyanon/sdk'; +import { Address, encodeFunctionData, parseUnits, PublicClient } from 'viem'; +import { + ADDRESSES, + DEFAULT_SLIPPAGE, + MAX_TICK, + MIN_TICK, + PERCENTAGE_BASE, + SUPPORTED_CHAINS, + ZERO_ADDRESS, +} from '../constants'; +import { amountToWei, convertPriceToTick, convertTickToPrice, getDecimals } from '../utils'; +import { algebraFactoryAbi, algebraPoolAbi, nonFungiblePositionManagerAbi } from '../abis'; + +interface Props { + chainName: string; + account: Address; + tokenA: Address; + tokenB: Address; + amountA: string; + amountB: string; + amountAMin?: string; + amountBMin?: string; + lowerPrice?: string; // Assuming price is calculated as tokenB / tokenA (on DEX price is denominated as token1 / token0) + upperPrice?: string; // Assuming price is calculated as tokenB / tokenA (on DEX price is denominated as token1 / token0) + lowerPricePercentage?: number; + upperPricePercentage?: number; + recipient?: Address; + slippage?: number; +} + +// TODO: How can we detect if user has input wrong lowerPrice? We can inverse it if necessary (i.e. tokenA != token0) +// TODO: How can we detect if user has input wrong upperPrice? We can inverse it if necessary (i.e. tokenA != token0) +// TODO: How to determine min amount? +// TODO: Need to call refundETH() if adding liquidity with native asset +// TODO: /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized +// /// a method does not exist, i.e. the pool is assumed to be initialized. +export async function mint( + { chainName, account, tokenA, tokenB, amountA, amountB, amountAMin, amountBMin, lowerPrice, upperPrice, lowerPricePercentage, upperPricePercentage, recipient, slippage }: Props, + { sendTransactions, notify, getProvider }: FunctionOptions, +): Promise { + try { + // Check wallet connection + if (!account) return toResult('Wallet not connected', true); + + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + // Validate slippage + if (slippage && (!Number.isInteger(slippage) || slippage < 0 || slippage > 300)) { + return toResult(`Invalid slippage tolerance: ${slippage}, please provide a whole non-negative number, max 3% got ${slippage / 100} %`, true); + } + + await notify(`Preparing to add liquidity on Camelot V3...`); + + const provider = getProvider(chainId); + + // Get pool data + const pool = await getPool(chainId, provider, tokenA, tokenB); + if (!pool || pool === ZERO_ADDRESS) return toResult(`Pool for pair (${tokenA}, ${tokenB}) not found`, true); + + // Get token0 and token1 + const poolState = await getPoolState(provider, pool); + if (!poolState) return toResult(`Invalid pool (${pool}), failed to read pool data`, true); + + const [token0, token1, tickSpacing, sqrtPriceX96, currentTick] = poolState; + + // Remap tokenAB to token01 + let [[, token0Symbol, amount0Wei, amount0MinWei], [, token1Symbol, amount1Wei, amount1MinWei]] = await tokenABToToken01( + provider, + token0, + token1, + tokenA, + amountA, + amountAMin, + tokenB, + amountB, + amountBMin, + ); + + // Convert prices to ticks + const [token0Decimals, token1Decimals] = await Promise.all([getDecimals(provider, token0), getDecimals(provider, token1)]); + let tickLower = priceToTick(lowerPrice, lowerPricePercentage, token0Decimals, token1Decimals, tickSpacing, currentTick, true); + let tickUpper = priceToTick(upperPrice, upperPricePercentage, token0Decimals, token1Decimals, tickSpacing, currentTick, false); + + if (tickLower > tickUpper) { + return toResult('Lower price should be less than upper price.', true); + } + + // Adjust lower / upper tick when placing limit order at a specific price + if (tickLower === tickUpper) { + if (tickLower <= currentTick) { + tickLower -= 1; + } else if (tickUpper > currentTick) { + tickUpper += 1; + } + } + + // Simulate mint if amountAMin and/or amountBMin are not provided + if (amount0MinWei == 0n || amount1MinWei == 0n) { + const [simulatedAmount0, simulatedAmount1] = await simulateMintAmounts( + chainId, + provider, + token0, + token1, + tickLower, + tickUpper, + amount0Wei, + amount1Wei, + recipient ?? account, + ); + } + + // Set slippage tolerance + let slippageMultiplier + if(slippage) { + slippageMultiplier = PERCENTAGE_BASE - BigInt(slippage); + } else { + // Set default 0.2% slippage tolerance + slippageMultiplier = PERCENTAGE_BASE - DEFAULT_SLIPPAGE; + } + + amount0MinWei = (amount0MinWei * slippageMultiplier) / PERCENTAGE_BASE; + amount1MinWei = (amount1MinWei * slippageMultiplier) / PERCENTAGE_BASE; + + // Validate amounts + if (currentTick >= tickLower && currentTick <= tickUpper) { + // Two-sided liquidity position + if (amount0Wei === 0n) return toResult(`Amount ${token0Symbol} must be greater than 0`, true); + + if (amount1Wei === 0n) return toResult(`Amount ${token1Symbol} must be greater than 0`, true); + + // if (amount0MinWei === 0n) + // return toResult(`Amount ${token0Symbol} MIN must be greater than 0`, true); + // + // if (amount1MinWei === 0n) + // return toResult(`Amount ${token1Symbol} MIN must be greater than 0`, true); + } else if (currentTick < tickLower) { + // Single-sided liquidity position - token0 + if (amount0Wei === 0n) return toResult(`Amount ${token0Symbol} must be greater than 0`, true); + + // if (amount0MinWei === 0n) + // return toResult(`Amount ${token0Symbol} MIN must be greater than 0`, true); + } else if (currentTick > tickUpper) { + // Single-sided liquidity position - token1 + if (amount1Wei === 0n) return toResult(`Amount ${token1Symbol} must be greater than 0`, true); + + // if (amount1MinWei === 0n) + // return toResult(`Amount ${token1Symbol} MIN must be greater than 0`, true); + } + + const transactions: TransactionParams[] = []; + + // Check and prepare approve transaction if needed + await checkToApprove({ + args: { + account, + target: token0, + spender: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + amount: amount0Wei, + }, + provider, + transactions, + }); + + await checkToApprove({ + args: { + account, + target: token1, + spender: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + amount: amount1Wei, + }, + provider, + transactions, + }); + + // Prepare mint transaction + const mintTxData = encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'mint', + args: [ + { + token0: token0, + token1: token1, + tickLower: tickLower, + tickUpper: tickUpper, + amount0Desired: amount0Wei, + amount1Desired: amount1Wei, + amount0Min: amount0MinWei, + amount1Min: amount1MinWei, + recipient: recipient ?? account, + deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + }, + ], + }); + + // Wrap into multicall + const multicallTx: TransactionParams = { + target: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + data: encodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + functionName: 'multicall', + args: [[mintTxData]], + }), + }; + + transactions.push(multicallTx); + + await notify('Waiting for add liquidity transaction confirmation...'); + + const result = await sendTransactions({ chainId, account, transactions }); + const mintMessage = result.data[result.data.length - 1]; + + return toResult(result.isMultisig ? mintMessage.message : `Successfully added liquidity on Camelot V3. ${mintMessage.message}`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return toResult(errorMessage, true); + } +} + +async function tokenABToToken01( + provider: PublicClient, + token0: Address, + token1: Address, + tokenA: Address, + amountA: string, + amountAMin: string | undefined, + tokenB: Address, + amountB: string, + amountBMin: string | undefined, +): Promise<[Address, string, bigint, bigint][]> { + // Convert amounts to wei + let [amountAWei, amountAMinWei, amountBWei, amountBMinWei] = await Promise.all([ + amountToWei(provider, tokenA, amountA), + amountToWei(provider, tokenA, amountAMin), + amountToWei(provider, tokenB, amountB), + amountToWei(provider, tokenB, amountBMin), + ]); + + // Remap tokenAB to token01 + if (token0 === tokenA && token1 === tokenB) { + return [ + [tokenA, 'A', amountAWei, amountAMinWei], + [tokenB, 'B', amountBWei, amountBMinWei], + ]; + } else if (token0 === tokenB && token1 === tokenA) { + return [ + [tokenB, 'B', amountBWei, amountBMinWei], + [tokenA, 'A', amountAWei, amountAMinWei], + ]; + } + + throw new Error(`Invalid token pair: ${tokenA} and ${tokenB}`); +} + +async function getPool(chainId: number, provider: PublicClient, tokenA: Address, tokenB: Address): Promise
{ + try { + return await provider.readContract({ + address: ADDRESSES[chainId].ALGEBRA_FACTORY_ADDRESS, + abi: algebraFactoryAbi, + functionName: 'poolByPair', + args: [tokenA, tokenB], + }); + } catch (error) { + return undefined; + } +} + +async function getPoolState(provider: PublicClient, pool: Address): Promise<[Address, Address, number, bigint, number] | undefined> { + try { + const poolData = await provider.multicall({ + contracts: [ + { + address: pool, + abi: algebraPoolAbi, + functionName: 'token0', + args: [], + }, + { + address: pool, + abi: algebraPoolAbi, + functionName: 'token1', + args: [], + }, + { + address: pool, + abi: algebraPoolAbi, + functionName: 'tickSpacing', + args: [], + }, + { + address: pool, + abi: algebraPoolAbi, + functionName: 'globalState', + args: [], + }, + ], + }); + + const token0 = poolData[0].result!; + const token1 = poolData[1].result!; + const tickSpacing = poolData[2].result!; + const sqrtPriceX96 = poolData[3].result![0]; + const currentTick = poolData[3].result![1]; + + return [token0, token1, tickSpacing, sqrtPriceX96, currentTick]; + } catch (error) { + return undefined; + } +} + +function priceToTick( + price: string | undefined, + pricePercentage: number | undefined, + token0Decimals: number, + token1Decimals: number, + tickSpacing: number, + currentTick: number, + isLower: boolean, +) { + let tick: number; + if (isLower) { + tick = MIN_TICK; + } else { + tick = MAX_TICK; + } + + if (price) { + tick = convertPriceToTick(parseUnits(price, token1Decimals), token0Decimals, tickSpacing, isLower); + } else if (pricePercentage) { + const currentPrice = convertTickToPrice(currentTick, token0Decimals, token1Decimals); + + let percentageMultiplier: number; + if (isLower) { + percentageMultiplier = Number(PERCENTAGE_BASE) - pricePercentage; + } else { + percentageMultiplier = Number(PERCENTAGE_BASE) + pricePercentage; + } + + const adjustedPrice = (currentPrice * percentageMultiplier) / Number(PERCENTAGE_BASE); + tick = convertPriceToTick(parseUnits(adjustedPrice.toString(), token1Decimals), token0Decimals, tickSpacing, isLower); + } + + return tick; +} + +// TODO: How to implement this, there is no option for tracing, multicall doesn't allow overriding the sender, simulateContract accepts only 1 contract +export async function simulateMintAmounts( + chainId: number, + provider: PublicClient, + token0: Address, + token1: Address, + tickLower: number, + tickUpper: number, + amount0Wei: bigint, + amount1Wei: bigint, + recipient: Address, +): Promise<[bigint, bigint]> { + // const approveToken0Data = encodeFunctionData({ + // abi: nonFungiblePositionManagerAbi, + // functionName: 'approve', + // args: [ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, amount0Wei], + // }) + // + // const approveToken1Data = encodeFunctionData({ + // abi: nonFungiblePositionManagerAbi, + // functionName: 'approve', + // args: [ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, amount1Wei], + // }) + // + // const mintData = encodeFunctionData({ + // abi: nonFungiblePositionManagerAbi, + // functionName: 'mint', + // args: [{ + // token0: token0, + // token1: token1, + // tickLower: tickLower, + // tickUpper: tickUpper, + // amount0Desired: amount0Wei, + // amount1Desired: amount1Wei, + // amount0Min: 0n, + // amount1Min: 0n, + // recipient: recipient, + // deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + // }], + // }) + // + // const multi = await provider.multicall({ + // contracts: [ + // { + // address: token0, + // abi: nonFungiblePositionManagerAbi, + // functionName: 'approve', + // args: [ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, amount0Wei], + // }, + // { + // address: token1, + // abi: nonFungiblePositionManagerAbi, + // functionName: 'approve', + // args: [ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, amount1Wei], + // }, + // { + // address: ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS, + // abi: nonFungiblePositionManagerAbi, + // functionName: 'mint', + // args: [{ + // token0: token0, + // token1: token1, + // tickLower: tickLower, + // tickUpper: tickUpper, + // amount0Desired: amount0Wei, + // amount1Desired: amount1Wei, + // amount0Min: 0n, + // amount1Min: 0n, + // recipient: recipient, + // deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes from now + // }], + // } + // ], + // }) + + return [0n, 0n]; +} diff --git a/projects/camelot-v3/functions/quoteExactInputSingle.ts b/projects/camelot-v3/functions/quoteExactInputSingle.ts new file mode 100644 index 00000000..c9a3d0ff --- /dev/null +++ b/projects/camelot-v3/functions/quoteExactInputSingle.ts @@ -0,0 +1,52 @@ +import { Address, PublicClient } from 'viem'; +import { FunctionOptions, FunctionReturn, getChainFromName, toResult } from '@heyanon/sdk'; +import { ADDRESSES, SUPPORTED_CHAINS } from '../constants'; +import { quoterAbi } from '../abis'; +import { amountToWei, weiToAmount } from '../utils'; + +interface Props { + chainName: string; + tokenIn: Address; + tokenOut: Address; + amountIn: string; +} + +export async function quoteExactInputSingle({ chainName, tokenIn, tokenOut, amountIn }: Props, { getProvider, notify }: FunctionOptions): Promise { + try { + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + await notify(`Quoting swap on Camelot V3...`); + + const provider = getProvider(chainId); + + // Convert amounts to wei + const amountInWei = await amountToWei(provider, tokenIn, amountIn); + + // Validate amounts + if (amountInWei === 0n) return toResult('Amount IN must be greater than 0', true); + + // Get quote + const quote = await callQuoteExactInputSingle(chainId, provider, tokenIn, tokenOut, amountInWei); + const [amountOutWei, feeWei] = quote.result; + + // Convert wei to amounts + const [amountOut, fee] = await Promise.all([weiToAmount(provider, tokenOut, amountOutWei), weiToAmount(provider, tokenIn, BigInt(feeWei))]); + + return toResult(`Expecting to receive ${amountOut} ${tokenOut} and paying ${fee} ${tokenIn} in fees`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return toResult(errorMessage, true); + } +} + +export async function callQuoteExactInputSingle(chainId: number, provider: PublicClient, tokenIn: Address, tokenOut: Address, amountInWei: bigint) { + return provider.simulateContract({ + address: ADDRESSES[chainId].QUOTER_ADDRESS, + abi: quoterAbi, + functionName: 'quoteExactInputSingle', + args: [tokenIn, tokenOut, amountInWei, 0n], + }); +} diff --git a/projects/camelot-v3/functions/quoteExactOutputSingle.ts b/projects/camelot-v3/functions/quoteExactOutputSingle.ts new file mode 100644 index 00000000..8ce978e2 --- /dev/null +++ b/projects/camelot-v3/functions/quoteExactOutputSingle.ts @@ -0,0 +1,55 @@ +import { Address, PublicClient } from 'viem'; +import { FunctionOptions, FunctionReturn, getChainFromName, toResult } from '@heyanon/sdk'; +import { ADDRESSES, SUPPORTED_CHAINS } from '../constants'; +import { quoterAbi } from '../abis'; +import { amountToWei, weiToAmount } from '../utils'; + +interface Props { + chainName: string; + tokenIn: Address; + tokenOut: Address; + amountOut: string; +} + +export async function quoteExactOutputSingle( + { chainName, tokenIn, tokenOut, amountOut }: Props, + { sendTransactions, getProvider, notify }: FunctionOptions, +): Promise { + try { + // Validate chain + const chainId = getChainFromName(chainName); + if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true); + if (!SUPPORTED_CHAINS.includes(chainId)) return toResult(`Camelot V3 is not supported on ${chainName}`, true); + + await notify(`Quoting swap on Camelot V3...`); + + const provider = getProvider(chainId); + + // Convert amounts to wei + const amountOutWei = await amountToWei(provider, tokenIn, amountOut); + + // Validate amounts + if (amountOutWei === 0n) return toResult('Amount OUT must be greater than 0', true); + + // Get quote + const quote = await callQuoteExactOutputSingle(chainId, provider, tokenIn, tokenOut, amountOutWei); + const [amountInWei, feeWei] = quote.result; + + // Convert wei to amounts + const [amountIn, fee] = await Promise.all([weiToAmount(provider, tokenIn, amountInWei), weiToAmount(provider, tokenOut, BigInt(feeWei))]); + + return toResult(`Expecting to pay ${amountIn} ${tokenIn} and ${fee} ${tokenIn} in fees`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return toResult(errorMessage, true); + } +} + +export async function callQuoteExactOutputSingle(chainId: number, provider: PublicClient, tokenIn: Address, tokenOut: Address, amountOutWei: bigint) { + return provider.simulateContract({ + address: ADDRESSES[chainId].QUOTER_ADDRESS, + abi: quoterAbi, + functionName: 'quoteExactOutputSingle', + args: [tokenIn, tokenOut, amountOutWei, 0n], + }); +} diff --git a/projects/camelot-v3/index.ts b/projects/camelot-v3/index.ts new file mode 100644 index 00000000..cffb8fd2 --- /dev/null +++ b/projects/camelot-v3/index.ts @@ -0,0 +1,9 @@ +import { AdapterExport } from '@heyanon/sdk'; +import { tools } from './tools'; +import * as functions from './functions'; + +export default { + tools, + functions, + description: 'Camelot V3 DEX', +} satisfies AdapterExport; diff --git a/projects/camelot-v3/jest.config.js b/projects/camelot-v3/jest.config.js new file mode 100644 index 00000000..858df289 --- /dev/null +++ b/projects/camelot-v3/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/tests'], + moduleFileExtensions: ['ts', 'js'], + transform: { + '^.+\\.ts$': 'ts-jest', + }, + testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'], +}; \ No newline at end of file diff --git a/projects/camelot-v3/package.json b/projects/camelot-v3/package.json new file mode 100644 index 00000000..466d7e7d --- /dev/null +++ b/projects/camelot-v3/package.json @@ -0,0 +1,24 @@ +{ + "name": "@projects/camelot-v3", + "version": "1.0.0", + "scripts": { + "yarn": "yarn install" + }, + "dependencies": { + "@heyanon/sdk": "^1.0.4", + "axios": "^1.7.9", + "viem": "^2.22.7" + }, + "license": "MIT", + "engines": { + "npm": "please-use-yarn", + "node": ">=18.x", + "yarn": ">=1.22" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "typescript": "^5.7.3" + } +} diff --git a/projects/camelot-v3/tests/collect.test.ts b/projects/camelot-v3/tests/collect.test.ts new file mode 100644 index 00000000..259bb758 --- /dev/null +++ b/projects/camelot-v3/tests/collect.test.ts @@ -0,0 +1,306 @@ +import { collect } from '../functions'; +import { Address } from 'viem'; +import { ChainId, SendTransactionProps, toResult, TransactionReturn } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { ADDRESSES, PERCENTAGE_BASE } from '../constants'; +import { queryLPPositions } from '../functions/getLPPositions'; + +// Test data taken from: https://arbiscan.io/tx/0x145e7492ff248c9928b44151a959bffab40ca9046384910412483ad66629e30d +const chainId = ChainId.ARBITRUM; +const account = '0xc06CaeDBBb5D3C8E71a210cbe9bfA13cf73e0d5f' as Address; +const pool = '0xc23f308CF1bFA7efFFB592920a619F00990F8D74' as Address; +const tokenId = 223602n; +const tokenA = '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34' as Address; // USDe +const tokenB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Address; // USDC +const tokenADecimals = 18; +const tokenBDecimals = 6; +const deadline = 1738399977; +const collectPercentage = 10; + +jest.mock('../functions/getLPPositions'); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenA: + return Promise.resolve(tokenADecimals); + case tokenB: + return Promise.resolve(tokenBDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + case 'positions': + if (readContractProps.args[0] < 100000n) { + throw new Error('Invalid tokenId'); + } + + const nonce = 0n; + const operator = '0x0000000000000000000000000000000000000000'; + const token0 = '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34'; + const token1 = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; + const tickLower = -1; + const tickUpper = -1; + const liquidity = '0'; + const feeGrowthInside0LastX128 = '0'; + const feeGrowthInside1LastX128 = '0'; + const tokensOwed0 = '0'; + const tokensOwed1 = '0'; + + return Promise.resolve([ + nonce, + operator, + token0, + token1, + tickLower, + tickUpper, + liquidity, + feeGrowthInside0LastX128, + feeGrowthInside1LastX128, + tokensOwed0, + tokensOwed1, + ]); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'collect': + let amount0; + let amount1; + if (simulateContractProps.args[0].tokenId === 100001n) { + amount0 = 0n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId == tokenId) { + amount0 = (238360482534311640541212n * PERCENTAGE_BASE) / BigInt(collectPercentage); + amount1 = (52080430n * PERCENTAGE_BASE) / BigInt(collectPercentage); + } + + return Promise.resolve({ + result: [amount0, amount1], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), +}); + +describe('collect', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + tokenId: Number(tokenId), + collectPercentage: collectPercentage, + amount0Max: '0', + amount1Max: '0', + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await collect(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xfc6f78650000000000000000000000000000000000000000000000000000000000036972000000000000000000000000c06caedbbb5d3c8e71a210cbe9bfa13cf73e0d5f0000000000000000000000000000000000000000000032798c32c8febaa5a81c00000000000000000000000000000000000000000000000000000000031aaf2e', + ); + }); + + it('should query positions if tokenId is not provided', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const mockPositions = [{ id: `${tokenId}`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await collect( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }, + ); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xfc6f78650000000000000000000000000000000000000000000000000000000000036972000000000000000000000000c06caedbbb5d3c8e71a210cbe9bfa13cf73e0d5f0000000000000000000000000000000000000000000032798c32c8febaa5a81c00000000000000000000000000000000000000000000000000000000031aaf2e', + ); + }); + + it('should use amount MAX values when provided', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const amountAMax = '92.3'; + const amountBMax = '0.2'; + const result = await collect( + { ...props, collectPercentage: undefined, amountAMax: amountAMax, amountBMax: amountBMax }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }, + ); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xfc6f78650000000000000000000000000000000000000000000000000000000000036972000000000000000000000000c06caedbbb5d3c8e71a210cbe9bfa13cf73e0d5f00000000000000000000000000000000000000000000000500eb78f9408e00000000000000000000000000000000000000000000000000000000000000030d40', + ); + }); + + it('should return error if tokenId is not provided and user has multiple positions open', async () => { + const mockPositions = [ + { id: `1`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }, + { id: `2`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }, + ]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await collect( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('There are multiple LP positions, please provide a specific position ID to collect fees from', true)); + }); + + it('should return error if tokenId is not provided and position is not found', async () => { + const mockPositions = [{ id: `1`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await collect( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Position with ID 1 not found', true)); + }); + + it('should return error if both amountAMax and amountBMax are 0', async () => { + const result = await collect( + { ...props, collectPercentage: undefined, amountAMax: '0', amountBMax: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Nothing to collect, since both amounts are 0', true)); + }); + + it('should return error if tokenId is decimal', async () => { + let tokenId = 100001.01; + const result = await collect( + { ...props, tokenId: tokenId }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid token ID: ${tokenId}, please provide a whole non-negative number`, true)); + }); + + it('should return error if tokenId is negative', async () => { + let tokenId = -100001; + const result = await collect( + { ...props, tokenId: tokenId }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid token ID: ${tokenId}, please provide a whole non-negative number`, true)); + }); + + it('should return error if collectPercentage is decimal', async () => { + let collectPercentage = 10.01; + const result = await collect( + { ...props, collectPercentage: 10.01 }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid collect percentage: ${collectPercentage}, please provide a whole non-negative number`, true)); + }); + + it('should return error if collectPercentage is negative', async () => { + let collectPercentage = -10; + const result = await collect( + { ...props, collectPercentage: collectPercentage }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid collect percentage: ${collectPercentage}, please provide a whole non-negative number`, true)); + }); +}); diff --git a/projects/camelot-v3/tests/decreaseLiquidity.test.ts b/projects/camelot-v3/tests/decreaseLiquidity.test.ts new file mode 100644 index 00000000..536854c4 --- /dev/null +++ b/projects/camelot-v3/tests/decreaseLiquidity.test.ts @@ -0,0 +1,317 @@ +import { decreaseLiquidity } from '../functions'; +import { Address, decodeFunctionData } from 'viem'; +import { ChainId, SendTransactionProps, toResult, TransactionReturn } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { ADDRESSES, PERCENTAGE_BASE } from '../constants'; +import { queryLPPositions } from '../functions/getLPPositions'; +import { nonFungiblePositionManagerAbi } from '../abis'; + +// Test data taken from: https://arbiscan.io/tx/0x522c3170d075e54e576cce546292969318cc46f3df5a94651dfea54961013c52 +const chainId = ChainId.ARBITRUM; +const account = '0x4f545Eac62b68765D84A19D6A6e532B21C536d20' as Address; +const pool = '0xa17aFCAb059F3C6751F5B64347b5a503C3291868' as Address; +const tokenId = 221117n; +const tokenA = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Address; // USDC +const tokenB = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' as Address; // USDT +const tokenADecimals = 6; +const tokenBDecimals = 6; +const deadline = 1738400417; +const decreasePercentage = 10; + +jest.mock('../functions/getLPPositions'); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenA: + return Promise.resolve(tokenADecimals); + case tokenB: + return Promise.resolve(tokenBDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + case 'positions': + if (readContractProps.args[0] < 100000n) { + throw new Error('Invalid tokenId'); + } + + const nonce = 0n; + const operator = '0x0000000000000000000000000000000000000000'; + const token0 = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; + const token1 = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9'; + const tickLower = -5; + const tickUpper = 8; + const liquidity = ((3353274044023n * PERCENTAGE_BASE) / BigInt(decreasePercentage)).toString(); + const feeGrowthInside0LastX128 = '0'; + const feeGrowthInside1LastX128 = '0'; + const tokensOwed0 = '0'; + const tokensOwed1 = '0'; + + return Promise.resolve([ + nonce, + operator, + token0, + token1, + tickLower, + tickUpper, + liquidity, + feeGrowthInside0LastX128, + feeGrowthInside1LastX128, + tokensOwed0, + tokensOwed1, + ]); + case 'poolByPair': + return Promise.resolve(pool); + case 'globalState': + const currentTick = 4n; + return Promise.resolve([-1n, currentTick]); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'decreaseLiquidity': + let amount0; + let amount1; + if (simulateContractProps.args[0].tokenId === 100001n) { + amount0 = 0n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId === 100002n) { + amount0 = 10n ** 18n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId == tokenId) { + amount0 = 506061013n; + amount1 = 1662395072n; + } + + return Promise.resolve({ + result: [amount0, amount1], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), +}); + +describe('decreaseLiquidity', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + decreasePercentage: decreasePercentage, + tokenId: Number(tokenId), + amountAMin: '0', + amountBMin: '0', + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await decreaseLiquidity(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + const decodedMulticall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: capturedTransactions[0].data, + }); + const decodedCall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: (decodedMulticall.args[0] as any)[0], + }); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000035fbd0000000000000000000000000000000000000000000000000000030cbeb54e77000000000000000000000000000000000000000000000000000000001e285578000000000000000000000000000000000000000000000000000000006311100000000000000000000000000000000000000000000000000000000000679de2a100000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should query positions if tokenId is not provided', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const mockPositions = [{ id: `${tokenId}`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }, + ); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000035fbd0000000000000000000000000000000000000000000000000000030cbeb54e77000000000000000000000000000000000000000000000000000000001e285578000000000000000000000000000000000000000000000000000000006311100000000000000000000000000000000000000000000000000000000000679de2a100000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return error if tokenId is not provided and user has multiple positions open', async () => { + const mockPositions = [ + { id: `1`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }, + { id: `2`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }, + ]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('There are multiple LP positions, please provide a specific position ID to collect fees from', true)); + }); + + it('should return error if tokenId is not provided and position is not found', async () => { + const mockPositions = [{ id: `1`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Position with ID 1 not found', true)); + }); + + it('should return error if amountAMin is 0', async () => { + const mockPositions = [{ id: `100001`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: undefined, amountAMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount A MIN must be greater than 0', true)); + }); + + it('should return error if amountBMin is 0', async () => { + const mockPositions = [{ id: `100002`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: undefined, amountBMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount B MIN must be greater than 0', true)); + }); + + it('should return error if tokenId is decimal', async () => { + const mockPositions = [{ id: `100000`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: 100000.01 }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Invalid token ID: 100000.01, please provide a whole non-negative number', true)); + }); + + it('should return error if tokenId is negative', async () => { + const mockPositions = [{ id: `100000`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: -100000 }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Invalid token ID: -100000, please provide a whole non-negative number', true)); + }); + + it('should return error if decreasePercentage is decimal', async () => { + let decreasePercentage = 10.01; + const result = await decreaseLiquidity( + { ...props, decreasePercentage: decreasePercentage }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid decrease percentage: ${decreasePercentage}, please provide a whole non-negative number`, true)); + }); + + it('should return error if decreasePercentage is negative', async () => { + let decreasePercentage = -10; + const result = await decreaseLiquidity( + { ...props, decreasePercentage: decreasePercentage }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid decrease percentage: ${decreasePercentage}, please provide a whole non-negative number`, true)); + }); +}); diff --git a/projects/camelot-v3/tests/decreaseLiquidityCollectAndBurn.test.ts b/projects/camelot-v3/tests/decreaseLiquidityCollectAndBurn.test.ts new file mode 100644 index 00000000..c0937046 --- /dev/null +++ b/projects/camelot-v3/tests/decreaseLiquidityCollectAndBurn.test.ts @@ -0,0 +1,146 @@ +import { decreaseLiquidity } from '../functions'; +import { Address } from 'viem'; +import { ChainId, SendTransactionProps, TransactionReturn } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { ADDRESSES, PERCENTAGE_BASE } from '../constants'; + +// Test data taken from: https://arbiscan.io/tx/0x145e7492ff248c9928b44151a959bffab40ca9046384910412483ad66629e30d +const chainId = ChainId.ARBITRUM; +const account = '0xc06CaeDBBb5D3C8E71a210cbe9bfA13cf73e0d5f' as Address; +const pool = '0xc23f308CF1bFA7efFFB592920a619F00990F8D74' as Address; +const tokenId = 223602n; +const tokenA = '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34' as Address; // USDe +const tokenB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Address; // USDC +const tokenADecimals = 18; +const tokenBDecimals = 6; +const deadline = 1738399977; +const decreasePercentage = 100; + +jest.mock('../functions/getLPPositions'); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenA: + return Promise.resolve(tokenADecimals); + case tokenB: + return Promise.resolve(tokenBDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + case 'positions': + if (readContractProps.args[0] < 100000n) { + throw new Error('Invalid tokenId'); + } + + const nonce = 0n; + const operator = '0x0000000000000000000000000000000000000000'; + const token0 = '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34'; + const token1 = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; + const tickLower = -276328; + const tickUpper = -276317; + const liquidity = ((217745001017074966791n * PERCENTAGE_BASE) / BigInt(decreasePercentage)).toString(); + const feeGrowthInside0LastX128 = '0'; + const feeGrowthInside1LastX128 = '0'; + const tokensOwed0 = '0'; + const tokensOwed1 = '0'; + + return Promise.resolve([ + nonce, + operator, + token0, + token1, + tickLower, + tickUpper, + liquidity, + feeGrowthInside0LastX128, + feeGrowthInside1LastX128, + tokensOwed0, + tokensOwed1, + ]); + case 'poolByPair': + return Promise.resolve(pool); + case 'globalState': + const currentTick = -276337; + return Promise.resolve([-1n, currentTick]); + default: + throw new Error('Invalid function'); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'decreaseLiquidity': + let amount0; + let amount1; + if (simulateContractProps.args[0].tokenId == tokenId) { + amount0 = 119145902314377225600454n; + amount1 = 0n; + } + + return Promise.resolve({ + result: [amount0, amount1], + }); + case 'collect': + const amount0Max = 119778964394363746027393n; + const amount1Max = 26040215n; + + return Promise.resolve({ + result: [amount0Max, amount1Max], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), +}); + +describe('decreaseLiquidity, collect and burn', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + decreasePercentage: decreasePercentage, + tokenId: Number(tokenId), + amountAMin: '0', + amountBMin: '0', + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await decreaseLiquidity(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000003697200000000000000000000000000000000000000000000000bcdd1a4c337d2e1070000000000000000000000000000000000000000000019399edaab809be7f645000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000679de0e9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f78650000000000000000000000000000000000000000000000000000000000036972000000000000000000000000c06caedbbb5d3c8e71a210cbe9bfa13cf73e0d5f00000000000000000000000000000000000000000000195d3b0d3c85a4c0878100000000000000000000000000000000000000000000000000000000018d579700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000003697200000000000000000000000000000000000000000000000000000000', + ); + }); +}); diff --git a/projects/camelot-v3/tests/decreaseLiquiditySingleSided.test.ts b/projects/camelot-v3/tests/decreaseLiquiditySingleSided.test.ts new file mode 100644 index 00000000..4487ed07 --- /dev/null +++ b/projects/camelot-v3/tests/decreaseLiquiditySingleSided.test.ts @@ -0,0 +1,230 @@ +import { decreaseLiquidity } from '../functions'; +import { Address } from 'viem'; +import { ChainId, SendTransactionProps, toResult, TransactionReturn } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { ADDRESSES, PERCENTAGE_BASE } from '../constants'; +import { queryLPPositions } from '../functions/getLPPositions'; + +// Test data taken from: https://arbiscan.io/tx/0x11ec956b971a43714a5dd90568d8171c44ea7c87672763bdd6fdec47a3e14493 +const chainId = ChainId.ARBITRUM; +const account = '0x78e709c05E4CDCBbd7c9fbA899960452f9b83966' as Address; +const pool = '0xc86Eb7B85807020b4548EE05B54bfC956eEbbfCD' as Address; +const tokenId = 223898n; +const tokenA = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Address; // USDC +const tokenB = '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8' as Address; // USDC.e +const tokenADecimals = 6; +const tokenBDecimals = 6; +const deadline = 1738401280; +const decreasePercentage = 10; + +jest.mock('../functions/getLPPositions'); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenA: + return Promise.resolve(tokenADecimals); + case tokenB: + return Promise.resolve(tokenBDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + case 'positions': + const nonce = 0n; + const operator = '0x0000000000000000000000000000000000000000'; + const token0 = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; + const token1 = '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8'; + const liquidity = ((25009798932636n * PERCENTAGE_BASE) / BigInt(decreasePercentage)).toString(); + const feeGrowthInside0LastX128 = '0'; + const feeGrowthInside1LastX128 = '0'; + const tokensOwed0 = '0'; + const tokensOwed1 = '0'; + + let tickLower; + let tickUpper; + if (readContractProps.args[0] == tokenId || readContractProps.args[0] == 100001n) { + tickLower = -5; + tickUpper = -1; + } + if (readContractProps.args[0] == 2n * tokenId || readContractProps.args[0] == -100001n) { + tickLower = 1; + tickUpper = 5; + } + + return Promise.resolve([ + nonce, + operator, + token0, + token1, + tickLower, + tickUpper, + liquidity, + feeGrowthInside0LastX128, + feeGrowthInside1LastX128, + tokensOwed0, + tokensOwed1, + ]); + case 'poolByPair': + return Promise.resolve(pool); + case 'globalState': + const currentTick = 0n; + return Promise.resolve([-1n, currentTick]); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'decreaseLiquidity': + let amount0; + let amount1; + if (simulateContractProps.args[0].tokenId == 100001n || simulateContractProps.args[0].tokenId == -100001n) { + amount0 = 0n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId == tokenId) { + amount0 = 0n; + amount1 = 5000959550n; + } + if (simulateContractProps.args[0].tokenId == 2n * tokenId) { + amount0 = 12355000959550n; + amount1 = 0n; + } + + return Promise.resolve({ + result: [amount0, amount1], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), +}); + +describe('decreaseLiquidity Single Sided ABOVE', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + decreasePercentage: decreasePercentage, + tokenId: Number(2n * tokenId), // Artificial ID + amountAMin: '0', + amountBMin: '0', + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await decreaseLiquidity(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000006d534000000000000000000000000000000000000000000000000000016bf0c2e689c00000000000000000000000000000000000000000000000000000b3c0c2909be000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000679de60000000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return error if amountAMin is 0', async () => { + const mockPositions = [{ id: `-100001`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: undefined, amountAMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount A MIN must be greater than 0', true)); + }); +}); + +describe('decreaseLiquidity Single Sided BELOW', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + decreasePercentage: decreasePercentage, + tokenId: Number(tokenId), + amountAMin: '0', + amountBMin: '0', + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await decreaseLiquidity(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a40c49ccbe0000000000000000000000000000000000000000000000000000000000036a9a000000000000000000000000000000000000000000000000000016bf0c2e689c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012a05533e00000000000000000000000000000000000000000000000000000000679de60000000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return error if amountBMin is 0', async () => { + const mockPositions = [{ id: `100001`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await decreaseLiquidity( + { ...props, tokenId: undefined, amountBMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount B MIN must be greater than 0', true)); + }); +}); diff --git a/projects/camelot-v3/tests/exactInputSingle.test.ts b/projects/camelot-v3/tests/exactInputSingle.test.ts new file mode 100644 index 00000000..ebc03f1b --- /dev/null +++ b/projects/camelot-v3/tests/exactInputSingle.test.ts @@ -0,0 +1,273 @@ +import { exactInputSingle } from '../functions'; +import { Address, decodeFunctionData } from 'viem'; +import { toResult, FunctionOptions, SendTransactionProps, TransactionReturn, ChainId } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { swapRouterAbi } from '../abis'; +import { ADDRESSES } from '../constants'; + +// Test data taken from: https://arbiscan.io/tx/0xe24017219919a7c224c2d86cf66b78943f6893e5bd6253ee757b42b71158acca +const chainId = ChainId.ARBITRUM; +const spender = '0x33128fA08f5E0545f4714434b53bDb5E98F62474' as Address; +const tokenIn = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Address; // USDC +const tokenOut = '0x2bcC6D6CdBbDC0a4071e48bb3B969b06B3330c07'; // WSOL +const tokenInDecimals = 6; +const tokenOutDecimals = 9; +const deadline = 1737057612; + +jest.mock('@heyanon/sdk', () => ({ + ...jest.requireActual('@heyanon/sdk'), + checkToApprove: jest.fn((props: any) => { + if (props.args.account != spender) { + throw new Error('Invalid account'); + } + if (props.args.target != tokenIn) { + throw new Error('Invalid target'); + } + if (props.args.spender != ADDRESSES[chainId].SWAP_ROUTER_ADDRESS) { + throw new Error('Invalid spender'); + } + if (props.args.amount <= 0n) { + throw new Error('Invalid amount'); + } + return Promise.resolve(); + }), +})); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockSendTransactions = jest.fn(); + +const mockGetProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenIn: + return Promise.resolve(tokenInDecimals); + case tokenOut: + return Promise.resolve(tokenOutDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + break; + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), +}); + +describe('exactInputSingle', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: spender, + tokenIn: tokenIn, + tokenOut: '0x2bcC6D6CdBbDC0a4071e48bb3B969b06B3330c07' as Address, // WSOL + amountIn: '600', + amountOutMin: '2.806134849', + recipient: spender, + slippage: 250, + }; + + const functionOptions: FunctionOptions = { + sendTransactions: mockSendTransactions, + notify: mockNotify, + getProvider: mockGetProvider, + }; + + it('should prepare and send transactions correctly', async () => { + // Capture transactions to verify correct call data + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await exactInputSingle(props, { + ...functionOptions, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }); + + // Check return values + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + // Check transaction data + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].SWAP_ROUTER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xbc651188000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000002bcc6d6cdbbdc0a4071e48bb3b969b06b3330c0700000000000000000000000033128fa08f5e0545f4714434b53bdb5e98f62474000000000000000000000000000000000000000000000000000000006789654c0000000000000000000000000000000000000000000000000000000023c3460000000000000000000000000000000000000000000000000000000000a313c3a50000000000000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return an error if the amountIn is 0', async () => { + const result = await exactInputSingle({ ...props, amountIn: '0' }, functionOptions); + expect(result).toEqual(toResult('Amount IN must be greater than 0', true)); + }); + + it('should return an error if the amountOutMin is 0', async () => { + const result = await exactInputSingle({ ...props, amountOutMin: '0' }, functionOptions); + expect(result).toEqual(toResult('Amount OUT MIN must be greater than 0', true)); + }); + + it('should return an error if decimals() fails', async () => { + const mockGetProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + throw new Error(`Invalid token ${readContractProps.address}`); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + }); + + const result = await exactInputSingle({ ...props, amountIn: '0' }, { ...functionOptions, getProvider: mockGetProvider }); + expect(result).toEqual(toResult(`Invalid ERC20 token contract at address ${props.tokenIn}. Failed to fetch token details`, true)); + }); + + it('should automatically adjust the slippage', async () => { + // Mock quote + const amountOut = 2806732770n; + const newMockGetProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + if (readContractProps.address == props.tokenIn) { + return Promise.resolve(tokenInDecimals); + } + if (readContractProps.address == props.tokenOut) { + return Promise.resolve(tokenOutDecimals); + } + throw new Error(`Invalid token ${readContractProps.address}`); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'quoteExactInputSingle': + return Promise.resolve({ + result: [amountOut, 4515n], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), + }); + + // Capture transactions to verify correct call data + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await exactInputSingle( + { ...props, amountOutMin: undefined, slippage: undefined }, + { + ...functionOptions, + sendTransactions: mockSendTransactions, + getProvider: newMockGetProvider, + }, + ); + + // Check return values + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + // Check transaction data + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].SWAP_ROUTER_ADDRESS); + + const decoded = decodeFunctionData({ + abi: swapRouterAbi, + data: capturedTransactions[0].data, + }); + const expectedAmountOutMin = amountOut - (amountOut * 2n) / 10000n; + const actualAmountOutMin = (decoded.args[0] as any).amountOutMinimum; + + // Evaluate diff, because the 2 different equations can result in slightly different values due to rounding (max 1 wei diff) + const diff = expectedAmountOutMin - actualAmountOutMin; + expect(diff).toBeGreaterThanOrEqual(-1n); + expect(diff).toBeLessThanOrEqual(1n); + }); + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await exactInputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is negative', async () => { + let slippage = -10; + const result = await exactInputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: -10, please provide a whole non-negative number, max 3% got -0.1 %', true)); + }); + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await exactInputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is above threshold', async () => { + let slippage = 500; + const result = await exactInputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: 500, please provide a whole non-negative number, max 3% got 5 %', true)); + }); + + it('should set the recipient correctly', async () => { + // Capture transactions to verify correct call data + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const newRecipient = '0x000000000000000000000000000000000000dEaD' as Address; + const result = await exactInputSingle( + { ...props, recipient: newRecipient }, + { + ...functionOptions, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }, + ); + + // Check return values + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + // Check transaction data + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].SWAP_ROUTER_ADDRESS); + + const decoded = decodeFunctionData({ + abi: swapRouterAbi, + data: capturedTransactions[0].data, + }); + expect((decoded.args[0] as any).recipient).toEqual(newRecipient); + }); +}); diff --git a/projects/camelot-v3/tests/exactOutputSingle.test.ts b/projects/camelot-v3/tests/exactOutputSingle.test.ts new file mode 100644 index 00000000..80e2e5e2 --- /dev/null +++ b/projects/camelot-v3/tests/exactOutputSingle.test.ts @@ -0,0 +1,275 @@ +import { exactInputSingle, exactOutputSingle } from '../functions'; +import { Address, decodeFunctionData } from 'viem'; +import { toResult, FunctionOptions, SendTransactionProps, TransactionReturn, ChainId } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { swapRouterAbi } from '../abis'; +import { ADDRESSES } from '../constants'; + +// Test data taken from: https://arbiscan.io/tx/0x481eb8d96c5c33cbc5ebf3224cff93008518cdbb88d2de2d5804e31d7de3730f +const chainId = ChainId.ARBITRUM; +const spender = '0x29BBc2B5afF41A2143f7d28fe6944453178f1473' as Address; +const tokenIn = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' as Address; // WETH +const tokenOut = '0xD56734d7f9979dD94FAE3d67C7e928234e71cD4C' as Address; // TIA.n +const amountIn = 107031653463694801n; +const tokenInDecimals = 18; +const tokenOutDecimals = 6; +const deadline = 1737101411; + +jest.mock('@heyanon/sdk', () => ({ + ...jest.requireActual('@heyanon/sdk'), + checkToApprove: jest.fn((props: any) => { + if (props.args.account != spender) { + throw new Error('Invalid account'); + } + if (props.args.target != tokenIn) { + throw new Error('Invalid target'); + } + if (props.args.spender != ADDRESSES[chainId].SWAP_ROUTER_ADDRESS) { + throw new Error('Invalid spender'); + } + if (props.args.amount <= 0n) { + throw new Error('Invalid amount'); + } + return Promise.resolve(); + }), +})); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockSendTransactions = jest.fn(); + +const mockGetProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + if (readContractProps.address == tokenIn) { + return Promise.resolve(tokenInDecimals); + } + if (readContractProps.address == tokenOut) { + return Promise.resolve(tokenOutDecimals); + } + throw new Error(`Invalid token ${readContractProps.address}`); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), +}); + +describe('exactOutputSingle', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: spender, + tokenIn: tokenIn, + tokenOut: '0xD56734d7f9979dD94FAE3d67C7e928234e71cD4C' as Address, // TIA + amountOut: '67.200000', + amountInMax: '0.10715526778495682', + recipient: spender, + slippage: 250, + }; + + const functionOptions: FunctionOptions = { + sendTransactions: mockSendTransactions, + notify: mockNotify, + getProvider: mockGetProvider, + }; + + it('should prepare and send transactions correctly', async () => { + // Mock time, to match a TX deadline (position 5 minutes before the deadline) + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + + // Capture transactions to verify correct call data + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await exactOutputSingle(props, { + ...functionOptions, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }); + + // Check return values + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + // Check transaction data + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].SWAP_ROUTER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xdb3e219800000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000d56734d7f9979dd94fae3d67c7e928234e71cd4c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029bbc2b5aff41a2143f7d28fe6944453178f147300000000000000000000000000000000000000000000000000000000678a1063000000000000000000000000000000000000000000000000000000000401640000000000000000000000000000000000000000000000000001863593b7f004440000000000000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return an error if the amountOut is 0', async () => { + const result = await exactOutputSingle({ ...props, amountOut: '0' }, functionOptions); + expect(result).toEqual(toResult('Amount OUT must be greater than 0', true)); + }); + + it('should return an error if the amountInMax is 0', async () => { + const result = await exactOutputSingle({ ...props, amountInMax: '0' }, functionOptions); + expect(result).toEqual(toResult('Amount IN MAX must be greater than 0', true)); + }); + + it('should return an error if decimals() fails', async () => { + const mockGetProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + throw new Error(`Invalid token ${readContractProps.address}`); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + }); + + const result = await exactOutputSingle({ ...props, amountOut: '0' }, { ...functionOptions, getProvider: mockGetProvider }); + expect(result).toEqual(toResult(`Invalid ERC20 token contract at address ${props.tokenOut}. Failed to fetch token details`, true)); + }); + + it('should automatically adjust the slippage', async () => { + // Mock quote + const amountIn = 107031653463694801n; + const newMockGetProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + if (readContractProps.address == props.tokenIn) { + return Promise.resolve(tokenInDecimals); + } + if (readContractProps.address == props.tokenOut) { + return Promise.resolve(tokenOutDecimals); + } + throw new Error(`Invalid token ${readContractProps.address}`); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'quoteExactOutputSingle': + return Promise.resolve({ + result: [amountIn, 4515n], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), + }); + + // Capture transactions to verify correct call data + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await exactOutputSingle( + { ...props, amountInMax: undefined, slippage: undefined }, + { + ...functionOptions, + sendTransactions: mockSendTransactions, + getProvider: newMockGetProvider, + }, + ); + + // Check return values + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + // Check transaction data + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].SWAP_ROUTER_ADDRESS); + + const decoded = decodeFunctionData({ + abi: swapRouterAbi, + data: capturedTransactions[0].data, + }); + const expectedAmountInMax = amountIn + (amountIn * 2n) / 10000n; + const actualAmountInMax = (decoded.args[0] as any).amountInMaximum; + + // Evaluate diff, because the 2 different equations can result in slightly different values due to rounding (max 1 wei diff) + const diff = expectedAmountInMax - actualAmountInMax; + expect(diff).toBeGreaterThanOrEqual(-1n); + expect(diff).toBeLessThanOrEqual(1n); + }); + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await exactOutputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is negative', async () => { + let slippage = -10; + const result = await exactOutputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: -10, please provide a whole non-negative number, max 3% got -0.1 %', true)); + }); + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await exactOutputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is above threshold', async () => { + let slippage = 500; + const result = await exactOutputSingle({ ...props, slippage: slippage }, functionOptions); + expect(result).toEqual(toResult('Invalid slippage tolerance: 500, please provide a whole non-negative number, max 3% got 5 %', true)); + }); + + it('should set the recipient correctly', async () => { + // Capture transactions to verify correct call data + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const newRecipient = '0x000000000000000000000000000000000000dEaD' as Address; + const result = await exactOutputSingle( + { ...props, recipient: newRecipient }, + { + ...functionOptions, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }, + ); + + // Check return values + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + // Check transaction data + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].SWAP_ROUTER_ADDRESS); + + const decoded = decodeFunctionData({ + abi: swapRouterAbi, + data: capturedTransactions[0].data, + }); + expect((decoded.args[0] as any).recipient).toEqual(newRecipient); + }); +}); diff --git a/projects/camelot-v3/tests/increaseLiquidity.test.ts b/projects/camelot-v3/tests/increaseLiquidity.test.ts new file mode 100644 index 00000000..c595de3f --- /dev/null +++ b/projects/camelot-v3/tests/increaseLiquidity.test.ts @@ -0,0 +1,346 @@ +import { exactInputSingle, increaseLiquidity } from '../functions'; +import { Address } from 'viem'; +import { ChainId, SendTransactionProps, toResult, TransactionReturn } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { ADDRESSES } from '../constants'; +import { queryLPPositions } from '../functions/getLPPositions'; + +// Test data taken from: https://arbiscan.io/tx/0xd5a45ebf7e5104b8eea93eb0698e934c82c1cb0441154e7013bca28b9e5d5c61 +const chainId = ChainId.ARBITRUM; +const account = '0x88af05758d2834a3A06Ccf65f28a25210d5eaD5E' as Address; +const pool = '0xa17aFCAb059F3C6751F5B64347b5a503C3291868' as Address; +const tokenId = 224013n; +const tokenA = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Address; // USDC +const tokenB = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' as Address; // USDT +const tokenADecimals = 6; +const tokenBDecimals = 6; +const deadline = 1738397137; + +jest.mock('../functions/getLPPositions'); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenA: + return Promise.resolve(tokenADecimals); + case tokenB: + return Promise.resolve(tokenBDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + break; + case 'positions': + if (readContractProps.args[0] < 100000n) { + throw new Error('Invalid tokenId'); + } + + const nonce = 0n; + const operator = '0x0000000000000000000000000000000000000000'; + const token0 = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; + const token1 = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9'; + const tickLower = 3; + const tickUpper = 6; + const liquidity = '533615414141289'; + const feeGrowthInside0LastX128 = '1344498118854185446034780858302742'; + const feeGrowthInside1LastX128 = '1335669014047967467069261652700432'; + const tokensOwed0 = '5523'; + const tokensOwed1 = '58214'; + + return Promise.resolve([ + nonce, + operator, + token0, + token1, + tickLower, + tickUpper, + liquidity, + feeGrowthInside0LastX128, + feeGrowthInside1LastX128, + tokensOwed0, + tokensOwed1, + ]); + case 'poolByPair': + return Promise.resolve(pool); + case 'globalState': + const currentTick = 4n; + return Promise.resolve([-1n, currentTick]); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'increaseLiquidity': + const liquidity = -1n; + + let amount0; + let amount1; + if (simulateContractProps.args[0].tokenId === 100001n) { + amount0 = 0n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId === 100002n) { + amount0 = 10n ** 18n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId == tokenId) { + amount0 = 409058501n; + amount1 = 253150759n; + } + + return Promise.resolve({ + result: [liquidity, amount0, amount1], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), +}); + +describe('increaseLiquidity', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + amountA: '409.058501', + amountB: '253.150759', + tokenId: Number(tokenId), + amountAMin: '0', + amountBMin: '0', + slippage: 250, + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await increaseLiquidity(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c4219f5d170000000000000000000000000000000000000000000000000000000000036b0d000000000000000000000000000000000000000000000000000000001861bcc5000000000000000000000000000000000000000000000000000000000f16c6270000000000000000000000000000000000000000000000000000000017c5b1a6000000000000000000000000000000000000000000000000000000000eb6346600000000000000000000000000000000000000000000000000000000679dd5d100000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should query positions if tokenId is not provided', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const mockPositions = [{ id: `${tokenId}`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await increaseLiquidity( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }, + ); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c4219f5d170000000000000000000000000000000000000000000000000000000000036b0d000000000000000000000000000000000000000000000000000000001861bcc5000000000000000000000000000000000000000000000000000000000f16c6270000000000000000000000000000000000000000000000000000000017c5b1a6000000000000000000000000000000000000000000000000000000000eb6346600000000000000000000000000000000000000000000000000000000679dd5d100000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return error if tokenId is not provided and user has multiple positions open', async () => { + const mockPositions = [ + { id: `1`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }, + { id: `2`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }, + ]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await increaseLiquidity( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('There are multiple LP positions, please provide a specific position ID to collect fees from', true)); + }); + + it('should return error if tokenId is not provided and position is not found', async () => { + const mockPositions = [{ id: `1`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await increaseLiquidity( + { ...props, tokenId: undefined }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Position with ID 1 not found', true)); + }); + + it('should return error if amountA is 0', async () => { + const result = await increaseLiquidity( + { ...props, amountA: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount A must be greater than 0', true)); + }); + + it('should return error if amountB is 0', async () => { + const result = await increaseLiquidity( + { ...props, amountB: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount B must be greater than 0', true)); + }); + + it('should return error if amountAMin is 0', async () => { + const mockPositions = [{ id: `100001`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await increaseLiquidity( + { ...props, tokenId: undefined, amountAMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount A MIN must be greater than 0', true)); + }); + + it('should return error if amountBMin is 0', async () => { + const mockPositions = [{ id: `100002`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await increaseLiquidity( + { ...props, tokenId: undefined, amountBMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount B MIN must be greater than 0', true)); + }); + + it('should return error if tokenId is decimal', async () => { + let tokenId = 1.1 + const result = await increaseLiquidity( + { ...props, tokenId: tokenId }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid token ID: ${tokenId}, please provide a whole non-negative number`, true)); + }); + + it('should return error if tokenId is negative', async () => { + let tokenId = -1; + const result = await increaseLiquidity( + { ...props, tokenId: -1 }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid token ID: ${tokenId}, please provide a whole non-negative number`, true)); + }); + + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await increaseLiquidity({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + },); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is negative', async () => { + let slippage = -10; + const result = await increaseLiquidity({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + },); + expect(result).toEqual(toResult('Invalid slippage tolerance: -10, please provide a whole non-negative number, max 3% got -0.1 %', true)); + }); + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await increaseLiquidity({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + },); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is above threshold', async () => { + let slippage = 500; + const result = await increaseLiquidity({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + },); + expect(result).toEqual(toResult('Invalid slippage tolerance: 500, please provide a whole non-negative number, max 3% got 5 %', true)); + }); +}); diff --git a/projects/camelot-v3/tests/increaseLiquiditySingleSided.test.ts b/projects/camelot-v3/tests/increaseLiquiditySingleSided.test.ts new file mode 100644 index 00000000..313bdd30 --- /dev/null +++ b/projects/camelot-v3/tests/increaseLiquiditySingleSided.test.ts @@ -0,0 +1,259 @@ +import { increaseLiquidity } from '../functions'; +import { Address } from 'viem'; +import { ChainId, SendTransactionProps, toResult, TransactionReturn } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { ADDRESSES } from '../constants'; +import { queryLPPositions } from '../functions/getLPPositions'; + +// Test data taken from: https://arbiscan.io/tx/0xbd47bbb0464a8a9ac54da8e5a72389226908229a08b33c9301592e44a94586fc +const chainId = ChainId.ARBITRUM; +const account = '0x9e47FBb2a2A27B3b02E4a63b3eF5A3dC863c0223' as Address; +const tokenId = 223965n; +const pool = '0x293dfd996d5cd72bed712b0eeab96dbe400c0416' as Address; +const tokenA = '0x35751007a407ca6FEFfE80b3cB397736D2cf4dbe' as Address; +const tokenB = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' as Address; +const tokenADecimals = 18; +const tokenBDecimals = 18; +const deadline = 1738389388; + +jest.mock('../functions/getLPPositions'); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const mockProvider = jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenA: + return Promise.resolve(tokenADecimals); + case tokenB: + return Promise.resolve(tokenBDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + case 'positions': + const nonce = 0n; + const operator = '0x0000000000000000000000000000000000000000'; + const token0 = '0x35751007a407ca6FEFfE80b3cB397736D2cf4dbe'; + const token1 = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1'; + const liquidity = '589983001442019276107172'; + const feeGrowthInside0LastX128 = '2375395507613789249999920488145'; + const feeGrowthInside1LastX128 = '2511051852204881784824549554011'; + const tokensOwed0 = '295898528996718'; + const tokensOwed1 = '312799634385020'; + + let tickLower; + let tickUpper; + if (readContractProps.args[0] == tokenId || readContractProps.args[0] == 100001n) { + tickLower = 555; + tickUpper = 557; + } + if (readContractProps.args[0] == 2n * tokenId || readContractProps.args[0] == -100001n) { + tickLower = -557; + tickUpper = -555; + } + + return Promise.resolve([ + nonce, + operator, + token0, + token1, + tickLower, + tickUpper, + liquidity, + feeGrowthInside0LastX128, + feeGrowthInside1LastX128, + tokensOwed0, + tokensOwed1, + ]); + case 'poolByPair': + return Promise.resolve(pool); + case 'globalState': + const currentTick = 550n; + return Promise.resolve([-1n, currentTick]); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + simulateContract: jest.fn((simulateContractProps: any) => { + switch (simulateContractProps.functionName) { + case 'increaseLiquidity': + const liquidity = -1n; + + let amount0; + let amount1; + if (simulateContractProps.args[0].tokenId == 100001n || simulateContractProps.args[0].tokenId == -100001n) { + amount0 = 0n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId == tokenId) { + amount0 = 3312498266597809837n; + amount1 = 0n; + } + if (simulateContractProps.args[0].tokenId == 2n * tokenId) { + amount0 = 0n; + amount1 = 6687501733402190163n; + } + + return Promise.resolve({ + result: [liquidity, amount0, amount1], + }); + default: + throw new Error(`Invalid function ${simulateContractProps.functionName}`); + } + }), +}); + +describe('increaseLiquidity Single Sided ABOVE', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + amountA: '3.312498266597809837', + amountB: '0', + tokenId: Number(tokenId), + amountAMin: '0', + amountBMin: '0', + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await increaseLiquidity(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c4219f5d170000000000000000000000000000000000000000000000000000000000036add0000000000000000000000000000000000000000000000002df85b9f83a8dead00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002df601154b28e323000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000679db78c00000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return error if amountA is 0', async () => { + const result = await increaseLiquidity( + { ...props, amountA: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount A must be greater than 0', true)); + }); + + it('should return error if amountAMin is 0', async () => { + const mockPositions = [{ id: `100001`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await increaseLiquidity( + { ...props, tokenId: undefined, amountAMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount A MIN must be greater than 0', true)); + }); +}); + +describe('increaseLiquidity Single Sided BELOW', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: account, + tokenA: tokenA, + tokenB: tokenB, + amountA: '0', + amountB: '6.6875017334', + tokenId: Number(2n * tokenId), // Artificial ID + amountAMin: '0', + amountBMin: '0', + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions = props.transactions; + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + + const result = await increaseLiquidity(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c4219f5d17000000000000000000000000000000000000000000000000000000000006d5ba00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005ccec765061db60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005cca06f1f5321cdc00000000000000000000000000000000000000000000000000000000679db78c00000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should return error if amountB is 0', async () => { + const result = await increaseLiquidity( + { ...props, amountB: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount B must be greater than 0', true)); + }); + + it('should return error if amountBMin is 0', async () => { + const mockPositions = [{ id: `-100001`, token0: { id: tokenA }, token1: { id: tokenB }, liquidity: '-1' }]; + (queryLPPositions as jest.Mock).mockResolvedValue(mockPositions); + + const result = await increaseLiquidity( + { ...props, tokenId: undefined, amountBMin: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockProvider, + }, + ); + + expect(result).toEqual(toResult('Amount B MIN must be greater than 0', true)); + }); +}); diff --git a/projects/camelot-v3/tests/mint.test.ts b/projects/camelot-v3/tests/mint.test.ts new file mode 100644 index 00000000..f4d0506a --- /dev/null +++ b/projects/camelot-v3/tests/mint.test.ts @@ -0,0 +1,484 @@ +import { exactInputSingle, mint } from '../functions'; +import { Address, decodeFunctionData } from 'viem'; +import { ChainId, SendTransactionProps, toResult, TransactionReturn } from '@heyanon/sdk'; +import { TransactionParams } from '@heyanon/sdk/dist/blockchain/types'; +import { ADDRESSES, MAX_TICK, MIN_TICK, ZERO_ADDRESS } from '../constants'; +import { nonFungiblePositionManagerAbi } from '../abis/nonFungiblePositionManagerAbi'; + +// Test data taken from: https://arbiscan.io/tx/0xe02e9729a5c5762ff0d431504136e60fb0ab24561dc1e1cee96da8599337abb4 +const chainId = ChainId.ARBITRUM; +const spender = '0x0419959C9ffF74FEaC47e51D5869fabcA61FFF15' as Address; +const pool = '0xc23f308CF1bFA7efFFB592920a619F00990F8D74' as Address; +const tokenA = '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34' as Address; // USD.e +const tokenB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Address; // USDC +const deadline = 1737101308; +const tokenADecimals = 18; +const tokenBDecimals = 6; +const lowerPrice = '0.9999026535686368'; +const upperPrice = '1.0007028557202362'; + +jest.mock('@heyanon/sdk', () => ({ + ...jest.requireActual('@heyanon/sdk'), + checkToApprove: jest.fn((props: any) => { + if (props.args.account != spender) { + throw new Error('Invalid account'); + } + if (props.args.target != tokenA && props.args.target != tokenB) { + throw new Error('Invalid target'); + } + if (props.args.spender != ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS) { + throw new Error('Invalid spender'); + } + if (props.args.amount <= 0n) { + throw new Error('Invalid amount'); + } + return Promise.resolve(); + }), +})); + +const mockNotify = jest.fn((message: string) => { + console.log(message); + return Promise.resolve(); +}); + +const createMockGetProvider = (overrides: any = {}) => + jest.fn().mockReturnValue({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + switch (readContractProps.address) { + case tokenA: + return Promise.resolve(tokenADecimals); + case tokenB: + return Promise.resolve(tokenBDecimals); + default: + throw new Error(`Invalid token ${readContractProps.address}`); + } + case 'poolByPair': + return Promise.resolve(pool); + case 'tickSpacing': + return Promise.resolve(1); + default: + throw new Error(`Invalid function ${readContractProps.functionName}`); + } + }), + multicall: jest.fn((multicallProps: any) => { + return Promise.resolve([{ result: tokenA }, { result: tokenB }, { result: 1 }, { result: [79224581245752470721102n, -276320] }]); + }), + ...overrides, + }); + +const createMockSendTransactions = (capturedTransactions: TransactionParams[]) => + jest.fn((props: SendTransactionProps): Promise => { + capturedTransactions.push(...props.transactions); + return Promise.resolve({ + isMultisig: false, + data: [{ message: 'Transaction successful', hash: '0x' }], + }); + }); + +describe('mint', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(Date, 'now').mockImplementation(() => (deadline - 5 * 60) * 1000); + }); + + const props = { + chainName: 'arbitrum-one', + account: spender, + tokenA: tokenA, + tokenB: tokenB, + amountA: '991.000000753028705765', + amountB: '8.680072', + recipient: spender, + lowerPrice: lowerPrice, + upperPrice: upperPrice, + slippage: 250, + }; + + it('should prepare and send transactions correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = createMockSendTransactions(capturedTransactions); + const mockGetProvider = createMockGetProvider(); + + const result = await mint(props, { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + expect(capturedTransactions[0].data).toEqual( + '0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001449cc1a2830000000000000000000000005d3a1ff2b6bab83b63cd9ad0787074081a52ef34000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc89bfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc8a3000000000000000000000000000000000000000000000035b8e34224501e2de50000000000000000000000000000000000000000000000000000000000847288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000419959c9fff74feac47e51d5869fabca61fff1500000000000000000000000000000000000000000000000000000000678a0ffc00000000000000000000000000000000000000000000000000000000', + ); + }); + + it('should set the lowerPrice and upperPrice if set in percentage', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = createMockSendTransactions(capturedTransactions); + const mockGetProvider = createMockGetProvider(); + + const result = await mint( + { + ...props, + lowerPrice: undefined, + upperPrice: undefined, + lowerPricePercentage: 500, + upperPricePercentage: 2000, + }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }, + ); + + const decodedMulticall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: capturedTransactions[0].data, + }); + const decodedCall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: (decodedMulticall.args[0] as any)[0], + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + + expect((decodedCall.args[0] as any).tickLower).toEqual(-276833); + expect((decodedCall.args[0] as any).tickUpper).toEqual(-274497); + }); + + it('should bound the lowerPrice and upperPrice if undefined', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = createMockSendTransactions(capturedTransactions); + const mockGetProvider = createMockGetProvider(); + + const result = await mint( + { ...props, lowerPrice: undefined, upperPrice: undefined }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }, + ); + + const decodedMulticall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: capturedTransactions[0].data, + }); + const decodedCall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: (decodedMulticall.args[0] as any)[0], + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + + expect((decodedCall.args[0] as any).tickLower).toEqual(MIN_TICK); + expect((decodedCall.args[0] as any).tickUpper).toEqual(MAX_TICK); + }); + + it('should set the recipient correctly', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = createMockSendTransactions(capturedTransactions); + const mockGetProvider = createMockGetProvider(); + + const newRecipient = '0x000000000000000000000000000000000000dEaD' as Address; + const result = await mint( + { ...props, recipient: newRecipient }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }, + ); + + const decodedMulticall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: capturedTransactions[0].data, + }); + const decodedCall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: (decodedMulticall.args[0] as any)[0], + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + + expect((decodedCall.args[0] as any).recipient).toEqual(newRecipient); + }); + + it('should adjust the tickLower and tickUpper if they match (below current tick)', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = createMockSendTransactions(capturedTransactions); + const mockGetProvider = createMockGetProvider(); + + const result = await mint( + { ...props, lowerPrice: lowerPrice, upperPrice: lowerPrice }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }, + ); + + const decodedMulticall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: capturedTransactions[0].data, + }); + const decodedCall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: (decodedMulticall.args[0] as any)[0], + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + + expect((decodedCall.args[0] as any).tickLower).toEqual(-276326); + expect((decodedCall.args[0] as any).tickUpper).toEqual(-276325); + }); + + it('should adjust the tickLower and tickUpper if they match (above current tick)', async () => { + let capturedTransactions: TransactionParams[] = []; + const mockSendTransactions = createMockSendTransactions(capturedTransactions); + const mockGetProvider = createMockGetProvider(); + + const result = await mint( + { ...props, lowerPrice: '100', upperPrice: '100' }, + { + notify: mockNotify, + sendTransactions: mockSendTransactions, + getProvider: mockGetProvider, + }, + ); + + const decodedMulticall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: capturedTransactions[0].data, + }); + const decodedCall = decodeFunctionData({ + abi: nonFungiblePositionManagerAbi, + data: (decodedMulticall.args[0] as any)[0], + }); + + expect(result.success).toEqual(true); + expect(result.data).toContain('success'); + + expect(capturedTransactions).toHaveLength(1); + expect(capturedTransactions[0].target).toEqual(ADDRESSES[chainId].NONFUNGIBLE_POSITION_MANAGER_ADDRESS); + + expect((decodedCall.args[0] as any).tickLower).toEqual(-230271); + expect((decodedCall.args[0] as any).tickUpper).toEqual(-230270); + }); + + it('should return an error if poolByPair() fails', async () => { + const mockGetProvider = createMockGetProvider({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + if (readContractProps.address == props.tokenA) { + return Promise.resolve(tokenADecimals); + } + if (readContractProps.address == props.tokenB) { + return Promise.resolve(tokenBDecimals); + } + break; + case 'poolByPair': + throw new Error('Invalid pool'); + default: + throw new Error('Invalid function'); + } + }), + }); + + const result = await mint(props, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockGetProvider, + }); + + expect(result).toEqual(toResult(`Pool for pair (${props.tokenA}, ${props.tokenB}) not found`, true)); + }); + + it('should return an error if poolByPair() returns ZERO address', async () => { + const mockGetProvider = createMockGetProvider({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + if (readContractProps.address == props.tokenA) { + return Promise.resolve(tokenADecimals); + } + if (readContractProps.address == props.tokenB) { + return Promise.resolve(tokenBDecimals); + } + break; + case 'poolByPair': + return Promise.resolve(ZERO_ADDRESS); + default: + throw new Error('Invalid function'); + } + }), + }); + + const result = await mint(props, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockGetProvider, + }); + + expect(result).toEqual(toResult(`Pool for pair (${props.tokenA}, ${props.tokenB}) not found`, true)); + }); + + it('should return an error if getPoolState() fails', async () => { + const mockGetProvider = createMockGetProvider({ + multicall: jest.fn((multicallProps: any) => { + throw new Error('Invalid multicall'); + }), + }); + + const result = await mint(props, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockGetProvider, + }); + + expect(result).toEqual(toResult(`Invalid pool (${pool}), failed to read pool data`, true)); + }); + + it('should return an error if decimals() fails', async () => { + const mockGetProvider = createMockGetProvider({ + readContract: jest.fn((readContractProps: any) => { + switch (readContractProps.functionName) { + case 'decimals': + throw new Error(`Invalid token ${readContractProps.address}`); + case 'poolByPair': + return Promise.resolve(pool); + case 'tickSpacing': + return Promise.resolve(1); + default: + throw new Error('Invalid function'); + } + }), + }); + + const result = await mint( + { ...props, amountA: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: mockGetProvider, + }, + ); + + expect(result).toEqual(toResult(`Invalid ERC20 token contract at address ${props.tokenA}. Failed to fetch token details`, true)); + }); + + it('should return an error if lowerPrice < upperPrice', async () => { + const result = await mint( + { ...props, lowerPrice: upperPrice, upperPrice: lowerPrice }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: createMockGetProvider(), + }, + ); + + expect(result).toEqual(toResult(`Lower price should be less than upper price.`, true)); + }); + + it('should return error if amountA is 0', async () => { + const result = await mint( + { ...props, amountA: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: createMockGetProvider(), + }, + ); + + expect(result).toEqual(toResult('Amount A must be greater than 0', true)); + }); + + it('should return error if amountB is 0', async () => { + const result = await mint( + { ...props, amountB: '0' }, + { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: createMockGetProvider(), + }, + ); + + expect(result).toEqual(toResult('Amount B must be greater than 0', true)); + }); + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await mint({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: jest.fn(), + }); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is negative', async () => { + let slippage = -10; + const result = await mint({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: jest.fn(), + }); + expect(result).toEqual(toResult('Invalid slippage tolerance: -10, please provide a whole non-negative number, max 3% got -0.1 %', true)); + }); + + it('should return an error if slippage is decimal', async () => { + let slippage = 10.01; + const result = await mint({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: jest.fn(), + }); + expect(result).toEqual(toResult('Invalid slippage tolerance: 10.01, please provide a whole non-negative number, max 3% got 0.1001 %', true)); + }); + + it('should return an error if slippage is above threshold', async () => { + let slippage = 500; + const result = await mint({ ...props, slippage: slippage }, { + notify: mockNotify, + sendTransactions: jest.fn(), + getProvider: jest.fn(), + }); + expect(result).toEqual(toResult('Invalid slippage tolerance: 500, please provide a whole non-negative number, max 3% got 5 %', true)); + }); + + // + // it('should return an error if the amountAMin is 0', async () => { + // const result = await mint({...props, amountAMin: '0'}, functionOptions); + // expect(result).toEqual(toResult('Amount A MIN must be greater than 0', true)); + // }); + // + // it('should return an error if the amountBMin is 0', async () => { + // const result = await mint({...props, amountBMin: '0'}, functionOptions); + // expect(result).toEqual(toResult('Amount B MIN must be greater than 0', true)); + // }); + // +}); diff --git a/projects/camelot-v3/tools.ts b/projects/camelot-v3/tools.ts new file mode 100644 index 00000000..2cb6a2af --- /dev/null +++ b/projects/camelot-v3/tools.ts @@ -0,0 +1,423 @@ +import { SUPPORTED_CHAINS } from './constants'; +import { AiTool, getChainName } from '@heyanon/sdk'; + +export const tools: AiTool[] = [ + { + name: 'exactInputSingle', + description: 'Swap exact amount of X tokens IN for token OUT receiving at least Y tokens OUT with at most Z slippage tolerance. Optionally send them to another recipient', + required: ['chainName', 'account', 'tokenIn', 'tokenOut', 'amountIn', 'recipient', 'slippage'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'account', + type: 'string', + description: 'Account address that will execute the swap', + }, + { + name: 'tokenIn', + type: 'string', + description: 'Token address to swap from', + }, + { + name: 'tokenOut', + type: 'string', + description: 'Token address to swap to', + }, + { + name: 'amountIn', + type: 'string', + description: 'Amount of token in to swap in decimal format', + }, + { + name: 'amountOutMin', + type: 'string', + description: 'Minimum expected amount of token out to receive in decimal format', + }, + { + name: 'recipient', + type: ['string', 'null'], + description: 'Recipient address to receive the swapped token out', + }, + { + name: 'slippage', + type: 'number', + description: 'Slippage tolerance in percentage. 10000 is 100%. Default is 0.2%, maximum 3%', + }, + ], + }, + { + name: 'exactOutputSingle', + description: 'Swap token IN for exact amount of X tokens OUT while spending at most Y tokens IN with at most Z slippage tolerance. Optionally send them to another recipient', + required: ['chainName', 'account', 'tokenIn', 'tokenOut', 'amountOut', 'amountInMax', 'recipient', 'slippage'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'account', + type: 'string', + description: 'Account address that will execute the swap', + }, + { + name: 'tokenIn', + type: 'string', + description: 'Token address to swap from', + }, + { + name: 'tokenOut', + type: 'string', + description: 'Token address to swap to', + }, + { + name: 'amountOut', + type: 'string', + description: 'Exact amount of token out to receive in decimal format', + }, + { + name: 'amountInMax', + type: ['string', 'null'], + description: 'Maximum amount of token in to spend in decimal format', + }, + { + name: 'recipient', + type: ['string', 'null'], + description: 'Recipient address to receive the swapped token out', + }, + { + name: 'slippage', + type: 'number', + description: 'Slippage tolerance in percentage. 10000 is 100%. Default is 0.2%, maximum 3%', + }, + ], + }, + { + name: 'mint', + description: + 'Add liquidity to a pool using tokens A and B by specifying the amounts of each token to provide with at most Z slippage tolerance. Optionally, you can define the liquidity range, either as absolute prices or as percentages relative to the current price. You can also choose to send the position NFT to another recipient. In certain cases, this functionality can be leveraged to place a limit order at a specific price or within a defined price range.', + required: ['chainName', 'account', 'tokenA', 'tokenB', 'amountA', 'amountB', 'amountAMin', 'amountBMin', 'lowerPrice', 'upperPrice', 'lowerPricePercentage', 'upperPricePercentage', 'recipient', 'slippage'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'account', + type: 'string', + description: 'Account address that will execute the mint', + }, + { + name: 'tokenA', + type: 'string', + description: 'Token A address to add liquidity', + }, + { + name: 'tokenB', + type: 'string', + description: 'Token B address to add liquidity', + }, + { + name: 'amountA', + type: 'string', + description: 'Amount of token A to add in decimal format', + }, + { + name: 'amountB', + type: 'string', + description: 'Amount of token B to add in decimal format', + }, + { + name: 'amountAMin', + type: ['string', 'null'], + description: 'Minimum amount of token A to add in decimal format', + }, + { + name: 'amountBMin', + type: ['string', 'null'], + description: 'Minimum amount of token B to add in decimal format', + }, + { + name: 'lowerPrice', + type: ['string', 'null'], + description: 'Lower price range for adding liquidity (provided as token1 / token0)', + }, + { + name: 'upperPrice', + type: ['string', 'null'], + description: 'Upper price range for adding liquidity (provided as token1 / token0)', + }, + { + name: 'lowerPricePercentage', + type: ['number', 'null'], + description: 'Lower price percentage (from current pool price) for adding liquidity. 10000 is 100%', + }, + { + name: 'upperPricePercentage', + type: ['number', 'null'], + description: 'Upper price percentage (from current pool price) for adding liquidity. 10000 is 100%', + }, + { + name: 'recipient', + type: ['string', 'null'], + description: 'Recipient address to receive the position NFT', + }, + { + name: 'slippage', + type: 'number', + description: 'Slippage tolerance in percentage. 10000 is 100%. Default is 0.2%', + }, + ], + }, + { + name: 'collect', + description: 'Collect fees from a liquidity position on Camelot V3. If you have multiple positions, ensure you specify the position ID to collect fees from the correct one. You can also optionally define the percentage of fees to collect or set a maximum amount to be collected.', + required: ['chainName', 'account', 'tokenA', 'tokenB', 'tokenId', 'collectPercentage', 'amountAMax', 'amountBMax', 'recipient'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'account', + type: 'string', + description: 'Account address that will collect the fees', + }, + { + name: 'tokenA', + type: 'string', + description: 'Token A address of the liquidity position', + }, + { + name: 'tokenB', + type: 'string', + description: 'Token B address of the liquidity position', + }, + { + name: 'tokenId', + type: ['number', 'null'], + description: 'Specific position ID to collect fees from', + }, + { + name: 'collectPercentage', + type: ['number', 'null'], + description: 'Percentage of fees to collect. 10000 is 100%. Default is 100%', + }, + { + name: 'amountAMax', + type: ['string', 'null'], + description: 'Maximum amount of token A to collect in decimal format', + }, + { + name: 'amountBMax', + type: ['string', 'null'], + description: 'Maximum amount of token B to collect in decimal format', + }, + { + name: 'recipient', + type: ['string', 'null'], + description: 'Recipient address to receive the collected fees', + }, + ], + }, + { + name: 'decreaseLiquidity', + description: 'Decrease the percentage of liquidity from a position on Camelot V3. If you have multiple positions, ensure you specify the position ID to decrease liquidity from the correct one. You can also optionally define the minimum amounts to receive.', + required: ['chainName', 'account', 'tokenA', 'tokenB', 'decreasePercentage', 'tokenId', 'amountAMin', 'amountBMin'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'account', + type: 'string', + description: 'Account address that will execute the decrease liquidity', + }, + { + name: 'tokenA', + type: 'string', + description: 'Token A address of the liquidity position', + }, + { + name: 'tokenB', + type: 'string', + description: 'Token B address of the liquidity position', + }, + { + name: 'decreasePercentage', + type: 'number', + description: 'Percentage of liquidity to remove. 10000 is 100%', + }, + { + name: 'tokenId', + type: ['number', 'null'], + description: 'Specific position ID to decrease liquidity from', + }, + { + name: 'amountAMin', + type: ['string', 'null'], + description: 'Minimum amount of token A to receive in decimal format', + }, + { + name: 'amountBMin', + type: ['string', 'null'], + description: 'Minimum amount of token B to receive in decimal format', + }, + ], + }, + { + name: 'increaseLiquidity', + description: 'Increase liquidity in a position on Camelot V3 with at most Z slippage tolerance. If you have multiple positions, ensure you specify the position ID to increase liquidity in the correct one. You can also optionally define the minimum amounts to provide.', + required: ['chainName', 'account', 'tokenA', 'tokenB', 'amountA', 'amountB', 'tokenId', 'amountAMin', 'amountBMin', 'slippage'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'account', + type: 'string', + description: 'Account address that will execute the increase liquidity', + }, + { + name: 'tokenA', + type: 'string', + description: 'Token A address of the liquidity position', + }, + { + name: 'tokenB', + type: 'string', + description: 'Token B address of the liquidity position', + }, + { + name: 'amountA', + type: 'string', + description: 'Amount of token A to add in decimal format', + }, + { + name: 'amountB', + type: 'string', + description: 'Amount of token B to add in decimal format', + }, + { + name: 'tokenId', + type: ['number', 'null'], + description: 'Specific position ID to increase liquidity for', + }, + { + name: 'amountAMin', + type: ['string', 'null'], + description: 'Minimum amount of token A to add in decimal format', + }, + { + name: 'amountBMin', + type: ['string', 'null'], + description: 'Minimum amount of token B to add in decimal format', + }, + { + name: 'slippage', + type: 'number', + description: 'Slippage tolerance in percentage. 10000 is 100%. Default is 0.2%', + }, + ], + }, + { + name: 'getLPPositions', + description: 'Retrieve LP positions for a given account on Camelot V3. Optionally, filter by tokens.', + required: ['chainName', 'account', 'tokenA', 'tokenB'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'account', + type: 'string', + description: 'Account address to query LP positions for', + }, + { + name: 'tokenA', + type: ['string', 'null'], + description: 'Optional token A address to filter LP positions', + }, + { + name: 'tokenB', + type: ['string', 'null'], + description: 'Optional token B address to filter LP positions', + }, + ], + }, + { + name: 'quoteExactInputSingle', + description: 'Get a quote for swapping an exact amount of token A for token B on Camelot V3.', + required: ['chainName', 'tokenIn', 'tokenOut', 'amountIn'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'tokenIn', + type: 'string', + description: 'Token address to swap from', + }, + { + name: 'tokenOut', + type: 'string', + description: 'Token address to swap to', + }, + { + name: 'amountIn', + type: 'string', + description: 'Amount of token in to swap in decimal format', + }, + ], + }, + { + name: 'quoteExactOutputSingle', + description: 'Get a quote for swapping token A for an exact amount of token B on Camelot V3.', + required: ['chainName', 'tokenIn', 'tokenOut', 'amountOut'], + props: [ + { + name: 'chainName', + type: 'string', + enum: SUPPORTED_CHAINS.map(getChainName), + description: 'Chain name where to execute', + }, + { + name: 'tokenIn', + type: 'string', + description: 'Token address to swap from', + }, + { + name: 'tokenOut', + type: 'string', + description: 'Token address to swap to', + }, + { + name: 'amountOut', + type: 'string', + description: 'Exact amount of token out to receive in decimal format', + }, + ], + } +]; diff --git a/projects/camelot-v3/tsconfig.json b/projects/camelot-v3/tsconfig.json new file mode 100644 index 00000000..07cc77cf --- /dev/null +++ b/projects/camelot-v3/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "lib": ["ES2020", "DOM"] + }, + "include": ["*.ts","abis/**/*.ts","functions/**/*.ts", "tests/**/*.ts"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/projects/camelot-v3/utils.ts b/projects/camelot-v3/utils.ts new file mode 100644 index 00000000..2fc5b42a --- /dev/null +++ b/projects/camelot-v3/utils.ts @@ -0,0 +1,58 @@ +import { Address, erc20Abi, formatUnits, parseUnits, PublicClient } from 'viem'; +import { MAX_TICK, MIN_TICK } from './constants'; + +export async function getDecimals(provider: PublicClient, token: Address): Promise { + // Try-catch to detect invalid token address + try { + return provider.readContract({ + address: token, + abi: erc20Abi, + functionName: 'decimals', + args: [], + }); + } catch (error) { + throw new Error(`Invalid ERC20 token contract at address ${token}. Failed to fetch token details`); + } +} + +export async function amountToWei(provider: PublicClient, token: Address, amount: string | undefined): Promise { + if (!amount) return 0n; + + + const decimals = await getDecimals(provider, token); + return parseUnits(amount, decimals); +} + +export async function weiToAmount(provider: PublicClient, token: Address, amountInWei: bigint): Promise { + const decimals = await getDecimals(provider, token); + return formatUnits(amountInWei, decimals); +} + +// REFERENCE: https://support.uniswap.org/hc/en-us/articles/21068898875661-How-to-convert-a-price-to-a-tick-that-can-be-initialized +export function convertPriceToTick(price: bigint, tokenBDecimals: number, tickSpacing: number, isLower: boolean): number { + const basePrice = parseUnits('1', tokenBDecimals); + const sqrtPrice = Number(price) / Number(basePrice); + const tick = Math.floor(Math.log(sqrtPrice) / Math.log(1.0001)); + + if (isLower) { + const adjustedTick = Math.floor(tick / tickSpacing) * tickSpacing; + if (adjustedTick < MIN_TICK) { + return MIN_TICK; + } + + return adjustedTick; + } + + const adjustedTick = Math.ceil(tick / tickSpacing) * tickSpacing; + if (adjustedTick > MAX_TICK) { + return MAX_TICK; + } + + return adjustedTick; +} + +export function convertTickToPrice(tick: number, token0Decimals: number, token1Decimals: number): number { + const price = Math.pow(1.0001, tick); + const diff = token0Decimals - token1Decimals; + return price * Math.pow(10, diff); +} diff --git a/projects/camelot-v3/yarn.lock b/projects/camelot-v3/yarn.lock new file mode 100644 index 00000000..e9ddec71 --- /dev/null +++ b/projects/camelot-v3/yarn.lock @@ -0,0 +1,3181 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/compat-data@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7" + integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.26.0", "@babel/generator@^7.26.5", "@babel/generator@^7.7.2": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458" + integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw== + dependencies: + "@babel/parser" "^7.26.5" + "@babel/types" "^7.26.5" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.25.9": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.5.tgz#6fec9aebddef25ca57a935c86dbb915ae2da3e1f" + integrity sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw== + dependencies: + "@babel/types" "^7.26.5" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/template@^7.25.9", "@babel/template@^7.3.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.25.9": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.5.tgz#6d0be3e772ff786456c1a37538208286f6e79021" + integrity sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/parser" "^7.26.5" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.5" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.5", "@babel/types@^7.3.3": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.5.tgz#7a1e1c01d28e26d1fe7f8ec9567b3b92b9d07747" + integrity sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@heyanon/sdk@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@heyanon/sdk/-/sdk-1.0.4.tgz#97c74d7b4c3c189acd1fda923f3e5dc141c680cc" + integrity sha512-OWWOjbKeUqCIlNQJy7R2ixVxL+1kFTM7gbvN4THArVyMw7bMif4iIk+ZCaeVKkKSBXtoVh7u03PzX10dPcLzVg== + dependencies: + "@real-wagmi/sdk" "^1.4.5" + viem "^2.22.7" + vitest "^2.1.8" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@noble/curves@1.7.0", "@noble/curves@~1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + +"@noble/curves@^1.6.0", "@noble/curves@~1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.0.tgz#fe035a23959e6aeadf695851b51a87465b5ba8f7" + integrity sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ== + dependencies: + "@noble/hashes" "1.7.0" + +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@noble/hashes@1.6.1", "@noble/hashes@~1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== + +"@noble/hashes@1.7.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.0.tgz#5d9e33af2c7d04fee35de1519b80c958b2e35e39" + integrity sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w== + +"@real-wagmi/sdk@^1.4.5": + version "1.4.5" + resolved "https://registry.yarnpkg.com/@real-wagmi/sdk/-/sdk-1.4.5.tgz#967bc7494934241941eb45de6eab0b92d610bc1d" + integrity sha512-IfCA86jWWswli86yBPK194IzuMDLBGkuKSvHNjW9xTmlhAKP9lOGD26W6Z50zLMokv0WfxqoH69KS2Z/kOFecA== + dependencies: + "@uniswap/token-lists" "1.0.0-beta.33" + big.js "^6.2.1" + decimal.js-light "^2.5.1" + tiny-invariant "^1.3.1" + toformat "^2.0.0" + viem "^2.7.20" + vitest "^1.3.1" + +"@rollup/rollup-android-arm-eabi@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz#14c737dc19603a096568044eadaa60395eefb809" + integrity sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q== + +"@rollup/rollup-android-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz#9d81ea54fc5650eb4ebbc0a7d84cee331bfa30ad" + integrity sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w== + +"@rollup/rollup-darwin-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz#29448cb1370cf678b50743d2e392be18470abc23" + integrity sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q== + +"@rollup/rollup-darwin-x64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz#0ca99741c3ed096700557a43bb03359450c7857d" + integrity sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA== + +"@rollup/rollup-freebsd-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz#233f8e4c2f54ad9b719cd9645887dcbd12b38003" + integrity sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ== + +"@rollup/rollup-freebsd-x64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz#dfba762a023063dc901610722995286df4a48360" + integrity sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw== + +"@rollup/rollup-linux-arm-gnueabihf@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz#b9da54171726266c5ef4237f462a85b3c3cf6ac9" + integrity sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg== + +"@rollup/rollup-linux-arm-musleabihf@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz#b9db69b3f85f5529eb992936d8f411ee6d04297b" + integrity sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug== + +"@rollup/rollup-linux-arm64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz#2550cf9bb4d47d917fd1ab4af756d7bbc3ee1528" + integrity sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw== + +"@rollup/rollup-linux-arm64-musl@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz#9d06b26d286c7dded6336961a2f83e48330e0c80" + integrity sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA== + +"@rollup/rollup-linux-loongarch64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz#e957bb8fee0c8021329a34ca8dfa825826ee0e2e" + integrity sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz#e8585075ddfb389222c5aada39ea62d6d2511ccc" + integrity sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw== + +"@rollup/rollup-linux-riscv64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz#7d0d40cee7946ccaa5a4e19a35c6925444696a9e" + integrity sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw== + +"@rollup/rollup-linux-s390x-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz#c2dcd8a4b08b2f2778eceb7a5a5dfde6240ebdea" + integrity sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA== + +"@rollup/rollup-linux-x64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz#183637d91456877cb83d0a0315eb4788573aa588" + integrity sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg== + +"@rollup/rollup-linux-x64-musl@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz#036a4c860662519f1f9453807547fd2a11d5bb01" + integrity sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow== + +"@rollup/rollup-win32-arm64-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz#51cad812456e616bfe4db5238fb9c7497e042a52" + integrity sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw== + +"@rollup/rollup-win32-ia32-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz#661c8b3e4cd60f51deaa39d153aac4566e748e5e" + integrity sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw== + +"@rollup/rollup-win32-x64-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz#73bf1885ff052b82fbb0f82f8671f73c36e9137c" + integrity sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og== + +"@scure/base@~1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.1.tgz#dd0b2a533063ca612c17aa9ad26424a2ff5aa865" + integrity sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ== + +"@scure/bip32@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.0.tgz#6dbc6b4af7c9101b351f41231a879d8da47e0891" + integrity sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA== + dependencies: + "@noble/curves" "~1.7.0" + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + +"@scure/bip32@^1.5.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.1.tgz#848eca1bc96f6b5ce6aa750798853fb142dace05" + integrity sha512-jSO+5Ud1E588Y+LFo8TaB8JVPNAZw/lGGao+1SepHDeTs2dFLurdNIAgUuDlwezqEjRjElkCJajVrtrZaBxvaQ== + dependencies: + "@noble/curves" "~1.8.0" + "@noble/hashes" "~1.7.0" + "@scure/base" "~1.2.1" + +"@scure/bip39@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.0.tgz#c8f9533dbd787641b047984356531d84485f19be" + integrity sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A== + dependencies: + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + +"@scure/bip39@^1.4.0": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.1.tgz#a056868d672c7203a6035c808893742a79e151f6" + integrity sha512-GnlufVSP9UdAo/H2Patfv22VTtpNTyfi+I3qCKpvuB5l1KWzEYx+l2TNpBy9Ksh4xTs3Rn06tBlpWCi/1Vz8gw== + dependencies: + "@noble/hashes" "~1.7.0" + "@scure/base" "~1.2.1" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/estree@1.0.6", "@types/estree@^1.0.0": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.14": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/node@*": + version "22.10.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.7.tgz#14a1ca33fd0ebdd9d63593ed8d3fbc882a6d28d7" + integrity sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg== + dependencies: + undici-types "~6.20.0" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@uniswap/token-lists@1.0.0-beta.33": + version "1.0.0-beta.33" + resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.33.tgz#966ba96c9ccc8f0e9e09809890b438203f2b1911" + integrity sha512-JQkXcpRI3jFG8y3/CGC4TS8NkDgcxXaOQuYW8Qdvd6DcDiIyg2vVYCG9igFEzF0G6UvxgHkBKC7cWCgzZNYvQg== + +"@vitest/expect@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30" + integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ== + dependencies: + "@vitest/spy" "1.6.0" + "@vitest/utils" "1.6.0" + chai "^4.3.10" + +"@vitest/expect@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.8.tgz#13fad0e8d5a0bf0feb675dcf1d1f1a36a1773bc1" + integrity sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw== + dependencies: + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.8.tgz#51dec42ac244e949d20009249e033e274e323f73" + integrity sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA== + dependencies: + "@vitest/spy" "2.1.8" + estree-walker "^3.0.3" + magic-string "^0.30.12" + +"@vitest/pretty-format@2.1.8", "@vitest/pretty-format@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.8.tgz#88f47726e5d0cf4ba873d50c135b02e4395e2bca" + integrity sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825" + integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg== + dependencies: + "@vitest/utils" "1.6.0" + p-limit "^5.0.0" + pathe "^1.1.1" + +"@vitest/runner@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.8.tgz#b0e2dd29ca49c25e9323ea2a45a5125d8729759f" + integrity sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg== + dependencies: + "@vitest/utils" "2.1.8" + pathe "^1.1.2" + +"@vitest/snapshot@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470" + integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ== + dependencies: + magic-string "^0.30.5" + pathe "^1.1.1" + pretty-format "^29.7.0" + +"@vitest/snapshot@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.8.tgz#d5dc204f4b95dc8b5e468b455dfc99000047d2de" + integrity sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg== + dependencies: + "@vitest/pretty-format" "2.1.8" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d" + integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw== + dependencies: + tinyspy "^2.2.0" + +"@vitest/spy@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.8.tgz#bc41af3e1e6a41ae3b67e51f09724136b88fa447" + integrity sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1" + integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw== + dependencies: + diff-sequences "^29.6.3" + estree-walker "^3.0.3" + loupe "^2.3.7" + pretty-format "^29.7.0" + +"@vitest/utils@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.8.tgz#f8ef85525f3362ebd37fd25d268745108d6ae388" + integrity sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA== + dependencies: + "@vitest/pretty-format" "2.1.8" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + +abitype@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.7.tgz#876a0005d211e1c9132825d45bcee7b46416b284" + integrity sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw== + +abitype@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" + integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== + +acorn-walk@^8.3.2: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +big.js@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.2.tgz#be3bb9ac834558b53b099deef2a1d06ac6368e1a" + integrity sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001688: + version "1.0.30001692" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz#4585729d95e6b95be5b439da6ab55250cd125bf9" + integrity sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A== + +chai@^4.3.10: + version "4.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" + integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.1.0" + +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + +chalk@^4.0.0, chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.7: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +decimal.js-light@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deep-eql@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" + integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== + dependencies: + type-detect "^4.0.0" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.73: + version "1.5.83" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.83.tgz#3f74078f0c83e24bf7e692eaa855a998d1bec34f" + integrity sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-module-lexer@^1.5.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +eventemitter3@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect-type@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75" + integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +local-pkg@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.1.tgz#69658638d2a95287534d4c2fff757980100dbb6d" + integrity sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ== + dependencies: + mlly "^1.7.3" + pkg-types "^1.2.1" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +loupe@^2.3.6, loupe@^2.3.7: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + +loupe@^3.1.0, loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@^0.30.12, magic-string@^0.30.5: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +mlly@^1.7.3, mlly@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.4.tgz#3d7295ea2358ec7a271eaa5d000a0f84febe100f" + integrity sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw== + dependencies: + acorn "^8.14.0" + pathe "^2.0.1" + pkg-types "^1.3.0" + ufo "^1.5.4" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.8: + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +ox@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.6.5.tgz#e6506a589bd6af9b5fecfcb2c641b63c9882edb6" + integrity sha512-vmnH8KvMDwFZDbNY1mq2CBRBWIgSliZB/dFV0xKp+DfF/dJkTENt6nmA+DzHSSAgL/GO2ydjkXWvlndJgSY4KQ== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-limit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" + integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== + dependencies: + yocto-queue "^1.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +pathe@^1.1.1, pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathe@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.1.tgz#ee1e6965c5ccfc98dc5a4b366a6ba6dd624a33d6" + integrity sha512-6jpjMpOth5S9ITVu5clZ7NOgHNsv5vRQdheL9ztp2vZmM6fRbLvyua1tiBIL4lk8SAe3ARzeXEly6siXCjDHDw== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + +picocolors@^1.0.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-types@^1.2.1, pkg-types@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.1.tgz#bd7cc70881192777eef5326c19deb46e890917df" + integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + dependencies: + confbox "^0.1.8" + mlly "^1.7.4" + pathe "^2.0.1" + +postcss@^8.4.43: + version "8.5.1" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214" + integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rollup@^4.20.0: + version "4.30.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.30.1.tgz#d5c3d066055259366cdc3eb6f1d051c5d6afaf74" + integrity sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.30.1" + "@rollup/rollup-android-arm64" "4.30.1" + "@rollup/rollup-darwin-arm64" "4.30.1" + "@rollup/rollup-darwin-x64" "4.30.1" + "@rollup/rollup-freebsd-arm64" "4.30.1" + "@rollup/rollup-freebsd-x64" "4.30.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.30.1" + "@rollup/rollup-linux-arm-musleabihf" "4.30.1" + "@rollup/rollup-linux-arm64-gnu" "4.30.1" + "@rollup/rollup-linux-arm64-musl" "4.30.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.30.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.30.1" + "@rollup/rollup-linux-riscv64-gnu" "4.30.1" + "@rollup/rollup-linux-s390x-gnu" "4.30.1" + "@rollup/rollup-linux-x64-gnu" "4.30.1" + "@rollup/rollup-linux-x64-musl" "4.30.1" + "@rollup/rollup-win32-arm64-msvc" "4.30.1" + "@rollup/rollup-win32-ia32-msvc" "4.30.1" + "@rollup/rollup-win32-x64-msvc" "4.30.1" + fsevents "~2.3.2" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.5.0, std-env@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-literal@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-2.1.1.tgz#26906e65f606d49f748454a08084e94190c2e5ad" + integrity sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q== + dependencies: + js-tokens "^9.0.1" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tinybench@^2.5.1, tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinypool@^0.8.3: + version "0.8.4" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8" + integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ== + +tinypool@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2" + integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" + integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8" + integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ== + +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-detect@^4.0.0, type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== + +ufo@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754" + integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +update-browserslist-db@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" + integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +viem@^2.22.7, viem@^2.7.20: + version "2.22.9" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.22.9.tgz#14ddb7f1ccf900784e347e1aa157e8e58043b0c2" + integrity sha512-2yy46qYhcdo8GZggQ3Zoq9QCahI0goddzpVI/vSnTpcClQBSDxYRCuAqRzzLqjvJ7hS0UYgplC7eRkM2sYgflw== + dependencies: + "@noble/curves" "1.7.0" + "@noble/hashes" "1.6.1" + "@scure/bip32" "1.6.0" + "@scure/bip39" "1.5.0" + abitype "1.0.7" + isows "1.0.6" + ox "0.6.5" + ws "8.18.0" + +vite-node@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f" + integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw== + dependencies: + cac "^6.7.14" + debug "^4.3.4" + pathe "^1.1.1" + picocolors "^1.0.0" + vite "^5.0.0" + +vite-node@2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.8.tgz#9495ca17652f6f7f95ca7c4b568a235e0c8dbac5" + integrity sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg== + dependencies: + cac "^6.7.14" + debug "^4.3.7" + es-module-lexer "^1.5.4" + pathe "^1.1.2" + vite "^5.0.0" + +vite@^5.0.0: + version "5.4.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" + integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^1.3.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f" + integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA== + dependencies: + "@vitest/expect" "1.6.0" + "@vitest/runner" "1.6.0" + "@vitest/snapshot" "1.6.0" + "@vitest/spy" "1.6.0" + "@vitest/utils" "1.6.0" + acorn-walk "^8.3.2" + chai "^4.3.10" + debug "^4.3.4" + execa "^8.0.1" + local-pkg "^0.5.0" + magic-string "^0.30.5" + pathe "^1.1.1" + picocolors "^1.0.0" + std-env "^3.5.0" + strip-literal "^2.0.0" + tinybench "^2.5.1" + tinypool "^0.8.3" + vite "^5.0.0" + vite-node "1.6.0" + why-is-node-running "^2.2.2" + +vitest@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.8.tgz#2e6a00bc24833574d535c96d6602fb64163092fa" + integrity sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ== + dependencies: + "@vitest/expect" "2.1.8" + "@vitest/mocker" "2.1.8" + "@vitest/pretty-format" "^2.1.8" + "@vitest/runner" "2.1.8" + "@vitest/snapshot" "2.1.8" + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" + pathe "^1.1.2" + std-env "^3.8.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.8" + why-is-node-running "^2.3.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +why-is-node-running@^2.2.2, why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" + integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==