diff --git a/README.md b/README.md index d700bda..5f29337 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # Enso SDK -The Enso SDK provides a set of tools and methods to interact with the Enso API. It includes functionalities for token approvals, routing, quoting, and balance checking. +The Enso SDK provides a set of tools and methods to interact with the Enso shortcuts. It includes functionalities for automated swap routing, multichain routing, token approvals, quoting, and balance checking. ## Introduction @@ -65,7 +65,7 @@ There are 3 routing strategies available depending on your use case: ### Token Approvals -Get approval data to allow token spending: +Get approval data to allow token spending when using `router` as `routingStrategy` (not needed when using `delegate`): ```typescript // Example: Approving USDC for spending @@ -108,41 +108,6 @@ const balances = await ensoClient.getBalances({ }); ``` -### Token Data - -Get paginated information about tokens: - -```typescript -// Example: Get details about wstETH including metadata -const tokenData = await ensoClient.getTokenData({ - chainId: 1, - address: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", // wstETH - includeMetadata: true, - type: "defi", // Filter by token type - can be "defi" or "base" -}); -``` - -### Token Pricing - -Get token price data: - -```typescript -// Example: Get current price of WETH -const priceData = await ensoClient.getPriceData({ - chainId: 1, - address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH -}); - -// Example: Get prices for multiple tokens -const multiPriceData = await ensoClient.getMultiplePriceData({ - chainId: 1, - addresses: [ - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC - ], -}); -``` - ### Bundled Transactions Bundle multiple DeFi actions into a single transaction and use results between transactions. @@ -182,6 +147,18 @@ const bundleData = await ensoClient.getBundleData( ); ``` +### Bridging Pool Address + +Use `getLayerZeroPool` to get the correct pool information, and use it as `primaryAddress` for the [`bridge` action](https://docs.enso.build/pages/build/reference/actions#bridge). + +```ts +const poolInfo = await client.getLayerZeroPool({ + chainId: 42161, // Arbitrum + destinationChainId: 999, // Zora + token: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' // USDT +}); +``` + ### Non-Tokenized Positions Route to a non-tokenized position: @@ -200,7 +177,43 @@ const nonTokenizedRoute = await ensoClient.getRouteNonTokenized({ }); ``` -### Protocol Interactions + +### Token Data + +Get paginated information about tokens: + +```typescript +// Example: Get details about wstETH including metadata +const tokenData = await ensoClient.getTokenData({ + chainId: 1, + address: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", // wstETH + includeMetadata: true, + type: "defi", // Filter by token type - can be "defi" or "base" +}); +``` + +### Token Pricing + +Get token price data: + +```typescript +// Example: Get current price of WETH +const priceData = await ensoClient.getPriceData({ + chainId: 1, + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH +}); + +// Example: Get prices for multiple tokens +const multiPriceData = await ensoClient.getMultiplePriceData({ + chainId: 1, + addresses: [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC + ], +}); +``` + +### Protocol Support Get information about supported protocols: diff --git a/package.json b/package.json index a1ef7ae..ccbf65b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ensofinance/sdk", - "version": "1.1.7", + "version": "2.0.0", "license": "MIT", "description": "SDK for interacting with the Enso API", "author": "Enso Finance", @@ -41,10 +41,11 @@ "@types/jest": "^29.5.14", "axios-mock-adapter": "^2.1.0", "jest": "^29.7.0", + "msw": "^2.7.6", "prettier": "^3.4.2", "ts-jest": "^29.3.2", "tsup": "^8.3.5", "typescript": "^5.7.2", - "msw": "^2.7.6" + "viem": "^2.37.6" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c224edd..297b1b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,9 +36,15 @@ importers: typescript: specifier: ^5.7.2 version: 5.7.3 + viem: + specifier: ^2.37.6 + version: 2.37.6(typescript@5.7.3) packages: + '@adraffy/ens-normalize@1.11.0': + resolution: {integrity: sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -494,6 +500,18 @@ packages: resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} engines: {node: '>=18'} + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -602,6 +620,15 @@ packages: cpu: [x64] os: [win32] + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -662,6 +689,17 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + abitype@1.1.0: + resolution: {integrity: sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -935,6 +973,9 @@ packages: engines: {node: '>=4'} hasBin: true + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1105,6 +1146,11 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} + peerDependencies: + ws: '*' + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -1420,6 +1466,14 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + ox@0.9.3: + resolution: {integrity: sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -1791,6 +1845,14 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + viem@2.37.6: + resolution: {integrity: sha512-b+1IozQ8TciVQNdQUkOH5xtFR0z7ZxR8pyloENi/a+RA408lv4LoX12ofwoiT3ip0VRhO5ni1em//X0jn/eW0g==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -1824,6 +1886,18 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -1849,6 +1923,8 @@ packages: snapshots: + '@adraffy/ens-normalize@1.11.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -2362,6 +2438,14 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.9.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + '@open-draft/deferred-promise@2.2.0': {} '@open-draft/logger@0.3.0': @@ -2431,6 +2515,19 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.31.0': optional: true + '@scure/base@1.2.6': {} + + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@sinclair/typebox@0.27.8': {} '@sinonjs/commons@3.0.1': @@ -2501,6 +2598,10 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + abitype@1.1.0(typescript@5.7.3): + optionalDependencies: + typescript: 5.7.3 + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -2782,6 +2883,8 @@ snapshots: esprima@4.0.1: {} + eventemitter3@5.0.1: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -2927,6 +3030,10 @@ snapshots: isexe@2.0.0: {} + isows@1.0.7(ws@8.18.3): + dependencies: + ws: 8.18.3 + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -3426,6 +3533,21 @@ snapshots: outvariant@1.4.3: {} + ox@0.9.3(typescript@5.7.3): + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.1.0(typescript@5.7.3) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - zod + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -3760,6 +3882,23 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + viem@2.37.6(typescript@5.7.3): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.1.0(typescript@5.7.3) + isows: 1.0.7(ws@8.18.3) + ox: 0.9.3(typescript@5.7.3) + ws: 8.18.3 + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -3801,6 +3940,8 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + ws@8.18.3: {} + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/src/client.ts b/src/client.ts index c9a0afd..0143aa4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -10,6 +10,8 @@ import { ConnectedNetwork, IporShortcutData, IporShortcutInputData, + LayerZeroPoolData, + LayerZeroPoolParams, MultiPriceParams, NetworkParams, NonTokenizedParams, @@ -331,7 +333,7 @@ export class EnsoClient { * ); */ public async getBundleData( - params: BundleParams, + params: BundleParams & { skipQuote?: boolean }, actions: BundleAction[], ): Promise { const url = "/shortcuts/bundle"; @@ -594,6 +596,36 @@ export class EnsoClient { url, }); } + + /** + * Gets LayerZero pool address for a token. + * + * Returns the LayerZero Stargate pool address for a given token address and chain ID. + * Returns null if no pool exists for the token on that chain. + * + * @param {Object} params - Parameters for the pool lookup + * @param {number} params.chainId - Chain ID + * @param {string} params.token - Token address + * @returns {Promise<{poolAddress: string | null, chainId: number, token: string}>} Pool lookup result + * @throws {Error} If the API request fails + * + * @example + * const poolInfo = await client.getLayerZeroPool({ + * chainId: 1, + * token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' + * }); + */ + public async getLayerZeroPool( + params: LayerZeroPoolParams, + ): Promise { + const url = "/layerzero/pool"; + + return this.request({ + method: "GET", + url, + params, + }); + } } // Custom error classes diff --git a/src/types/actions.ts b/src/types/actions.ts index 3e56bf2..f49dbb8 100644 --- a/src/types/actions.ts +++ b/src/types/actions.ts @@ -134,6 +134,14 @@ export type RepayAction = { }; }; +export type ContractCallArg = + | string + | Address + | Quantity + | boolean + | Array + | ActionOutputReference; + /** * Call arbitrary contract method. */ @@ -144,14 +152,18 @@ export type CallAction = { protocol: string; /** Action arguments */ args: { + tokenIn?: Address; + tokenOut?: Address; /** Contract address to call */ address: Address; /** Method to call */ method: string; - /** ABI of the method */ + /** Flattened function signature (e.g. "function collect((uint256 tokenId, address recipient, uint128 amount0Max, uint128 amount1Max) params) external payable returns (uint256 amount0, uint256 amount1)") */ abi: string; /** Arguments for the method */ - args: any[]; + args: ContractCallArg[]; + /** Optional value to send with the transaction in wei */ + value?: ActionOutputReference; }; }; @@ -344,8 +356,12 @@ export type BridgeAction = { destinationChainId: number; /** Receiver address on destination chain */ receiver: Address; - /** Optional callback data to execute on the destination chain. The callback bundle MUST start with a balance action */ + /** + * Optional callback actions bundle to execute on the destination chain. + * The callback bundle MUST start with a balance action */ callback?: BundleAction[]; + /** Additional value passed to the callback (in addition to bridge token) */ + callbackValue?: string; /** Optional callback execution gas costs */ callbackGasLimit?: string; /** Optional fee to pay in native asset */ @@ -375,6 +391,10 @@ export type DepositCLMMAction = { poolFee: Quantity; /** Optional receiver address */ receiver?: Address; + /** The minimum gap between valid price points for adding liquidity */ + tickSpacing?: Quantity; + /** The hook address */ + hook?: Address; }; }; @@ -676,6 +696,8 @@ export type SwapAction = { slippage?: Quantity; /** Optional pool fee in basis points when using specific pools */ poolFee?: Quantity; + /** The ID to identify the liquidity pool */ + poolId?: BytesArg; }; }; @@ -706,6 +728,102 @@ export type PermitTransferFromAction = { }; }; +/** + * Borrow tokens with position ID support. + */ +export type BorrowWithPositionIdAction = { + /** Protocol to borrow from */ + protocol: string; + /** Action type */ + action: "borrowwithpositionid"; + /** Action arguments */ + args: { + /** Collateral token address(es) */ + collateral: Address | Address[]; + /** Token to borrow */ + tokenOut: Address; + /** Amount to borrow in wei (with full decimals) */ + amountOut: ActionOutputReference; + /** Address of the lending pool contract */ + primaryAddress: Address; + /** Position ID for the borrow position */ + positionId: string; + }; +}; + +/** + * Single deposit with position ID support. + */ +export type SingleDepositWithPositionIdAction = { + /** Protocol to deposit to */ + protocol: string; + /** Action type */ + action: "singledepositwithpositionid"; + /** Action arguments */ + args: { + /** Input token address */ + tokenIn: Address; + /** Output token address (optional) */ + tokenOut?: Address; + /** Amount to deposit */ + amountIn: ActionOutputReference; + /** Primary contract address */ + primaryAddress: Address; + /** Position ID for the deposit */ + positionId: string; + /** Optional receiver address */ + receiver?: Address; + }; +}; + +/** + * Single redeem with position ID support. + */ +export type SingleRedeemWithPositionIdAction = { + /** Protocol to redeem from */ + protocol: string; + /** Action type */ + action: "singleredeemwithpositionid"; + /** Action arguments */ + args: { + /** Input token address (optional) */ + tokenIn?: Address; + /** Output token address */ + tokenOut: Address; + /** Amount to redeem */ + amountIn: ActionOutputReference; + /** Primary contract address */ + primaryAddress: Address; + /** Position ID for the redeem */ + positionId: string; + /** Optional receiver address */ + receiver?: Address; + }; +}; + +/** + * Repay with position ID support. + */ +export type RepayWithPositionIdAction = { + /** Protocol to repay to */ + protocol: string; + /** Action type */ + action: "repaywithpositionid"; + /** Action arguments */ + args: { + /** Token to repay with */ + tokenIn: Address; + /** Amount to repay in wei (with full decimals) */ + amountIn: ActionOutputReference; + /** Address of the lending pool contract */ + primaryAddress: Address; + /** Position ID for the repay */ + positionId: string; + /** The address of the user whose debt is being repaid */ + onBehalfOf?: Address; + }; +}; + /** * Union type of all possible bundle actions. */ @@ -719,7 +837,9 @@ export type BundleAction = | RedeemAction | ApproveAction | BorrowAction + | BorrowWithPositionIdAction | SingleDepositAction + | SingleDepositWithPositionIdAction | MultiDepositAction | TokenizedSingleDepositAction | TokenizedMultiDepositAction @@ -727,11 +847,13 @@ export type BundleAction = | HarvestAction | PermitTransferFromAction | SingleRedeemAction + | SingleRedeemWithPositionIdAction | MultiRedeemAction | TokenizedSingleRedeemAction | TokenizedMultiRedeemAction | RedeemCLMMAction | RepayAction + | RepayWithPositionIdAction | SwapAction | TransferFromAction | CallAction diff --git a/src/types/types.ts b/src/types/types.ts index c623695..3a09586 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -87,6 +87,8 @@ export type RouteParams = { toEoa?: boolean; /** Referral code that will be included in an on-chain event */ referralCode?: string; + /** Ethereum address of the receiver of any dust tokens */ + refundReceiver?: Address; }; /** @@ -131,6 +133,10 @@ export type Hop = { args: Record; /** Chain ID of the network */ chainId: number; + /** Source chain ID for cross-chain operations */ + sourceChainId?: number; + /** Destination chain ID for cross-chain operations */ + destinationChainId?: number; }; /** @@ -151,6 +157,8 @@ export type RouteData = { tx: Transaction; /** Collected fee amounts for each amountIn input */ feeAmount: Quantity[]; + /** Enso fee amounts */ + ensoFeeAmount: Quantity[]; }; /** @@ -249,6 +257,8 @@ export interface TokenParams { cursor?: number; /** Whether to include token metadata (symbol, name and logos) */ includeMetadata?: boolean; + /** Whether to include the underlying tokens */ + includeUnderlying?: boolean; /** Names of the tokens */ name?: string[]; /** Symbols of the tokens */ @@ -432,6 +442,8 @@ export type BundleParams = { referralCode?: string; /** A list of standards to be ignored from consideration */ ignoreStandards?: string[] | null; + /** Ethereum address of the receiver of any dust tokens */ + refundReceiver?: Address; }; /** @@ -448,7 +460,12 @@ export type BundleData = { tx: Transaction; /** Amounts out for each action */ amountsOut: Record; - route?: Hop[]; + /** The route the shortcut will use */ + route?: Hop[]; + /** Price impact in basis points, null if USD price not found */ + priceImpact: number | null; + /** Fee amount object */ + feeAmount: Record; }; /** @@ -683,3 +700,23 @@ interface PaginatedResult { /** Metadata for pagination */ meta: PaginationMeta; } + +export type LayerZeroPoolParams = { + chainId: number; + token: string; + destinationChainId?: string; + destinationToken?: string; +}; + +export type LayerZeroPoolData = { + pool: string; + chainId: number; + destinationChainId: number; + token: string; + decimals: number; + destinationData: { + pool: string; + token: string; + decimals: number; + }; +}[]; diff --git a/tests/bridging.test.ts b/tests/bridging.test.ts new file mode 100644 index 0000000..6c775b4 --- /dev/null +++ b/tests/bridging.test.ts @@ -0,0 +1,341 @@ +import { EnsoClient } from "../src"; + +import { parseUnits } from "viem"; + +describe("docs samples integration tests - bridging", () => { + it("mintErUsdCrossChainFromBerachain", async () => { + // Chain IDs + const BERACHAIN_ID = 80094; + const ETHEREUM_ID = 1; + + // Common addresses + const WALLET_ADDRESS = "0x93621DCA56fE26Cdee86e4F6B18E116e9758Ff11"; // User wallet + + // Token addresses + const USDC_BERACHAIN = "0x549943e04f40284185054145c6E4e9568C1D3241"; + const USDC_ETHEREUM = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; + const E_RUSD_ETHEREUM = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34"; + + // Protocol addresses + const RESERVOIR_MINTING_CONTRACT = + "0x4809010926aec940b550D34a46A52739f996D75D"; + const STARGATE_USDC_BRIDGE = "0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398"; + const STARGATE_E_RUSD_BRIDGE = "0xf0e9f6d9ba5d1b3f76e0f82f9dcdb9ebeef4b4da"; + + const client = new EnsoClient({ + apiKey: "56b3d1f4-5c59-4fc1-8998-16d001e277bc", + }); + + const bundle = await client.getBundleData( + { + chainId: BERACHAIN_ID, + fromAddress: WALLET_ADDRESS, + spender: WALLET_ADDRESS, + routingStrategy: "router", + }, + [ + { + protocol: "stargate", + action: "bridge", + args: { + primaryAddress: STARGATE_USDC_BRIDGE, + destinationChainId: ETHEREUM_ID, + tokenIn: USDC_BERACHAIN, + amountIn: parseUnits("1000", 6).toString(), // 1000 USDC + receiver: WALLET_ADDRESS, + callback: [ + // Step 1: Check USDC balance on Ethereum after bridge + { + protocol: "enso", + action: "balance", + args: { + token: USDC_ETHEREUM, + }, + }, + // Step 2: Mint e-rUSD using bridged USDC + { + protocol: "reservoir", + action: "deposit", + args: { + primaryAddress: RESERVOIR_MINTING_CONTRACT, + tokenIn: USDC_ETHEREUM, + tokenOut: E_RUSD_ETHEREUM, + amountIn: { useOutputOfCallAt: 0 }, // Use USDC from balance check + receiver: WALLET_ADDRESS, + }, + }, + // Step 3: Bridge newly minted e-rUSD back to Berachain + { + protocol: "stargate", + action: "bridge", + args: { + primaryAddress: STARGATE_E_RUSD_BRIDGE, + destinationChainId: BERACHAIN_ID, + tokenIn: E_RUSD_ETHEREUM, + amountIn: { useOutputOfCallAt: 1 }, // Use e-rUSD from minting + receiver: WALLET_ADDRESS, + }, + }, + ], + }, + }, + ], + ); + + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + + // Gas validation + expect(bundle.gas).toBeDefined(); + const gasEstimate = parseInt(bundle.gas.toString()); + expect(gasEstimate).toBeGreaterThan(100000); // Swap needs reasonable gas + + // Validate bundle action structure + const action = bundle.bundle[0]; + expect(action.args).toBeDefined(); + }); + + it("mintrUsdAndDepositToEulerCrossChain", async () => { + const BERACHAIN_ID = 80094; + const ETHEREUM_ID = 1; + + // Common addresses + const WALLET_ADDRESS = "0x93621DCA56fE26Cdee86e4F6B18E116e9758Ff11"; // User wallet + + // Token addresses + const USDC_BERACHAIN = "0x549943e04f40284185054145c6E4e9568C1D3241"; + const USDC_ETHEREUM = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; + const RUSD_ETHEREUM = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34"; + const RUSD_BERACHAIN = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34"; + + // Protocol addresses + const RESERVOIR_MINTING_CONTRACT = + "0x4809010926aec940b550D34a46A52739f996D75D"; + const STARGATE_USDC_BRIDGE = "0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398"; + const STARGATE_E_RUSD_BRIDGE = "0xf0e9f6d9ba5d1b3f76e0f82f9dcdb9ebeef4b4da"; + + // Euler addresses on Berachain + const EULER_VAULT_E_RUSD_BERACHAIN = + "0x109D6D1799f62216B4a7b0c6e245844AbD4DD281"; // Euler vault for e-rUSD on Berachain (need actual address) + + const client = new EnsoClient({ + apiKey: "56b3d1f4-5c59-4fc1-8998-16d001e277bc", + }); + + const bundle = await client.getBundleData( + { + chainId: BERACHAIN_ID, + fromAddress: WALLET_ADDRESS, + spender: WALLET_ADDRESS, + routingStrategy: "router", + }, + [ + { + protocol: "stargate", + action: "bridge", + args: { + primaryAddress: STARGATE_USDC_BRIDGE, + destinationChainId: ETHEREUM_ID, + tokenIn: USDC_BERACHAIN, + amountIn: parseUnits("1000", 6).toString(), // 1000 USDC + receiver: WALLET_ADDRESS, + callback: [ + // Step 1: Check USDC balance on Ethereum after bridge + { + protocol: "enso", + action: "balance", + args: { + token: USDC_ETHEREUM, + }, + }, + // Step 2: Mint e-rUSD using bridged USDC on Ethereum + { + protocol: "reservoir", + action: "deposit", + args: { + primaryAddress: RESERVOIR_MINTING_CONTRACT, + tokenIn: USDC_ETHEREUM, + tokenOut: RUSD_ETHEREUM, + amountIn: { useOutputOfCallAt: 0 }, // Use USDC from balance check + receiver: WALLET_ADDRESS, + }, + }, + // Step 3: Bridge newly minted e-rUSD back to Berachain + { + protocol: "stargate", + action: "bridge", + args: { + primaryAddress: STARGATE_E_RUSD_BRIDGE, + destinationChainId: BERACHAIN_ID, + tokenIn: RUSD_ETHEREUM, + amountIn: { useOutputOfCallAt: 1 }, // Use e-rUSD from minting + receiver: WALLET_ADDRESS, + // Callback executes on Berachain after e-rUSD arrives + callback: [ + // Step 4: Check e-rUSD balance on Berachain + { + protocol: "enso", + action: "balance", + args: { + token: RUSD_BERACHAIN, + }, + }, + // Step 5: Deposit e-rUSD into Euler vault on Berachain + { + protocol: "euler-v2", + action: "deposit", + args: { + primaryAddress: EULER_VAULT_E_RUSD_BERACHAIN, + tokenIn: RUSD_BERACHAIN, + tokenOut: EULER_VAULT_E_RUSD_BERACHAIN, // ERC4626 vault token + amountIn: { useOutputOfCallAt: 0 }, // Use e-rUSD from balance check + receiver: WALLET_ADDRESS, + }, + }, + ], + }, + }, + ], + }, + }, + ], + ); + + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); + + it("mintrUsdAndDepositToDolomiteCrossChain", async () => { + const BERACHAIN_ID = 80094; + const ETHEREUM_ID = 1; + + // Common addresses + const WALLET_ADDRESS = "0x93621DCA56fE26Cdee86e4F6B18E116e9758Ff11"; // User wallet + + // Token addresses + const USDC_BERACHAIN = "0x549943e04f40284185054145c6E4e9568C1D3241"; + const USDC_ETHEREUM = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; + const RUSD_ETHEREUM = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34"; + const RUSD_BERACHAIN = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34"; + + // Protocol addresses + const RESERVOIR_MINTING_CONTRACT = + "0x4809010926aec940b550D34a46A52739f996D75D"; + const STARGATE_USDC_BRIDGE = "0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398"; + const STARGATE_E_RUSD_BRIDGE = "0xf0e9f6d9ba5d1b3f76e0f82f9dcdb9ebeef4b4da"; + + const DOLOMITE_DRUSD_BERACHAIN = + "0x3000c6bf0aaeb813e252b584c4d9a82f99e7a71d"; // Euler vault for e-rUSD on Berachain (need actual address) + + const client = new EnsoClient({ + apiKey: "56b3d1f4-5c59-4fc1-8998-16d001e277bc", + }); + + const bundle = await client.getBundleData( + { + chainId: BERACHAIN_ID, + fromAddress: WALLET_ADDRESS, + spender: WALLET_ADDRESS, + routingStrategy: "router", + }, + [ + { + protocol: "stargate", + action: "bridge", + args: { + primaryAddress: STARGATE_USDC_BRIDGE, + destinationChainId: ETHEREUM_ID, + tokenIn: USDC_BERACHAIN, + amountIn: parseUnits("1000", 6).toString(), // 1000 USDC + receiver: WALLET_ADDRESS, + callback: [ + // Step 1: Check USDC balance on Ethereum after bridge + { + protocol: "enso", + action: "balance", + args: { + token: USDC_ETHEREUM, + }, + }, + // Step 2: Mint e-rUSD using bridged USDC on Ethereum + { + protocol: "reservoir", + action: "deposit", + args: { + primaryAddress: RESERVOIR_MINTING_CONTRACT, + tokenIn: USDC_ETHEREUM, + tokenOut: RUSD_ETHEREUM, + amountIn: { useOutputOfCallAt: 0 }, // Use USDC from balance check + receiver: WALLET_ADDRESS, + }, + }, + // Step 3: Bridge newly minted e-rUSD back to Berachain + { + protocol: "stargate", + action: "bridge", + args: { + primaryAddress: STARGATE_E_RUSD_BRIDGE, + destinationChainId: BERACHAIN_ID, + tokenIn: RUSD_ETHEREUM, + amountIn: { useOutputOfCallAt: 1 }, // Use e-rUSD from minting + receiver: WALLET_ADDRESS, + // Callback executes on Berachain after e-rUSD arrives + callback: [ + // Step 4: Check e-rUSD balance on Berachain + { + protocol: "enso", + action: "balance", + args: { + token: RUSD_BERACHAIN, + }, + }, + // Step 5: Deposit e-rUSD into Euler vault on Berachain + { + protocol: "dolomite-erc4626", + action: "deposit", + args: { + primaryAddress: DOLOMITE_DRUSD_BERACHAIN, + tokenIn: RUSD_BERACHAIN, + tokenOut: DOLOMITE_DRUSD_BERACHAIN, // ERC4626 vault token + // amountIn: { useOutputOfCallAt: 0 }, // Use e-rUSD from balance check + amountIn: "10000000000000000000", + receiver: WALLET_ADDRESS, + }, + }, + ], + }, + }, + ], + }, + }, + ], + ); + + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); +}); diff --git a/tests/docs.actions.test.ts b/tests/docs.actions.test.ts index 005db6a..5b8e73a 100644 --- a/tests/docs.actions.test.ts +++ b/tests/docs.actions.test.ts @@ -1,6 +1,7 @@ +import { parseUnits, zeroAddress } from "viem"; import { Address, BundleAction, EnsoClient } from "../src"; -describe("docs", () => { +describe("Docs samples inegration tests - actions", () => { const client = new EnsoClient({ apiKey: "56b3d1f4-5c59-4fc1-8998-16d001e277bc", }); @@ -37,7 +38,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(2); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("route", async () => { @@ -63,7 +73,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("swap", async () => { @@ -89,33 +108,144 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + + }); - it("call", async () => { - const bundle = await client.getBundleData( - { - chainId: 1, - fromAddress: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", - routingStrategy: "delegate", - }, - [ + describe("call", () => { + it("call", async () => { + const bundle = await client.getBundleData( { - protocol: "enso", - action: "call", - args: { - address: "0xD0aF6F692bFa10d6a535A3A321Dc8377F4EeEF12", // Contract address - method: "percentMul", // Method name - abi: "function percentMul(uint256,uint256) external", // ABI signature - args: [ - "1000000000000000000", // 1 ETH (first argument) - "7000", // 70% (second argument) - ], - }, + chainId: 1, + fromAddress: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", + routingStrategy: "delegate", }, - ], - ); - console.log(JSON.stringify(bundle, null, 2)); + [ + { + protocol: "enso", + action: "call", + args: { + address: "0xD0aF6F692bFa10d6a535A3A321Dc8377F4EeEF12", // Contract address + method: "percentMul", // Method name + abi: "function percentMul(uint256,uint256) external", // ABI signature + args: [ + "1000000000000000000", // 1 ETH (first argument) + "7000", // 70% (second argument) + ], + }, + }, + ], + ); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); + + it("call2", async () => { + const CONFIG = { + desiredTokenOut: "0x6969696969696969696969696969696969696969", + borrowerOperations: "0xed35ff90e6593ad71ed15082e24c204c379d3599", + trove: "0x94704805a75f9d6a18b7a2338102c63d922f1915", + underlying: "0xdE04c469Ad658163e2a5E860a03A86B52f6FA8C8", // BYUSD-HONEY-STABLE + mead: "0xedb5180661f56077292c92ab40b1ac57a279a396", + debtRepayAmount: parseUnits("500", 18).toString(), + collateralWithdrawalAmount: parseUnits("1", 18).toString(), + maxFeePercentage: parseUnits("0.005", 18).toString(), + } as const; + + async function main() { + // NOTE: Using this account that has collateral on block 6861368 + // If the sender is an account that doesnt have collateral, request will fail + const smartWalletAddress = "0x5dE1B7f14De50Ed6e430ea144eED8Bc9d0Bbb30C"; + + const bundle: BundleAction[] = [ + { + protocol: "enso", + action: "call", + args: { + address: CONFIG.borrowerOperations, + method: "adjustTrove", + abi: + "function adjustTrove(" + + "address troveManager, " + + "address account, " + + "uint256 maxFeePercentage, " + + "uint256 collateralDeposit, " + + "uint256 collateralWithdrawal, " + + "uint256 debtAmount, " + + "bool isDebtIncrease, " + + "address upperHint, " + + "address lowerHint" + + ") external", + args: [ + CONFIG.trove, + smartWalletAddress, + CONFIG.maxFeePercentage, + "0", + CONFIG.collateralWithdrawalAmount, + "0", + false, + zeroAddress, + zeroAddress, + ], + tokenOut: CONFIG.underlying, + }, + }, + { + protocol: "enso", + action: "balance", + args: { + token: CONFIG.underlying, + }, + }, + { + protocol: "enso", + action: "route", + args: { + tokenIn: CONFIG.underlying, + tokenOut: CONFIG.desiredTokenOut, + amountIn: { useOutputOfCallAt: 1 }, + }, + }, + ] as any; + + const bundleData = await client.getBundleData( + { + chainId: 80094, + fromAddress: smartWalletAddress, + routingStrategy: "delegate", + }, + bundle, + ); + expect(bundle).toBeDefined(); + expect(bundleData.bundle).toBeDefined(); + expect(Array.isArray(bundleData.bundle)).toBe(true); + expect(bundleData.bundle).toHaveLength(3); + + // Transaction validation + expect(bundleData.tx).toBeDefined(); + expect(bundleData.tx.data).toBeDefined(); + expect(bundleData.tx.to).toBeDefined(); + expect(bundleData.tx.from).toBeDefined(); + } + }); }); it("deposit", async () => { @@ -139,7 +269,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("depositCLMM", async () => { @@ -167,7 +306,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it.skip("redeem", async () => { @@ -203,7 +351,18 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + + }); it.skip("redeemCLMM", async () => { @@ -230,7 +389,17 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); it("borrow", async () => { @@ -264,7 +433,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(2); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("repay", async () => { @@ -308,7 +486,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(3); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("repay on behalf of other", async () => { @@ -333,7 +520,16 @@ describe("docs", () => { ], ); - console.log(JSON.stringify(bundleData)); + expect(bundleData).toBeDefined(); + expect(bundleData.bundle).toBeDefined(); + expect(Array.isArray(bundleData.bundle)).toBe(true); + expect(bundleData.bundle).toHaveLength(1); + + // Transaction validation + expect(bundleData.tx).toBeDefined(); + expect(bundleData.tx.data).toBeDefined(); + expect(bundleData.tx.to).toBeDefined(); + expect(bundleData.tx.from).toBeDefined(); }); it("harvest", async () => { @@ -354,7 +550,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("approve", async () => { @@ -377,7 +582,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("transfer", async () => { @@ -400,7 +614,17 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); it.skip("transferFrom", async () => { @@ -430,7 +654,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it.skip("permitTransferFrom", async () => { @@ -456,7 +689,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it.skip("bridge", async () => { @@ -562,7 +804,17 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); it("fee", async () => { @@ -585,7 +837,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it.skip("split", async () => { @@ -617,7 +878,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it.skip("merge", async () => { @@ -646,7 +916,17 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); it("balance", async () => { @@ -666,7 +946,16 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); }); it("minAmountOut", async () => { @@ -700,7 +989,17 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(2); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); // singleDeposit action (from docs) @@ -725,7 +1024,17 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); // multiDeposit action (from docs) @@ -758,7 +1067,17 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); // multiOutSingleDeposit action (from docs) @@ -786,6 +1105,184 @@ describe("docs", () => { }, ], ); - console.log(JSON.stringify(bundle, null, 2)); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + + + }); + describe("Custom Deposit Function Call", () => { + const client = new EnsoClient({ + apiKey: "56b3d1f4-5c59-4fc1-8998-16d001e277bc", + }); + const USDT: Address = "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb"; + + const STAKED_HYPE: Address = "0xffaa4a3d97fe9107cef8a3f48c069f577ff76cc1"; + const HYPE_DEPOSITOR: Address = + "0x6e358dd1204c3fb1D24e569DF0899f48faBE5337"; + const LOOPED_HYPE: Address = "0x5748ae796AE46A4F1348a1693de4b50560485562"; + + const SENDER: Address = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"; + const ENSO_SHORTCUTS: Address = + "0x4Fe93ebC4Ce6Ae4f81601cC7Ce7139023919E003"; + + const USDT_AMOUNT = "100000000"; + + describe("Smart Wallet", () => { + it("Smart Wallet deposits to LOOPED_HYPE with custom community code and dynamic token amount", async () => { + const bundle = await client.getBundleData( + { + chainId: 999, + fromAddress: SENDER, + routingStrategy: "delegate", + receiver: SENDER, + }, + [ + { + protocol: "enso", + action: "route", + args: { + tokenIn: USDT, + tokenOut: STAKED_HYPE, + amountIn: USDT_AMOUNT, + }, + }, + // without `route` we must manually approve the spender of tokens - the HypeDepositor contract + { + protocol: "erc20", + action: "approve", + args: { + amount: { useOutputOfCallAt: 0 }, + spender: HYPE_DEPOSITOR, + token: STAKED_HYPE, + }, + }, + { + protocol: "enso", + action: "call", + args: { + address: HYPE_DEPOSITOR, + args: [ + STAKED_HYPE, + { useOutputOfCallAt: 0 }, + 0, + SENDER, + "0x1234", + ], + method: "deposit", + abi: "function deposit(address depositAsset, uint256 depositAmount, uint256 minimumMint, address to, bytes communityCode) external returns (uint256 shares)", + }, + }, + ], + ); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(3); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); + + it("Smart Wallet routes to LOOPED_HYPE", async () => { + const bundle = await client.getBundleData( + { + chainId: 999, + fromAddress: SENDER, + routingStrategy: "delegate", + receiver: SENDER, + }, + [ + { + protocol: "enso", + action: "route", + args: { + tokenIn: USDT, + tokenOut: LOOPED_HYPE, + amountIn: USDT_AMOUNT, + receiver: SENDER, + }, + }, + ], + ); + expect(bundle).toBeDefined(); + expect(bundle.bundle).toBeDefined(); + expect(Array.isArray(bundle.bundle)).toBe(true); + expect(bundle.bundle).toHaveLength(1); + + // Transaction validation + expect(bundle.tx).toBeDefined(); + expect(bundle.tx.data).toBeDefined(); + expect(bundle.tx.to).toBeDefined(); + expect(bundle.tx.from).toBeDefined(); + }); + }); + + describe("EOA", () => { + it("EOA deposits to LOOPED_HYPE with custom community code and dynamic token amount", async () => { + // Approve Enso router to use USDT + const approval = await client.getApprovalData({ + amount: USDT_AMOUNT, + chainId: 999, + fromAddress: SENDER, + tokenAddress: USDT, + }); + + const bundle = await client.getBundleData( + { + chainId: 999, + fromAddress: SENDER, + routingStrategy: "router", + receiver: SENDER, + }, + [ + { + protocol: "enso", + action: "route", + args: { + tokenIn: USDT, + tokenOut: STAKED_HYPE, + amountIn: USDT_AMOUNT, + }, + }, + { + protocol: "erc20", + action: "approve", + args: { + amount: { useOutputOfCallAt: 0 }, + spender: HYPE_DEPOSITOR, + token: STAKED_HYPE, + }, + }, + { + protocol: "enso", + action: "call", + args: { + address: HYPE_DEPOSITOR, + args: [ + STAKED_HYPE, + { useOutputOfCallAt: 0 }, + 0, + SENDER, + "0x1234", + ], + method: "deposit", + abi: "function deposit(address depositAsset, uint256 depositAmount, uint256 minimumMint, address to, bytes communityCode) external returns (uint256 shares)", + }, + }, + ], + ); + }); + }); }); }); diff --git a/tests/docs.route.test.ts b/tests/docs.route.test.ts index 2389993..8ed69a6 100644 --- a/tests/docs.route.test.ts +++ b/tests/docs.route.test.ts @@ -1,6 +1,6 @@ import { Address, BundleAction, EnsoClient, RouteParams } from "../src"; -describe("docs route tests", () => { +describe("docs samples integration tests - route", () => { const client = new EnsoClient({ apiKey: "56b3d1f4-5c59-4fc1-8998-16d001e277bc", }); @@ -93,7 +93,7 @@ describe("docs route tests", () => { console.log(route); }); - it("should route USDC from Ethereum mainnet and zap into Plume Mystic vault (myPUSD)", async () => { + it.skip("should route USDC from Ethereum mainnet and zap into Plume Mystic vault (myPUSD)", async () => { const routeParams: RouteParams = { fromAddress: testWallet, receiver: testWallet,