From 24ac093e5245befea14c9cfc5da7749b9f7f9061 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Sun, 29 Dec 2024 06:43:02 -0500 Subject: [PATCH] chore: add nonfungible position manager example (#122) Introduce a new example demonstrating minting liquidity with the Uniswap Nonfungible Position Manager. Refactor transaction handling by replacing deprecated `register()` calls with `watch()` for better event monitoring. Adjust `get_erc20_state_overrides` to use provider references for improved API consistency. --- Cargo.toml | 4 + examples/nonfungible_position_manager.rs | 132 +++++++++++++++++++++++ examples/self_permit.rs | 4 +- examples/swap_router.rs | 4 +- src/extensions/state_overrides.rs | 9 +- 5 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 examples/nonfungible_position_manager.rs diff --git a/Cargo.toml b/Cargo.toml index 3be89b7..7af7aa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,3 +72,7 @@ required-features = ["extensions"] [[example]] name = "self_permit" required-features = ["extensions"] + +[[example]] +name = "nonfungible_position_manager" +required-features = ["extensions"] diff --git a/examples/nonfungible_position_manager.rs b/examples/nonfungible_position_manager.rs new file mode 100644 index 0000000..e53c038 --- /dev/null +++ b/examples/nonfungible_position_manager.rs @@ -0,0 +1,132 @@ +use alloy::{ + eips::BlockId, + providers::{ext::AnvilApi, Provider, ProviderBuilder}, + rpc::types::TransactionRequest, + transports::{http::reqwest::Url, Transport}, +}; +use alloy_primitives::{address, Address, U256}; +use uniswap_lens::bindings::ierc721enumerable::IERC721Enumerable; +use uniswap_sdk_core::{prelude::*, token}; +use uniswap_v3_sdk::prelude::*; + +#[tokio::main] +async fn main() { + dotenv::dotenv().ok(); + let rpc_url: Url = std::env::var("MAINNET_RPC_URL").unwrap().parse().unwrap(); + let block_id = BlockId::from(17000000); + let wbtc = token!( + 1, + address!("2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"), + 8, + "WBTC" + ); + let weth = WETH9::on_chain(1).unwrap(); + let npm = *NONFUNGIBLE_POSITION_MANAGER_ADDRESSES.get(&1).unwrap(); + + // Create an Anvil fork + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .on_anvil_with_config(|anvil| { + anvil + .fork(rpc_url) + .fork_block_number(block_id.as_u64().unwrap()) + }); + let account = provider.get_accounts().await.unwrap()[0]; + + let pool = Pool::from_pool_key( + 1, + FACTORY_ADDRESS, + wbtc.address(), + weth.address(), + FeeAmount::LOW, + provider.clone(), + None, + ) + .await + .unwrap(); + let mut position = Position::new( + pool.clone(), + pool.liquidity, + nearest_usable_tick(pool.tick_current - pool.tick_spacing(), pool.tick_spacing()), + nearest_usable_tick(pool.tick_current + pool.tick_spacing(), pool.tick_spacing()), + ); + + // Set the state of the account to allow the position to be minted + let MintAmounts { amount0, amount1 } = position.mint_amounts().unwrap(); + let mut overrides = get_erc20_state_overrides( + position.pool.token0.address(), + account, + npm, + amount0, + &provider, + ) + .await + .unwrap(); + overrides.extend( + get_erc20_state_overrides( + position.pool.token1.address(), + account, + npm, + amount1, + &provider, + ) + .await + .unwrap(), + ); + for (token, account_override) in overrides { + for (slot, value) in account_override.state_diff.unwrap() { + provider + .anvil_set_storage_at(token, U256::from_be_bytes(slot.0), value) + .await + .unwrap(); + } + } + + let minted_position = mint_liquidity(&mut position, account, &provider).await; + + assert_eq!(minted_position.liquidity, position.liquidity); + assert_eq!(minted_position.tick_lower, position.tick_lower); + assert_eq!(minted_position.tick_upper, position.tick_upper); +} + +async fn mint_liquidity(position: &mut Position, from: Address, provider: &P) -> Position +where + T: Transport + Clone, + P: Provider, +{ + let npm = *NONFUNGIBLE_POSITION_MANAGER_ADDRESSES.get(&1).unwrap(); + + let options = AddLiquidityOptions { + slippage_tolerance: Percent::default(), + deadline: U256::MAX, + use_native: None, + token0_permit: None, + token1_permit: None, + specific_opts: AddLiquiditySpecificOptions::Mint(MintSpecificOptions { + recipient: from, + create_pool: false, + }), + }; + let params = add_call_parameters(position, options).unwrap(); + let tx = TransactionRequest::default() + .from(from) + .to(npm) + .input(params.calldata.into()); + provider + .send_transaction(tx) + .await + .unwrap() + .watch() + .await + .unwrap(); + + let token_id = IERC721Enumerable::new(npm, provider) + .tokenOfOwnerByIndex(from, U256::ZERO) + .call() + .await + .unwrap() + ._0; + Position::from_token_id(1, npm, token_id, provider, None) + .await + .unwrap() +} diff --git a/examples/self_permit.rs b/examples/self_permit.rs index d461181..abd521b 100644 --- a/examples/self_permit.rs +++ b/examples/self_permit.rs @@ -93,9 +93,7 @@ async fn main() { .send_transaction(tx) .await .unwrap() - .register() - .await - .unwrap() + .watch() .await .unwrap(); diff --git a/examples/swap_router.rs b/examples/swap_router.rs index 064c799..e4be5fc 100644 --- a/examples/swap_router.rs +++ b/examples/swap_router.rs @@ -95,9 +95,7 @@ async fn main() { .send_transaction(tx) .await .unwrap() - .register() - .await - .unwrap() + .watch() .await .unwrap(); diff --git a/src/extensions/state_overrides.rs b/src/extensions/state_overrides.rs index 2bb58b1..adb91a2 100644 --- a/src/extensions/state_overrides.rs +++ b/src/extensions/state_overrides.rs @@ -21,7 +21,7 @@ pub async fn get_erc20_state_overrides( owner: Address, spender: Address, amount: U256, - provider: P, + provider: &P, ) -> Result where T: Transport + Clone, @@ -90,10 +90,9 @@ mod tests { let owner = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"); let npm = *NONFUNGIBLE_POSITION_MANAGER_ADDRESSES.get(&1).unwrap(); let amount = U256::from(1_000_000); - let overrides = - get_erc20_state_overrides(USDC.address(), owner, npm, amount, provider.clone()) - .await - .unwrap(); + let overrides = get_erc20_state_overrides(USDC.address(), owner, npm, amount, &provider) + .await + .unwrap(); let usdc = IERC20::new(USDC.address(), provider); let balance = usdc .balanceOf(owner)