-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support for creating a Uniswap V3 liquidity pool (#5)
- Loading branch information
1 parent
9d1de43
commit ac3fafc
Showing
11 changed files
with
314 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,5 +40,6 @@ | |
# Environment Configurations | ||
**/env/ | ||
**/.env/ | ||
**/.env | ||
**/.env.local/ | ||
**/.env.test/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,10 @@ | |
|
||
## Unreleased | ||
|
||
### Added | ||
|
||
- Added `uniswap_v3_create_pool` action. | ||
|
||
## [0.0.1] - 2024-11-04 | ||
|
||
### Added | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
130 changes: 130 additions & 0 deletions
130
cdp-agentkit-core/cdp_agentkit_core/actions/uniswap_v3/constants.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
UNISWAP_V3_FACTORY_ABI = [ | ||
{"inputs": [], "stateMutability": "nonpayable", "type": "constructor"}, | ||
{ | ||
"anonymous": False, | ||
"inputs": [ | ||
{"indexed": True, "internalType": "uint24", "name": "fee", "type": "uint24"}, | ||
{"indexed": True, "internalType": "int24", "name": "tickSpacing", "type": "int24"}, | ||
], | ||
"name": "FeeAmountEnabled", | ||
"type": "event", | ||
}, | ||
{ | ||
"anonymous": False, | ||
"inputs": [ | ||
{"indexed": True, "internalType": "address", "name": "oldOwner", "type": "address"}, | ||
{"indexed": True, "internalType": "address", "name": "newOwner", "type": "address"}, | ||
], | ||
"name": "OwnerChanged", | ||
"type": "event", | ||
}, | ||
{ | ||
"anonymous": False, | ||
"inputs": [ | ||
{"indexed": True, "internalType": "address", "name": "token0", "type": "address"}, | ||
{"indexed": True, "internalType": "address", "name": "token1", "type": "address"}, | ||
{"indexed": True, "internalType": "uint24", "name": "fee", "type": "uint24"}, | ||
{"indexed": False, "internalType": "int24", "name": "tickSpacing", "type": "int24"}, | ||
{"indexed": False, "internalType": "address", "name": "pool", "type": "address"}, | ||
], | ||
"name": "PoolCreated", | ||
"type": "event", | ||
}, | ||
{ | ||
"inputs": [ | ||
{"internalType": "address", "name": "tokenA", "type": "address"}, | ||
{"internalType": "address", "name": "tokenB", "type": "address"}, | ||
{"internalType": "uint24", "name": "fee", "type": "uint24"}, | ||
], | ||
"name": "createPool", | ||
"outputs": [{"internalType": "address", "name": "pool", "type": "address"}], | ||
"stateMutability": "nonpayable", | ||
"type": "function", | ||
}, | ||
{ | ||
"inputs": [ | ||
{"internalType": "uint24", "name": "fee", "type": "uint24"}, | ||
{"internalType": "int24", "name": "tickSpacing", "type": "int24"}, | ||
], | ||
"name": "enableFeeAmount", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function", | ||
}, | ||
{ | ||
"inputs": [{"internalType": "uint24", "name": "", "type": "uint24"}], | ||
"name": "feeAmountTickSpacing", | ||
"outputs": [{"internalType": "int24", "name": "", "type": "int24"}], | ||
"stateMutability": "view", | ||
"type": "function", | ||
}, | ||
{ | ||
"inputs": [ | ||
{"internalType": "address", "name": "", "type": "address"}, | ||
{"internalType": "address", "name": "", "type": "address"}, | ||
{"internalType": "uint24", "name": "", "type": "uint24"}, | ||
], | ||
"name": "getPool", | ||
"outputs": [{"internalType": "address", "name": "", "type": "address"}], | ||
"stateMutability": "view", | ||
"type": "function", | ||
}, | ||
{ | ||
"inputs": [], | ||
"name": "owner", | ||
"outputs": [{"internalType": "address", "name": "", "type": "address"}], | ||
"stateMutability": "view", | ||
"type": "function", | ||
}, | ||
{ | ||
"inputs": [], | ||
"name": "parameters", | ||
"outputs": [ | ||
{"internalType": "address", "name": "factory", "type": "address"}, | ||
{"internalType": "address", "name": "token0", "type": "address"}, | ||
{"internalType": "address", "name": "token1", "type": "address"}, | ||
{"internalType": "uint24", "name": "fee", "type": "uint24"}, | ||
{"internalType": "int24", "name": "tickSpacing", "type": "int24"}, | ||
], | ||
"stateMutability": "view", | ||
"type": "function", | ||
}, | ||
{ | ||
"inputs": [{"internalType": "address", "name": "_owner", "type": "address"}], | ||
"name": "setOwner", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function", | ||
}, | ||
] | ||
|
||
UNISWAP_V3_FACTORY_CONTRACT_ADDRESSES = { | ||
"base-sepolia": "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24", | ||
"base-mainnet": "0x33128a8fC17869897dcE68Ed026d694621f6FDfD", | ||
"ethereum-mainnet": "0x1F98431c8aD98523631AE4a59f267346ea31F984", | ||
"arbitrum-mainnet": "0x1F98431c8aD98523631AE4a59f267346ea31F984", | ||
"polygon-mainnet": "0x1F98431c8aD98523631AE4a59f267346ea31F984", | ||
} | ||
|
||
|
||
def get_contract_address(network: str) -> str: | ||
"""Get the Uniswap V3 Factory contract address for the specified network. | ||
Args: | ||
network (str): The network ID to get the contract address for. | ||
Valid networks are: base-sepolia, base-mainnet, ethereum-mainnet, | ||
arbitrum-mainnet, polygon-mainnet. | ||
Returns: | ||
str: The contract address for the specified network. | ||
Raises: | ||
ValueError: If the specified network is not supported. | ||
""" | ||
network = network.lower() | ||
if network not in UNISWAP_V3_FACTORY_CONTRACT_ADDRESSES: | ||
raise ValueError( | ||
f"Invalid network: {network}. Valid networks are: {', '.join(UNISWAP_V3_FACTORY_CONTRACT_ADDRESSES.keys())}" | ||
) | ||
return UNISWAP_V3_FACTORY_CONTRACT_ADDRESSES[network] |
55 changes: 55 additions & 0 deletions
55
cdp-agentkit-core/cdp_agentkit_core/actions/uniswap_v3/create_pool.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from cdp import Wallet | ||
from pydantic import BaseModel, Field | ||
|
||
from cdp_agentkit_core.actions.uniswap_v3.constants import ( | ||
UNISWAP_V3_FACTORY_ABI, | ||
get_contract_address, | ||
) | ||
|
||
UNISWAP_V3_CREATE_POOL_PROMPT = """ | ||
This tool will create a Uniswap v3 pool for trading 2 tokens, one of which can be the native gas token. For native gas token, use the address 0x4200000000000000000000000000000000000006, and for ERC20 token, use its contract address. This tool takes the address of the first token, address of the second token, and the fee to charge for trades as inputs. The fee is denominated in hundredths of a bip (i.e. 1e-6) and must be passed a string. Acceptable fee values are 100, 500, 3000, and 10000. Supported networks are Base Sepolia, Base Mainnet, Ethereum Mainnet, Polygon Mainnet, and Arbitrum Mainnet.""" | ||
|
||
|
||
class UniswapV3CreatePoolInput(BaseModel): | ||
"""Input argument schema for create pool action.""" | ||
|
||
token_a: str = Field( | ||
..., | ||
description="The address of the first token to trade, e.g. 0x4200000000000000000000000000000000000006 for native gas token", | ||
) | ||
token_b: str = Field( | ||
..., | ||
description="The address of the second token to trade, e.g. 0x1234567890123456789012345678901234567890 for ERC20 token", | ||
) | ||
fee: str = Field( | ||
..., | ||
description="The fee to charge for trades, denominated in hundredths of a bip (i.e. 1e-6). Acceptable fee values are 100, 500, 3000, and 10000.", | ||
) | ||
|
||
|
||
def uniswap_v3_create_pool(wallet: Wallet, token_a: str, token_b: str, fee: str) -> str: | ||
"""Create a Uniswap v3 pool for trading 2 tokens, one of which can be the native gas token. | ||
Args: | ||
wallet (Wallet): The wallet to create the pool from. | ||
token_a (str): The address of the first token to trade, e.g. 0x4200000000000000000000000000000000000006 for native gas token | ||
token_b (str): The address of the second token to trade, e.g. 0x1234567890123456789012345678901234567890 for ERC20 token | ||
fee (str): The fee to charge for trades, denominated in hundredths of a bip (i.e. 1e-6). | ||
Returns: | ||
str: A message containing the pool creation details. | ||
""" | ||
factory_address = get_contract_address(wallet.network_id) | ||
|
||
pool = wallet.invoke_contract( | ||
contract_address=factory_address, | ||
method="createPool", | ||
abi=UNISWAP_V3_FACTORY_ABI, | ||
args={ | ||
"tokenA": token_a, | ||
"tokenB": token_b, | ||
"fee": fee, | ||
}, | ||
).wait() | ||
return f"Created pool for {token_a} and {token_b} with fee {fee} on network {wallet.network_id}.\nTransaction hash for the pool creation: {pool.transaction.transaction_hash}\nTransaction link for the pool creation: {pool.transaction.transaction_link}" |
Empty file.
85 changes: 85 additions & 0 deletions
85
cdp-agentkit-core/tests/actions/uniswap_v3/test_create_pool.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from unittest.mock import patch | ||
|
||
import pytest | ||
|
||
from cdp_agentkit_core.actions.uniswap_v3.constants import UNISWAP_V3_FACTORY_ABI | ||
from cdp_agentkit_core.actions.uniswap_v3.create_pool import ( | ||
UniswapV3CreatePoolInput, | ||
uniswap_v3_create_pool, | ||
) | ||
|
||
MOCK_TOKEN_A = "0x4200000000000000000000000000000000000006" | ||
MOCK_TOKEN_B = "0x1234567890123456789012345678901234567890" | ||
MOCK_FEE = "3000" | ||
|
||
|
||
def test_create_pool_input_model_valid(): | ||
"""Test that CreatePoolInput accepts valid parameters.""" | ||
input_model = UniswapV3CreatePoolInput( | ||
token_a=MOCK_TOKEN_A, | ||
token_b=MOCK_TOKEN_B, | ||
fee=MOCK_FEE, | ||
) | ||
|
||
assert input_model.token_a == MOCK_TOKEN_A | ||
assert input_model.token_b == MOCK_TOKEN_B | ||
assert input_model.fee == MOCK_FEE | ||
|
||
|
||
def test_create_pool_input_model_missing_params(): | ||
"""Test that CreatePoolInput raises error when params are missing.""" | ||
with pytest.raises(ValueError): | ||
UniswapV3CreatePoolInput() | ||
|
||
|
||
def test_create_pool_success(wallet_factory, contract_invocation_factory): | ||
"""Test successful pool creation with valid parameters.""" | ||
mock_wallet = wallet_factory() | ||
mock_contract_instance = contract_invocation_factory() | ||
|
||
with ( | ||
patch.object( | ||
mock_wallet, "invoke_contract", return_value=mock_contract_instance | ||
) as mock_invoke, | ||
patch.object( | ||
mock_contract_instance, "wait", return_value=mock_contract_instance | ||
) as mock_contract_wait, | ||
): | ||
action_response = uniswap_v3_create_pool(mock_wallet, MOCK_TOKEN_A, MOCK_TOKEN_B, MOCK_FEE) | ||
|
||
expected_response = f"Created pool for {MOCK_TOKEN_A} and {MOCK_TOKEN_B} with fee {MOCK_FEE} on network {mock_wallet.network_id}.\nTransaction hash for the pool creation: {mock_contract_instance.transaction.transaction_hash}\nTransaction link for the pool creation: {mock_contract_instance.transaction.transaction_link}" | ||
assert action_response == expected_response | ||
|
||
mock_invoke.assert_called_once_with( | ||
contract_address="0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24", | ||
method="createPool", | ||
abi=UNISWAP_V3_FACTORY_ABI, | ||
args={ | ||
"tokenA": MOCK_TOKEN_A, | ||
"tokenB": MOCK_TOKEN_B, | ||
"fee": MOCK_FEE, | ||
}, | ||
) | ||
mock_contract_wait.assert_called_once_with() | ||
|
||
|
||
def test_create_pool_api_error(wallet_factory): | ||
"""Test create_pool when API error occurs.""" | ||
mock_wallet = wallet_factory() | ||
|
||
with patch.object( | ||
mock_wallet, "invoke_contract", side_effect=Exception("API error") | ||
) as mock_invoke: | ||
with pytest.raises(Exception, match="API error"): | ||
uniswap_v3_create_pool(mock_wallet, MOCK_TOKEN_A, MOCK_TOKEN_B, MOCK_FEE) | ||
|
||
mock_invoke.assert_called_once_with( | ||
contract_address="0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24", | ||
method="createPool", | ||
abi=UNISWAP_V3_FACTORY_ABI, | ||
args={ | ||
"tokenA": MOCK_TOKEN_A, | ||
"tokenB": MOCK_TOKEN_B, | ||
"fee": MOCK_FEE, | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters