This project implements a JSON-RPC 2.0 server for interacting with Uniswap V4 pools. It provides endpoints for swapping tokens, adding and removing liquidity, and other pool-related operations.
uniswap-v4-rpc/
│
├── cmd/
│ └── main.go
├── test/
├── uniswap-cli/
├── contracts/v4-hook/
│ ├── .forge-snapshots/
│ ├── broadcast/
│ ├── cache/
│ ├── lib/
│ ├── out/
│ ├── script/
│ ├── src/
│ │ └── Counter.sol
│ └── test/
│ ├── utils/
│ └── Counter.t.sol
├── .env
├── .gitignore
├── foundry.toml
├── README.md
├── remappings.txt
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── ethereum/
│ │ ├── client.go
│ │ ├── contracts.go
│ │ └── erc20.go
│ ├── handlers/
│ │ ├── addLiquidity.go
│ │ ├── addLiquidityPermit.go
│ │ ├── approveTokens.go
│ │ ├── initialize.go
│ │ ├── swap.go
│ │ └── swapPermit.go
│ ├── routes/
│ │ └── routes.go
│ └── pkg/utils/
│ └── ethereum_utils.go
├── test/integration/
│ ├── addresses_check_test.go
│ ├── config.yaml
│ ├── liquidity_test.go
│ ├── setup_test.go
│ └── swap_test.go
├── .env
├── .gitignore
├── config.yaml
├── go.mod
├── go.sum
├── main.go
└── README.md
foundryup
requires foundry
forge install
forge test --via-ir
- Clone the repository:
- Run
Anvil
to spin up a local blockchain - Compile Contracts and Navigate to the contracts directory and run (this runs the deploy script)
# start anvil, a local EVM chain
anvil
# in a new terminal
forge script script/Anvil.s.sol \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast
- Read the logs from the deploy script and copy them over to the config.yaml file
- Run
go run main.go
in the root Directory to start the golang rpc server
The config.yaml
file contains all necessary configuration for the server(Addresses will have to be manually set using the values from the deploy script):
# Ethereum Network Configuration
ethereum_node_url: "http://localhost:8545"
chain_id: 31337 # Local development chain ID
##### Contract Addresses
swap_router_address: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
lp_router_address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
manager_address: "0x5FbDB2315678afecb367f032d93F642f64180aa3"
hook_address: "0xA4B10483554041f45fe0E481B6Adc26b17eA0aC0"
###### Account Configuration
private_key: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
###### API Server Configuration
server_host: "localhost"
server_port: 8080
###### Gas Configuration
gas_limit: 500000
gas_price: 20 # 20 Gwei
###### Token Addresses
token0_address: "0x0165878A594ca255338adfa4d48449f69242Eb8F"
token1_address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707"
###### Pool Configuration
default_fee: 3000
default_tick_spacing: 60
###### Logging
log_level: "debug"
###### Environment
environment: "development"`
go run cmd/server/main.go
For more details please check: internal/routes/routes.go
curl -X POST http://localhost:8080/approve \
-H "Content-Type: application/json" \
-d '{
"currency0": "0xYourCurrency0Address",
"currency1": "0xYourCurrency1Address"
}'
curl -X POST http://localhost:8080/initialize \
-H "Content-Type: application/json" \
-d '{
"currency0": "0xYourCurrency0Address",
"currency1": "0xYourCurrency1Address"
}'
curl -X POST http://localhost:8080/addLiquidity \
-H "Content-Type: application/json" \
-d '{
"currency0": "0xYourCurrency0Address",
"currency1": "0xYourCurrency1Address"
}'
curl -X POST http://localhost:8080/performSwap \
-H "Content-Type: application/json" \
-d '{
"currency0": "0xYourCurrency0Address",
"currency1": "0xYourCurrency1Address",
"amount": "1000000000000000000",
"zeroForOne": true
}'
curl -X POST http://localhost:8080/performSwapWithPermit \
-H "Content-Type: application/json" \
-d '{
"currency0": "0xYourTokenAddress0",
"currency1": "0xYourTokenAddress1",
"amount": "1000000000000000000",
"zeroForOne": true,
"userAddress": "0xYourEthereumAddress",
"privateKey": "0xYourPrivateKey"
}'
curl -X POST http://localhost:8080/addLiquidityPermit \
-H "Content-Type: application/json" \
-d '{
"currency0": "0xYourCurrency0Address",
"currency1": "0xYourCurrency1Address",
"amount": "1000000000000000000",
"zeroForOne": true
}'
The project includes a CLI tool for interacting with the JSON-RPC server. To build the CLI tool:
go build -o uniswap-cli cmd/main.go
Update the cmd/main.go file with the correct addresses.
Example Usage (NB: initalize->approve->AddLiquidity->swap)
./uniswap-cli approve -currency0 0x1234... -currency1 0x5678...
./uniswap-cli swap -currency0 0x1234... -currency1 0x5678... -amount 1000000000000000000 -zeroForOne=true
./uniswap-cli addLiquidity -currency0 0x1234... -currency1 0x5678... -amount 1000000000000000000 -zeroForOne=true
The project includes integration tests that interact with a local Ethereum blockchain Anvil.
To run the tests:
- Ensure your local Ethereum blockkchain testnet is running (
Anvil
). - Update
config.yaml
in the test/integration folder with the contract details - Run the golang tests:
go test -v ./test/integration/...
- Run Foundry Tests
forge test
The tests cover:
Server:
- Address check (
address_check_test.go
) - Swapping tokens (
swap_test.go
) - Adding liquidity (
liquidity_test.go
) // Can remove liquidity if you update the value - Setup operations (
setup_test.go
)
Contracts:
- (
Counter.t.sol
) Checks for correct ERC-2612 simplementation as well as simple hook functionality.
- withPermit functions utilize ERC-2612 to have their approvals set on chain. The user technically does not need to have any eth to pay for gas as the server submits the tx on chain. Permit routes could easily be modified to just accept signatures instead of the private key but for the sake of testing I have used pk as an argument.
I reccomend using tokenC
tokenD
from the deploy script in the test/integration/config.yaml
file as this contract will be seeded.
Example PrivateKey and address for permit Routes:
address - 0x328809Bc894f92807417D2dAD6b7C998c1aFdac6
private key (0x removed) - 9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501