diff --git a/src/keywords.json b/src/keywords.json index b3fdb280..a833abdc 100644 --- a/src/keywords.json +++ b/src/keywords.json @@ -39,6 +39,11 @@ "test", "echidna" ], + "/swap": [ + "swap", + "trade", + "swapping" + ], "/super": [ "calling", "parent", diff --git a/src/nav.ts b/src/nav.ts index b5088b15..ccbcb1e0 100644 --- a/src/nav.ts +++ b/src/nav.ts @@ -32,6 +32,10 @@ export const SOL_ROUTES: Route[] = [ path: "create-liquidity", title: "Create Liquidity" }, + { + path: "swap", + title: "Swap" + }, { path: "hello-world", title: "Hello World", diff --git a/src/pages/swap/Swap.sol b/src/pages/swap/Swap.sol new file mode 100644 index 00000000..d5be89f1 --- /dev/null +++ b/src/pages/swap/Swap.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; +import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; + +contract Swap { + // set the router address + PoolSwapTest swapRouter = PoolSwapTest(0x01); + + // slippage tolerance to allow for unlimited price impact + uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1; + uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1; + + /// @notice Swap tokens + /// @param key the pool where the swap is happening + /// @param amountSpecified the amount of tokens to swap + /// @param zeroForOne whether the swap is token0 -> token1 or token1 -> token0 + /// @param hookData any data to be passed to the pool's hook + function swap(PoolKey memory key, int256 amountSpecified, bool zeroForOne, bytes memory hookData) internal { + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT // unlimited impact + }); + + // in v4, users have the option to receieve native ERC20s or wrapped ERC1155 tokens + // here, we'll take the ERC20s + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + + swapRouter.swap(key, params, testSettings, hookData); + } +} diff --git a/src/pages/swap/SwapExampleInputs.sol b/src/pages/swap/SwapExampleInputs.sol new file mode 100644 index 00000000..f8e1d22c --- /dev/null +++ b/src/pages/swap/SwapExampleInputs.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; +import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; + +contract SwapExampleInputs { + // set the router address + PoolSwapTest swapRouter = PoolSwapTest(0x01); + + // slippage tolerance to allow for unlimited price impact + uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1; + uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1; + + function exampleA() internal { + address token0 = address(0x11); + address token1 = address(0x22); + + // Using a hookless pool + PoolKey memory pool = PoolKey({ + currency0: Currency(token0), + currency1: Currency(token1), + fee: 3000, + tickSpacing: 60, + hooks: IHooks(address(0x0)) + }); + + // approve tokens to the swap router + IERC20(token0).approve(address(swapRouter), type(uint256).max); + IERC20(token1).approve(address(swapRouter), type(uint256).max); + + // ---------------------------- // + // Swap 1e18 token0 into token1 + // ---------------------------- // + bool zeroForOne = true; + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: 1e18, + sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT // unlimited impact + }); + + // in v4, users have the option to receieve native ERC20s or wrapped ERC1155 tokens + // here, we'll take the ERC20s + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + + bytes memory hookData = new bytes(0); // no hook data on the hookless pool + swapRouter.swap(key, params, testSettings, hookData); + } +} diff --git a/src/pages/swap/index.html.ts b/src/pages/swap/index.html.ts new file mode 100644 index 00000000..fc2f6700 --- /dev/null +++ b/src/pages/swap/index.html.ts @@ -0,0 +1,126 @@ +// metadata +export const version = "0.8.20" +export const title = "Single Swap" +export const description = "Swapping on a single pool" + +export const keywords = [ + "swap", + "trade", + "swapping", +] + +export const codes = [ + { + fileName: "Swap.sol", + code: "Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yMDsKCmltcG9ydCB7SVBvb2xNYW5hZ2VyfSBmcm9tICJAdW5pc3dhcC92NC1jb3JlL2NvbnRyYWN0cy9pbnRlcmZhY2VzL0lQb29sTWFuYWdlci5zb2wiOwppbXBvcnQge1Bvb2xLZXl9IGZyb20gIkB1bmlzd2FwL3Y0LWNvcmUvY29udHJhY3RzL3R5cGVzL1Bvb2xLZXkuc29sIjsKaW1wb3J0IHtQb29sU3dhcFRlc3R9IGZyb20gIkB1bmlzd2FwL3Y0LWNvcmUvY29udHJhY3RzL3Rlc3QvUG9vbFN3YXBUZXN0LnNvbCI7CmltcG9ydCB7VGlja01hdGh9IGZyb20gIkB1bmlzd2FwL3Y0LWNvcmUvY29udHJhY3RzL2xpYnJhcmllcy9UaWNrTWF0aC5zb2wiOwoKY29udHJhY3QgU3dhcCB7CiAgICAvLyBzZXQgdGhlIHJvdXRlciBhZGRyZXNzCiAgICBQb29sU3dhcFRlc3Qgc3dhcFJvdXRlciA9IFBvb2xTd2FwVGVzdCgweDAxKTsKCiAgICAvLyBzbGlwcGFnZSB0b2xlcmFuY2UgdG8gYWxsb3cgZm9yIHVubGltaXRlZCBwcmljZSBpbXBhY3QKICAgIHVpbnQxNjAgcHVibGljIGNvbnN0YW50IE1JTl9QUklDRV9MSU1JVCA9IFRpY2tNYXRoLk1JTl9TUVJUX1JBVElPICsgMTsKICAgIHVpbnQxNjAgcHVibGljIGNvbnN0YW50IE1BWF9QUklDRV9MSU1JVCA9IFRpY2tNYXRoLk1BWF9TUVJUX1JBVElPIC0gMTsKCiAgICAvLy8gQG5vdGljZSBTd2FwIHRva2VucwogICAgLy8vIEBwYXJhbSBrZXkgdGhlIHBvb2wgd2hlcmUgdGhlIHN3YXAgaXMgaGFwcGVuaW5nCiAgICAvLy8gQHBhcmFtIGFtb3VudFNwZWNpZmllZCB0aGUgYW1vdW50IG9mIHRva2VucyB0byBzd2FwCiAgICAvLy8gQHBhcmFtIHplcm9Gb3JPbmUgd2hldGhlciB0aGUgc3dhcCBpcyB0b2tlbjAgLT4gdG9rZW4xIG9yIHRva2VuMSAtPiB0b2tlbjAKICAgIC8vLyBAcGFyYW0gaG9va0RhdGEgYW55IGRhdGEgdG8gYmUgcGFzc2VkIHRvIHRoZSBwb29sJ3MgaG9vawogICAgZnVuY3Rpb24gc3dhcChQb29sS2V5IG1lbW9yeSBrZXksIGludDI1NiBhbW91bnRTcGVjaWZpZWQsIGJvb2wgemVyb0Zvck9uZSwgYnl0ZXMgbWVtb3J5IGhvb2tEYXRhKSBpbnRlcm5hbCB7CiAgICAgICAgSVBvb2xNYW5hZ2VyLlN3YXBQYXJhbXMgbWVtb3J5IHBhcmFtcyA9IElQb29sTWFuYWdlci5Td2FwUGFyYW1zKHsKICAgICAgICAgICAgemVyb0Zvck9uZTogemVyb0Zvck9uZSwKICAgICAgICAgICAgYW1vdW50U3BlY2lmaWVkOiBhbW91bnRTcGVjaWZpZWQsCiAgICAgICAgICAgIHNxcnRQcmljZUxpbWl0WDk2OiB6ZXJvRm9yT25lID8gTUlOX1BSSUNFX0xJTUlUIDogTUFYX1BSSUNFX0xJTUlUIC8vIHVubGltaXRlZCBpbXBhY3QKICAgICAgICB9KTsKCiAgICAgICAgLy8gaW4gdjQsIHVzZXJzIGhhdmUgdGhlIG9wdGlvbiB0byByZWNlaWV2ZSBuYXRpdmUgRVJDMjBzIG9yIHdyYXBwZWQgRVJDMTE1NSB0b2tlbnMKICAgICAgICAvLyBoZXJlLCB3ZSdsbCB0YWtlIHRoZSBFUkMyMHMKICAgICAgICBQb29sU3dhcFRlc3QuVGVzdFNldHRpbmdzIG1lbW9yeSB0ZXN0U2V0dGluZ3MgPQogICAgICAgICAgICBQb29sU3dhcFRlc3QuVGVzdFNldHRpbmdzKHt3aXRoZHJhd1Rva2VuczogdHJ1ZSwgc2V0dGxlVXNpbmdUcmFuc2ZlcjogdHJ1ZX0pOwoKICAgICAgICBzd2FwUm91dGVyLnN3YXAoa2V5LCBwYXJhbXMsIHRlc3RTZXR0aW5ncywgaG9va0RhdGEpOwogICAgfQp9Cg==", + }, + { + fileName: "SwapExampleInputs.sol", + code: "Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yMDsKCmltcG9ydCB7SVBvb2xNYW5hZ2VyfSBmcm9tICJAdW5pc3dhcC92NC1jb3JlL2NvbnRyYWN0cy9pbnRlcmZhY2VzL0lQb29sTWFuYWdlci5zb2wiOwppbXBvcnQge1Bvb2xLZXl9IGZyb20gIkB1bmlzd2FwL3Y0LWNvcmUvY29udHJhY3RzL3R5cGVzL1Bvb2xLZXkuc29sIjsKaW1wb3J0IHtQb29sU3dhcFRlc3R9IGZyb20gIkB1bmlzd2FwL3Y0LWNvcmUvY29udHJhY3RzL3Rlc3QvUG9vbFN3YXBUZXN0LnNvbCI7CmltcG9ydCB7VGlja01hdGh9IGZyb20gIkB1bmlzd2FwL3Y0LWNvcmUvY29udHJhY3RzL2xpYnJhcmllcy9UaWNrTWF0aC5zb2wiOwoKY29udHJhY3QgU3dhcEV4YW1wbGVJbnB1dHMgewogICAgLy8gc2V0IHRoZSByb3V0ZXIgYWRkcmVzcwogICAgUG9vbFN3YXBUZXN0IHN3YXBSb3V0ZXIgPSBQb29sU3dhcFRlc3QoMHgwMSk7CgogICAgLy8gc2xpcHBhZ2UgdG9sZXJhbmNlIHRvIGFsbG93IGZvciB1bmxpbWl0ZWQgcHJpY2UgaW1wYWN0CiAgICB1aW50MTYwIHB1YmxpYyBjb25zdGFudCBNSU5fUFJJQ0VfTElNSVQgPSBUaWNrTWF0aC5NSU5fU1FSVF9SQVRJTyArIDE7CiAgICB1aW50MTYwIHB1YmxpYyBjb25zdGFudCBNQVhfUFJJQ0VfTElNSVQgPSBUaWNrTWF0aC5NQVhfU1FSVF9SQVRJTyAtIDE7CgogICAgZnVuY3Rpb24gZXhhbXBsZUEoKSBpbnRlcm5hbCB7CiAgICAgICAgYWRkcmVzcyB0b2tlbjAgPSBhZGRyZXNzKDB4MTEpOwogICAgICAgIGFkZHJlc3MgdG9rZW4xID0gYWRkcmVzcygweDIyKTsKCiAgICAgICAgLy8gVXNpbmcgYSBob29rbGVzcyBwb29sCiAgICAgICAgUG9vbEtleSBtZW1vcnkgcG9vbCA9IFBvb2xLZXkoewogICAgICAgICAgICBjdXJyZW5jeTA6IEN1cnJlbmN5KHRva2VuMCksCiAgICAgICAgICAgIGN1cnJlbmN5MTogQ3VycmVuY3kodG9rZW4xKSwKICAgICAgICAgICAgZmVlOiAzMDAwLAogICAgICAgICAgICB0aWNrU3BhY2luZzogNjAsCiAgICAgICAgICAgIGhvb2tzOiBJSG9va3MoYWRkcmVzcygweDApKQogICAgICAgIH0pOwoKICAgICAgICAvLyBhcHByb3ZlIHRva2VucyB0byB0aGUgc3dhcCByb3V0ZXIKICAgICAgICBJRVJDMjAodG9rZW4wKS5hcHByb3ZlKGFkZHJlc3Moc3dhcFJvdXRlciksIHR5cGUodWludDI1NikubWF4KTsKICAgICAgICBJRVJDMjAodG9rZW4xKS5hcHByb3ZlKGFkZHJlc3Moc3dhcFJvdXRlciksIHR5cGUodWludDI1NikubWF4KTsKCiAgICAgICAgLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAvLwogICAgICAgIC8vIFN3YXAgMWUxOCB0b2tlbjAgaW50byB0b2tlbjEKICAgICAgICAvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIC8vCiAgICAgICAgYm9vbCB6ZXJvRm9yT25lID0gdHJ1ZTsKICAgICAgICBJUG9vbE1hbmFnZXIuU3dhcFBhcmFtcyBtZW1vcnkgcGFyYW1zID0gSVBvb2xNYW5hZ2VyLlN3YXBQYXJhbXMoewogICAgICAgICAgICB6ZXJvRm9yT25lOiB6ZXJvRm9yT25lLAogICAgICAgICAgICBhbW91bnRTcGVjaWZpZWQ6IDFlMTgsCiAgICAgICAgICAgIHNxcnRQcmljZUxpbWl0WDk2OiB6ZXJvRm9yT25lID8gTUlOX1BSSUNFX0xJTUlUIDogTUFYX1BSSUNFX0xJTUlUIC8vIHVubGltaXRlZCBpbXBhY3QKICAgICAgICB9KTsKCiAgICAgICAgLy8gaW4gdjQsIHVzZXJzIGhhdmUgdGhlIG9wdGlvbiB0byByZWNlaWV2ZSBuYXRpdmUgRVJDMjBzIG9yIHdyYXBwZWQgRVJDMTE1NSB0b2tlbnMKICAgICAgICAvLyBoZXJlLCB3ZSdsbCB0YWtlIHRoZSBFUkMyMHMKICAgICAgICBQb29sU3dhcFRlc3QuVGVzdFNldHRpbmdzIG1lbW9yeSB0ZXN0U2V0dGluZ3MgPQogICAgICAgICAgICBQb29sU3dhcFRlc3QuVGVzdFNldHRpbmdzKHt3aXRoZHJhd1Rva2VuczogdHJ1ZSwgc2V0dGxlVXNpbmdUcmFuc2ZlcjogdHJ1ZX0pOwoKICAgICAgICBieXRlcyBtZW1vcnkgaG9va0RhdGEgPSBuZXcgYnl0ZXMoMCk7IC8vIG5vIGhvb2sgZGF0YSBvbiB0aGUgaG9va2xlc3MgcG9vbAogICAgICAgIHN3YXBSb3V0ZXIuc3dhcChrZXksIHBhcmFtcywgdGVzdFNldHRpbmdzLCBob29rRGF0YSk7CiAgICB9Cn0K", + }, +] + +const html = `
++Expect Uniswap Labs to release an official contract around launch
+
Using the v4-core
provided test router, we can swap on a single pool. These snippets should only be used for non-production, testing purposes
Swapping, in production, will typically use a periphery contract. It is not recommended to directly swap with poolManager.swap
Swapping will require 3 arguments:
+(Note: A quoter contract is unavailable at this time)
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
+import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
+import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol";
+import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
+
+contract Swap {
+ // set the router address
+ PoolSwapTest swapRouter = PoolSwapTest(0x01);
+
+ // slippage tolerance to allow for unlimited price impact
+ uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1;
+ uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1;
+
+ /// @notice Swap tokens
+ /// @param key the pool where the swap is happening
+ /// @param amountSpecified the amount of tokens to swap
+ /// @param zeroForOne whether the swap is token0 -> token1 or token1 -> token0
+ /// @param hookData any data to be passed to the pool's hook
+ function swap(PoolKey memory key, int256 amountSpecified, bool zeroForOne, bytes memory hookData) internal {
+ IPoolManager.SwapParams memory params = IPoolManager.SwapParams({
+ zeroForOne: zeroForOne,
+ amountSpecified: amountSpecified,
+ sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT // unlimited impact
+ });
+
+ // in v4, users have the option to receieve native ERC20s or wrapped ERC1155 tokens
+ // here, we'll take the ERC20s
+ PoolSwapTest.TestSettings memory testSettings =
+ PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true});
+
+ swapRouter.swap(key, params, testSettings, hookData);
+ }
+}
+
// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
+import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
+import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol";
+import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
+
+contract SwapExampleInputs {
+ // set the router address
+ PoolSwapTest swapRouter = PoolSwapTest(0x01);
+
+ // slippage tolerance to allow for unlimited price impact
+ uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1;
+ uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1;
+
+ function exampleA() internal {
+ address token0 = address(0x11);
+ address token1 = address(0x22);
+
+ // Using a hookless pool
+ PoolKey memory pool = PoolKey({
+ currency0: Currency(token0),
+ currency1: Currency(token1),
+ fee: 3000,
+ tickSpacing: 60,
+ hooks: IHooks(address(0x0))
+ });
+
+ // approve tokens to the swap router
+ IERC20(token0).approve(address(swapRouter), type(uint256).max);
+ IERC20(token1).approve(address(swapRouter), type(uint256).max);
+
+ // ---------------------------- //
+ // Swap 1e18 token0 into token1
+ // ---------------------------- //
+ bool zeroForOne = true;
+ IPoolManager.SwapParams memory params = IPoolManager.SwapParams({
+ zeroForOne: zeroForOne,
+ amountSpecified: 1e18,
+ sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT // unlimited impact
+ });
+
+ // in v4, users have the option to receieve native ERC20s or wrapped ERC1155 tokens
+ // here, we'll take the ERC20s
+ PoolSwapTest.TestSettings memory testSettings =
+ PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true});
+
+ bytes memory hookData = new bytes(0); // no hook data on the hookless pool
+ swapRouter.swap(key, params, testSettings, hookData);
+ }
+}
+
`
+
+export default html
diff --git a/src/pages/swap/index.md b/src/pages/swap/index.md
new file mode 100644
index 00000000..1819ef82
--- /dev/null
+++ b/src/pages/swap/index.md
@@ -0,0 +1,30 @@
+---
+title: Single Swap
+version: 0.8.20
+description: Swapping on a single pool
+keywords: [swap, trade, swapping]
+---
+
+> Expect Uniswap Labs to release an official contract around launch
+
+Using the `v4-core` provided *test* router, we can swap on a single pool. These snippets should only be used for non-production, testing purposes
+
+Swapping, in production, will typically use a periphery contract. It is **not** recommended to directly swap with `poolManager.swap`
+
+Swapping involves 3 primary arguments:
+
+* Which pool/hook to swap on
+* The direction of the swap, token0 -> token1 or token1 -> token0
+* The input token amount
+
+(Note: A quoter contract is unavailable at this time)
+
+```solidity
+{{{Swap}}}
+```
+
+### Examples of Swapping on a V4 Pool
+
+```solidity
+{{{SwapExampleInputs}}}
+```
diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx
new file mode 100644
index 00000000..e44c294e
--- /dev/null
+++ b/src/pages/swap/index.tsx
@@ -0,0 +1,29 @@
+import React from "react"
+import Example from "../../components/Example"
+import html, { version, title, description, codes } from "./index.html"
+
+interface Path {
+ path: string
+ title: string
+}
+
+interface Props {
+ prev: Path | null
+ next: Path | null
+}
+
+const ExamplePage: React.FC