A minimal Solidity wrapper around Uniswap V2 Router02 that lets users swap ERC-20 tokens through a single
swapTokensentry point.
Swapping is a learning-oriented Foundry project that integrates with an existing Uniswap V2βcompatible router (Router02). Users approve the contract, call swapTokens with a token path and slippage parameters, and receive output tokens directly in their wallet. The contract pulls input tokens via OpenZeppelin SafeERC20, approves the router, and delegates the swap to swapExactTokensForTokens.
Key features:
- π Single
swapTokens()function wrappingswapExactTokensForTokens - π§© Minimal
IV2Router02interface β no full router dependency in source - π‘οΈ SafeERC20 for
transferFromandapprove - π£
SwapTokensevent with input/output token addresses and amounts - π§ͺ Foundry test suite with optional Arbitrum mainnet fork
- π Configurable router address set at deploy time
- Prerequisites & Dependencies
- Technologies & Versions
- Project Structure
- Quick Start
- Testing the Contract
- Architecture
- Security Policy
- Scripts & Commands
- Versioning
- License
- About the Author
| Requirement | Notes |
|---|---|
| π₯οΈ OS | macOS, Linux, or Windows |
| π§ Git | Required for cloning and submodules |
| βοΈ Foundry | forge, cast, and anvil for build and test |
| π RPC URL (optional) | Arbitrum (or target chain) endpoint for fork tests |
Quick minimum: Foundry installed and Solidity compiler 0.8.35.
curl -L https://foundry.paradigm.xyz | bash
foundryupVerify:
forge --version
cast --version| Dependency | Role |
|---|---|
| forge-std | Foundry testing utilities and cheatcodes |
| OpenZeppelin Contracts | IERC20 and SafeERC20 for safe token transfers |
After cloning, install submodules if needed:
git clone https://github.com/noelialuz/Swapping.git
cd Swapping
forge install| Technology | Version | Role |
|---|---|---|
| Solidity | 0.8.35 |
Smart contract language |
| Foundry | latest (foundryup) |
Build, test, and CLI interaction |
| OpenZeppelin Contracts | vendored in lib/ |
ERC-20 interfaces and SafeERC20 |
| Uniswap V2 Router02 | external (on-chain) | DEX router for token swaps |
| EVM | β | Execution environment (Ethereum-compatible chains) |
| SPDX | MIT |
License identifier in source |
Swapping/
βββ foundry.toml # Foundry configuration
βββ README.md # Project documentation
βββ lib/
β βββ forge-std/ # Foundry standard library
β βββ openzeppelin-contracts/ # OpenZeppelin (IERC20, SafeERC20)
βββ src/
β βββ SwapApp.sol # Main swap wrapper contract
β βββ interfaces/
β βββ IV2Router02.sol # Minimal Router02 interface
βββ test/
β βββ SwapApp.t.sol # Unit and fork tests
βββ .vscode/ # Editor remappings (optional)This repository is a Foundry-first project. The router itself is not deployed here β you point SwapApp at an existing V2-compatible Router02 address on your target network.
git clone https://github.com/noelialuz/Swapping.git
cd Swapping
forge buildDeploy SwapApp with the Router02 address for your network.
Example β Arbitrum mainnet Router02:
| Field | Value |
|---|---|
| Contract | SwapApp |
V2Router02Address_ |
0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24 |
Using Foundry (local or scripted):
# Example: deploy to a local Anvil node
anvil &
forge create src/SwapApp.sol:SwapApp \
--rpc-url http://127.0.0.1:8545 \
--private-key <DEPLOYER_PRIVATE_KEY> \
--constructor-args 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24Use the correct Router02 address for your chain. The address above is for reference in the included Arbitrum fork tests.
// 1. User approves SwapApp to spend tokenIn.
IERC20(tokenIn).approve(swapAppAddress, amountIn);
// 2. Build path: [tokenIn, tokenOut] (add intermediates for multi-hop).
address[] memory path = new address[](2);
path[0] = tokenIn;
path[1] = tokenOut;
// 3. Call swapTokens with slippage and deadline.
swapApp.swapTokens(amountIn, amountOutMin, path, deadline);
// Output tokens are sent directly to msg.sender by the router.Example β USDT β DAI on Arbitrum (test constants):
| Token | Address |
|---|---|
| USDT | 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9 |
| DAI | 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1 |
Runs deployment smoke test only:
forge test -vvExpected: testHasBeenDeployedCorrectly passes. The swap integration test requires a mainnet fork (see below).
The swap test forks Arbitrum mainnet and uses a funded address with USDT:
forge test -vvvv \
--fork-url https://arb1.arbitrum.io/rpc \
--match-test testSwapTokensCorrectlyOr run the full suite on a fork:
forge test -vv --fork-url https://arb1.arbitrum.io/rpcWhat the fork test verifies:
| Step | Action |
|---|---|
| 1 | Deploy SwapApp with Arbitrum Router02 |
| 2 | Impersonate a USDT holder on Arbitrum |
| 3 | Approve SwapApp and call swapTokens(USDT β DAI) |
| 4 | Assert USDT balance decreased and DAI balance increased |
Note: Fork tests depend on live chain state (token balances, liquidity). If the hard-coded holder address no longer has USDT, update
userintest/SwapApp.t.sol.
After deploying SwapApp on a testnet or fork:
# Approve SwapApp (from user wallet)
cast send $USDT "approve(address,uint256)" $SWAP_APP $AMOUNT_IN --private-key $USER_PK
# Swap USDT for DAI
cast send $SWAP_APP \
"swapTokens(uint256,uint256,address[],uint256)" \
$AMOUNT_IN \
$AMOUNT_OUT_MIN \
"[$USDT,$DAI]" \
$DEADLINE \
--private-key $USER_PK- Copy
src/SwapApp.solandsrc/interfaces/IV2Router02.solinto Remix. - Add OpenZeppelin imports via Remix GitHub import or flatten the contract.
- Compile with Solidity 0.8.35.
- Deploy with your network's Router02 address.
- Approve tokens to the deployed contract, then call
swapTokens.
Swapping consists of a thin wrapper contract that sits between the user and an on-chain DEX router:
flowchart LR
User["π€ User"]
SA["π SwapApp"]
R["π‘ V2 Router02"]
Pool["π§ Uniswap V2 Pools"]
User -->|"approve + swapTokens()"| SA
SA -->|"transferFrom (tokenIn)"| SA
SA -->|"approve + swapExactTokensForTokens"| R
R --> Pool
R -->|"tokenOut to user"| User
| Contract / Interface | Responsibility |
|---|---|
SwapApp |
Pulls input tokens, approves router, executes swap, emits event |
IV2Router02 |
Minimal interface for swapExactTokensForTokens |
| Variable | Visibility | Description |
|---|---|---|
V2Router02Address |
public |
Address of the Uniswap V2βcompatible router |
| Function | Access | Description |
|---|---|---|
swapTokens(uint256 amountIn_, uint256 amountOutMin_, address[] path_, uint256 deadline_) |
external |
Transfers path_[0] from caller, swaps via router, sends output to caller |
event SwapTokens(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut);- Approve β User grants
SwapAppallowance on the input ERC-20. - Transfer in β
SwapApppullsamountIn_frommsg.senderusing SafeERC20. - Router approve β Contract approves the router to spend the input token.
- Swap β Router executes
swapExactTokensForTokens; output goes tomsg.sender. - Event β
SwapTokenslogs token addresses and amounts.
| Parameter | Description |
|---|---|
amountIn_ |
Exact input token amount |
amountOutMin_ |
Minimum acceptable output (slippage protection) |
path_ |
Token addresses from input to output (e.g. [USDT, DAI]) |
deadline_ |
Unix timestamp after which the swap reverts |
β οΈ This project is intended for learning and demonstration purposes only. It has not undergone a professional security audit.
| Area | Detail |
|---|---|
| π Educational scope | Not production-ready; use at your own risk |
| π External router trust | Swaps depend entirely on the configured Router02 and underlying pools |
| πΈ No fee-on-transfer handling | Assumes standard ERC-20 behavior; deflationary/rebasing tokens may break swaps |
| β±οΈ Deadline & slippage | Caller must set sensible deadline_ and amountOutMin_; no defaults enforced |
| π Unlimited approve | Contract approves the router for the full amountIn_ per swap |
| π Network-specific | Router and token addresses differ per chain β verify before deployment |
| π§ͺ Test first | Use fork tests or a testnet before mainnet |
- Review all logic in
src/SwapApp.sol - Run fork tests against your target chain and router
- Consider a professional audit
- Add pausing, access control, or router upgrade patterns if needed
- Validate token paths and liquidity on-chain
If you discover a security issue, please do not open a public GitHub issue. Contact the repository owner directly (see About the Author).
Smart contracts carry inherent technical and financial risk. Use this repository at your own responsibility.
| Command | Description |
|---|---|
forge build |
Compile contracts |
forge test -vv |
Run tests locally |
forge test --fork-url <RPC_URL> |
Run tests against a forked network |
forge test -vvvv --fork-url https://arb1.arbitrum.io/rpc --match-test testSwapTokensCorrectly |
Verbose Arbitrum swap test |
forge create src/SwapApp.sol:SwapApp --constructor-args <ROUTER> |
Deploy via CLI |
anvil |
Start a local Ethereum node |
cast send ... "swapTokens(...)" |
Execute a swap on a deployed contract |
This project follows Semantic Versioning 2.0.0:
| Segment | Meaning |
|---|---|
| MAJOR | Breaking changes to contract interface or behavior |
| MINOR | New features, backward-compatible |
| PATCH | Bug fixes, docs, no breaking API changes |
| Version | Status | Notes |
|---|---|---|
| 0.1.0 | Current | Initial release: SwapApp, IV2Router02, Foundry tests with Arbitrum fork |
Tag releases on GitHub:
git tag -a v0.1.0 -m "Initial SwapApp release with V2 router integration"
git push origin v0.1.0Swapping is released under the MIT License β see the SPDX header in src/SwapApp.sol.
SPDX identifier: // SPDX-License-Identifier: MIT
| Name | Noelia Luz FernΓ‘ndez |
| GitHub | @Noelialuz |
| https://www.linkedin.com/in/noelia-luz-fernandez-03404440/ | |
| noelia_luz_fernandez@hotmail.com |
- Foundry Book β CLI testing, forking, and cheatcodes
- Uniswap V2 documentation β Router02 and swap mechanics
- OpenZeppelin SafeERC20 β safe token transfer patterns
- Solidity documentation β language reference and best practices