From 1c4ce167ce9dab818a8bb4294fee4214e8570a25 Mon Sep 17 00:00:00 2001 From: loothero Date: Thu, 19 Feb 2026 05:33:41 -0800 Subject: [PATCH] feat(tokenomics): transfer LP position NFT to liquidity owner The StreamComponent creates an Ekubo LP position during provide_initial_liquidity() but permanently locks the position NFT inside the stream token contract. This adds a liquidity_owner field to LiquidityConfig and transfers the LP position NFT to that address immediately after minting, allowing the owner to manage or withdraw liquidity. - Add liquidity_owner to LiquidityConfig struct and IStreamToken trait - Transfer LP NFT via IERC721 after position creation - Emit LiquidityPositionTransferred event - Validate liquidity_owner is non-zero in initializer - Update deploy script, example configs, and all test sites Co-Authored-By: Claude Opus 4.6 --- packages/economy/src/tokenomics/AGENTS.md | 1 + .../economy/src/tokenomics/constants.cairo | 1 + .../src/tokenomics/stream/stream.cairo | 28 ++++++++++ .../tokenomics/tests/helpers/deployment.cairo | 2 + .../src/tokenomics/tests/test_factory.cairo | 6 +++ .../tokenomics/tests/test_factory_fork.cairo | 2 + .../src/tokenomics/tests/test_premint.cairo | 1 + .../src/tokenomics/tests/test_stream.cairo | 1 + .../tokenomics/tests/test_stream_fork.cairo | 2 + .../tests/test_stream_lifecycle.cairo | 52 +++++++++++++++++++ .../interfaces/src/tokenomics/stream.cairo | 5 ++ .../presets/src/tests/test_stream_token.cairo | 2 + scripts/create_stream_token.sh | 14 ++++- scripts/examples/summit_attack_potion.json | 28 ++++++++++ scripts/examples/summit_poison_potion.json | 28 ++++++++++ scripts/examples/summit_revive_potion.json | 28 ++++++++++ scripts/examples/summit_xlife_potion.json | 28 ++++++++++ 17 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 scripts/examples/summit_attack_potion.json create mode 100644 scripts/examples/summit_poison_potion.json create mode 100644 scripts/examples/summit_revive_potion.json create mode 100644 scripts/examples/summit_xlife_potion.json diff --git a/packages/economy/src/tokenomics/AGENTS.md b/packages/economy/src/tokenomics/AGENTS.md index 31f0f1ab..eb00fde6 100644 --- a/packages/economy/src/tokenomics/AGENTS.md +++ b/packages/economy/src/tokenomics/AGENTS.md @@ -106,6 +106,7 @@ pub struct LiquidityConfig { pub stream_token_amount: u128, pub paired_token_amount: u128, pub min_liquidity: u128, + pub liquidity_owner: ContractAddress, } pub struct CreateTokenParams { diff --git a/packages/economy/src/tokenomics/constants.cairo b/packages/economy/src/tokenomics/constants.cairo index 8974abdf..fd54281a 100644 --- a/packages/economy/src/tokenomics/constants.cairo +++ b/packages/economy/src/tokenomics/constants.cairo @@ -56,6 +56,7 @@ pub mod Errors { pub const STREAM_DISTRIBUTIONS_NOT_STARTED: felt252 = 'Distributions not started'; pub const STREAM_INVALID_ORDER_INDEX: felt252 = 'Invalid order index'; pub const STREAM_POSITION_NOT_FOUND: felt252 = 'Position not found'; + pub const STREAM_INVALID_LIQUIDITY_OWNER: felt252 = 'Invalid liquidity owner'; pub const STREAM_INVALID_PREMINT_RECIPIENT: felt252 = 'Invalid premint recipient'; pub const STREAM_INVALID_PREMINT_AMOUNT: felt252 = 'Invalid premint amount'; diff --git a/packages/economy/src/tokenomics/stream/stream.cairo b/packages/economy/src/tokenomics/stream/stream.cairo index 3cf3631d..40451fea 100644 --- a/packages/economy/src/tokenomics/stream/stream.cairo +++ b/packages/economy/src/tokenomics/stream/stream.cairo @@ -16,6 +16,7 @@ pub mod StreamComponent { use core::poseidon::PoseidonTrait; use ekubo::interfaces::core::{ICoreDispatcher, ICoreDispatcherTrait}; use ekubo::interfaces::erc20::IERC20Dispatcher as EkuboIERC20Dispatcher; + use ekubo::interfaces::erc721::{IERC721Dispatcher, IERC721DispatcherTrait}; use ekubo::interfaces::extensions::twamm::OrderKey; use ekubo::interfaces::positions::{IPositionsDispatcher, IPositionsDispatcherTrait}; use ekubo::lens::token_registry::{ITokenRegistryDispatcher, ITokenRegistryDispatcherTrait}; @@ -55,6 +56,8 @@ pub mod StreamComponent { Stream_primary_min_liquidity: u128, /// Liquidity position ID Stream_liquidity_position_id: u64, + /// Liquidity owner (receives LP position NFT) + Stream_liquidity_owner: ContractAddress, /// Primary pool ID Stream_pool_id: u256, /// Distribution orders @@ -72,6 +75,7 @@ pub mod StreamComponent { pub enum Event { PoolInitialized: PoolInitialized, LiquidityProvided: LiquidityProvided, + LiquidityPositionTransferred: LiquidityPositionTransferred, DistributionStarted: DistributionStarted, ProceedsClaimed: ProceedsClaimed, Preminted: Preminted, @@ -102,6 +106,14 @@ pub mod StreamComponent { pub token1_amount: u256, } + /// Emitted when the LP position NFT is transferred to the liquidity owner + #[derive(Drop, starknet::Event)] + pub struct LiquidityPositionTransferred { + pub position_id: u64, + #[key] + pub owner: ContractAddress, + } + /// Emitted when a distribution order starts #[derive(Drop, starknet::Event)] pub struct DistributionStarted { @@ -235,6 +247,10 @@ pub mod StreamComponent { self.Stream_pool_id.read() } + fn get_liquidity_owner(self: @ComponentState) -> ContractAddress { + self.Stream_liquidity_owner.read() + } + fn get_deployment_state(self: @ComponentState) -> u8 { self.Stream_deployment_state.read() } @@ -290,6 +306,13 @@ pub mod StreamComponent { }, ); + // Transfer LP position NFT to liquidity owner + let nft_address = positions_dispatcher.get_nft_address(); + let nft = IERC721Dispatcher { contract_address: nft_address }; + let liquidity_owner = self.Stream_liquidity_owner.read(); + nft.transfer_from(get_contract_address(), liquidity_owner, position_id.into()); + self.emit(LiquidityPositionTransferred { position_id, owner: liquidity_owner }); + (position_id, liquidity, token0_cleared, token1_cleared) } @@ -384,6 +407,10 @@ pub mod StreamComponent { ); assert(liquidity_config.stream_token_amount > 0, Errors::STREAM_INVALID_STREAM_AMOUNT); assert(liquidity_config.paired_token_amount > 0, Errors::STREAM_INVALID_PAIRED_AMOUNT); + assert( + liquidity_config.liquidity_owner != zero_address, + Errors::STREAM_INVALID_LIQUIDITY_OWNER, + ); // Store configuration self.Stream_factory.write(factory); @@ -402,6 +429,7 @@ pub mod StreamComponent { self.Stream_primary_stream_token_amount.write(liquidity_config.stream_token_amount); self.Stream_primary_paired_token_amount.write(liquidity_config.paired_token_amount); self.Stream_primary_min_liquidity.write(liquidity_config.min_liquidity); + self.Stream_liquidity_owner.write(liquidity_config.liquidity_owner); // Store distribution orders let order_count: u32 = distribution_orders.len(); diff --git a/packages/economy/src/tokenomics/tests/helpers/deployment.cairo b/packages/economy/src/tokenomics/tests/helpers/deployment.cairo index edb7d1f6..b075fab8 100644 --- a/packages/economy/src/tokenomics/tests/helpers/deployment.cairo +++ b/packages/economy/src/tokenomics/tests/helpers/deployment.cairo @@ -125,6 +125,7 @@ pub fn deploy_stream_token() -> StreamTokenSetup { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, // 1000 tokens paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, // 100 tokens min_liquidity: 1, + liquidity_owner: OWNER(), }; // Distribution order - sells stream tokens for buy_token @@ -205,6 +206,7 @@ pub fn deploy_stream_token_with_premints( stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, // 1000 tokens paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, // 100 tokens min_liquidity: 1, + liquidity_owner: OWNER(), }; // Distribution order - sells stream tokens for buy_token diff --git a/packages/economy/src/tokenomics/tests/test_factory.cairo b/packages/economy/src/tokenomics/tests/test_factory.cairo index a9d4b299..40a93e7d 100644 --- a/packages/economy/src/tokenomics/tests/test_factory.cairo +++ b/packages/economy/src/tokenomics/tests/test_factory.cairo @@ -65,6 +65,7 @@ fn default_create_params(paired_token: ContractAddress) -> CreateTokenParams { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ @@ -300,6 +301,7 @@ fn test_create_token_no_distribution_orders_fails() { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; let empty_orders: Array = array![]; @@ -339,6 +341,7 @@ fn test_create_token_too_many_orders_fails() { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; // Create 11 orders (more than max 10) @@ -394,6 +397,7 @@ fn test_create_token_supply_too_low_fails() { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ @@ -541,6 +545,7 @@ fn create_params_with_premints( stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ @@ -596,6 +601,7 @@ fn test_create_token_with_premints_supply_too_low_fails() { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, // 1000 tokens paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ diff --git a/packages/economy/src/tokenomics/tests/test_factory_fork.cairo b/packages/economy/src/tokenomics/tests/test_factory_fork.cairo index e8d494a6..a4627bc2 100644 --- a/packages/economy/src/tokenomics/tests/test_factory_fork.cairo +++ b/packages/economy/src/tokenomics/tests/test_factory_fork.cairo @@ -72,6 +72,7 @@ fn _create_params_with_strk_and_premints( stream_token_amount: 1000 * TOKEN_UNIT, // 1000 tokens for LP paired_token_amount: 100 * TOKEN_UNIT, // 100 STRK for LP min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ @@ -155,6 +156,7 @@ fn test_factory_rejects_insufficient_supply_with_premints_fork() { stream_token_amount: 1000 * TOKEN_UNIT, // 1000 tokens for LP paired_token_amount: 100 * TOKEN_UNIT, min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ diff --git a/packages/economy/src/tokenomics/tests/test_premint.cairo b/packages/economy/src/tokenomics/tests/test_premint.cairo index 2fde64f1..663860a7 100644 --- a/packages/economy/src/tokenomics/tests/test_premint.cairo +++ b/packages/economy/src/tokenomics/tests/test_premint.cairo @@ -221,6 +221,7 @@ fn build_invalid_premint_calldata(params: InvalidPremintTestParams) -> Array = array![ diff --git a/packages/economy/src/tokenomics/tests/test_stream.cairo b/packages/economy/src/tokenomics/tests/test_stream.cairo index 4a9cc695..4bf2612f 100644 --- a/packages/economy/src/tokenomics/tests/test_stream.cairo +++ b/packages/economy/src/tokenomics/tests/test_stream.cairo @@ -49,6 +49,7 @@ fn deploy_stream_token_with_orders(order_count: u32) -> StreamTokenSetup { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; // Create the specified number of orders diff --git a/packages/economy/src/tokenomics/tests/test_stream_fork.cairo b/packages/economy/src/tokenomics/tests/test_stream_fork.cairo index 370294f0..d07823b2 100644 --- a/packages/economy/src/tokenomics/tests/test_stream_fork.cairo +++ b/packages/economy/src/tokenomics/tests/test_stream_fork.cairo @@ -47,6 +47,7 @@ fn deploy_stream_token_mainnet() -> (ContractAddress, IStreamTokenDispatcher, IE stream_token_amount: 1000 * TOKEN_UNIT, paired_token_amount: 100 * TOKEN_UNIT, min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ @@ -103,6 +104,7 @@ fn deploy_stream_token_mainnet_with_premints( stream_token_amount: 1000 * TOKEN_UNIT, paired_token_amount: 100 * TOKEN_UNIT, min_liquidity: 1, + liquidity_owner: OWNER(), }; let distribution_orders: Array = array![ diff --git a/packages/economy/src/tokenomics/tests/test_stream_lifecycle.cairo b/packages/economy/src/tokenomics/tests/test_stream_lifecycle.cairo index d277744c..c4c0b84b 100644 --- a/packages/economy/src/tokenomics/tests/test_stream_lifecycle.cairo +++ b/packages/economy/src/tokenomics/tests/test_stream_lifecycle.cairo @@ -46,6 +46,7 @@ fn deploy_stream_token_with_multiple_orders() -> StreamTokenSetup { stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, min_liquidity: 1, + liquidity_owner: OWNER(), }; // Create 2 orders with SAME buy_token and fee to trigger increase_sell_amount @@ -102,6 +103,26 @@ fn deploy_stream_token_with_multiple_orders() -> StreamTokenSetup { // Full Lifecycle Tests with Mocked Ekubo // ============================================================================ +// Mock NFT address returned by get_nft_address +fn MOCK_NFT() -> starknet::ContractAddress { + 'MOCK_NFT'.try_into().unwrap() +} + +/// Set up mocks for the LP NFT transfer (get_nft_address + transfer_from) +fn mock_nft_transfer(mock_positions: starknet::ContractAddress) { + start_mock_call(mock_positions, selector!("get_nft_address"), MOCK_NFT()); + start_mock_call(mock_positions, selector!("transfer_from"), ()); + start_mock_call(MOCK_NFT(), selector!("transferFrom"), ()); + start_mock_call(MOCK_NFT(), selector!("transfer_from"), ()); +} + +fn stop_mock_nft_transfer(mock_positions: starknet::ContractAddress) { + stop_mock_call(mock_positions, selector!("get_nft_address")); + stop_mock_call(mock_positions, selector!("transfer_from")); + stop_mock_call(MOCK_NFT(), selector!("transferFrom")); + stop_mock_call(MOCK_NFT(), selector!("transfer_from")); +} + /// Test provide_initial_liquidity succeeds with mocked Ekubo calls #[test] fn test_provide_initial_liquidity_with_mocks() { @@ -122,6 +143,9 @@ fn test_provide_initial_liquidity_with_mocks() { (1_u64, 1000_u128, 500_u256, 500_u256), ); + // Mock NFT transfer (get_nft_address + transfer_from) + mock_nft_transfer(mock_positions); + // Verify initial state assert!(setup.token.get_deployment_state() == 0, "Should be state 0 after construction"); @@ -147,6 +171,7 @@ fn test_provide_initial_liquidity_with_mocks() { // Clean up mocks stop_mock_call(mock_core, selector!("initialize_pool")); stop_mock_call(mock_positions, selector!("mint_and_deposit_and_clear_both")); + stop_mock_nft_transfer(mock_positions); } /// Test full lifecycle: provide_initial_liquidity -> start_distributions @@ -164,6 +189,7 @@ fn test_start_distributions_with_mocks() { selector!("mint_and_deposit_and_clear_both"), (1_u64, 1000_u128, 500_u256, 500_u256), ); + mock_nft_transfer(mock_positions); // Mock Ekubo Positions mint_and_increase_sell_amount for start_distributions // Returns: (position_id: u64, sale_rate: u128) @@ -194,6 +220,7 @@ fn test_start_distributions_with_mocks() { stop_mock_call(mock_core, selector!("initialize_pool")); stop_mock_call(mock_positions, selector!("mint_and_deposit_and_clear_both")); stop_mock_call(mock_positions, selector!("mint_and_increase_sell_amount")); + stop_mock_nft_transfer(mock_positions); } /// Test claim_distribution_proceeds with mocks @@ -212,6 +239,7 @@ fn test_claim_distribution_proceeds_with_mocks() { (1_u64, 1000_u128, 500_u256, 500_u256), ); start_mock_call(mock_positions, selector!("mint_and_increase_sell_amount"), (2_u64, 100_u128)); + mock_nft_transfer(mock_positions); // Mock withdraw_proceeds_from_sale_to to return proceeds start_mock_call(mock_positions, selector!("withdraw_proceeds_from_sale_to"), 250_u128); @@ -237,6 +265,7 @@ fn test_claim_distribution_proceeds_with_mocks() { stop_mock_call(mock_positions, selector!("mint_and_deposit_and_clear_both")); stop_mock_call(mock_positions, selector!("mint_and_increase_sell_amount")); stop_mock_call(mock_positions, selector!("withdraw_proceeds_from_sale_to")); + stop_mock_nft_transfer(mock_positions); } /// Test lifecycle emits correct events @@ -253,6 +282,7 @@ fn test_lifecycle_events_with_mocks() { selector!("mint_and_deposit_and_clear_both"), (1_u64, 1000_u128, 500_u256, 500_u256), ); + mock_nft_transfer(mock_positions); let setup_dispatcher = IStreamTokenSetupDispatcher { contract_address: setup.token_address }; @@ -277,8 +307,24 @@ fn test_lifecycle_events_with_mocks() { ], ); + // Verify LiquidityPositionTransferred event + spy + .assert_emitted( + @array![ + ( + setup.token_address, + StreamComponent::Event::LiquidityPositionTransferred( + StreamComponent::LiquidityPositionTransferred { + position_id: 1, owner: OWNER(), + }, + ), + ), + ], + ); + stop_mock_call(mock_core, selector!("initialize_pool")); stop_mock_call(mock_positions, selector!("mint_and_deposit_and_clear_both")); + stop_mock_nft_transfer(mock_positions); } /// Test DistributionStarted event @@ -296,6 +342,7 @@ fn test_distribution_started_event_with_mocks() { (1_u64, 1000_u128, 500_u256, 500_u256), ); start_mock_call(mock_positions, selector!("mint_and_increase_sell_amount"), (2_u64, 100_u128)); + mock_nft_transfer(mock_positions); let setup_dispatcher = IStreamTokenSetupDispatcher { contract_address: setup.token_address }; @@ -335,6 +382,7 @@ fn test_distribution_started_event_with_mocks() { stop_mock_call(mock_core, selector!("initialize_pool")); stop_mock_call(mock_positions, selector!("mint_and_deposit_and_clear_both")); stop_mock_call(mock_positions, selector!("mint_and_increase_sell_amount")); + stop_mock_nft_transfer(mock_positions); } /// Test ProceedsClaimed event @@ -353,6 +401,7 @@ fn test_proceeds_claimed_event_with_mocks() { ); start_mock_call(mock_positions, selector!("mint_and_increase_sell_amount"), (2_u64, 100_u128)); start_mock_call(mock_positions, selector!("withdraw_proceeds_from_sale_to"), 250_u128); + mock_nft_transfer(mock_positions); let setup_dispatcher = IStreamTokenSetupDispatcher { contract_address: setup.token_address }; @@ -384,6 +433,7 @@ fn test_proceeds_claimed_event_with_mocks() { stop_mock_call(mock_positions, selector!("mint_and_deposit_and_clear_both")); stop_mock_call(mock_positions, selector!("mint_and_increase_sell_amount")); stop_mock_call(mock_positions, selector!("withdraw_proceeds_from_sale_to")); + stop_mock_nft_transfer(mock_positions); } /// Test start_distributions with multiple orders sharing same buy_token/fee @@ -402,6 +452,7 @@ fn test_start_distributions_multiple_orders_same_pool() { selector!("mint_and_deposit_and_clear_both"), (1_u64, 1000_u128, 500_u256, 500_u256), ); + mock_nft_transfer(mock_positions); // Mock mint_and_increase_sell_amount for FIRST order (creates new position) // Returns: (position_id: u64, sale_rate: u128) @@ -443,4 +494,5 @@ fn test_start_distributions_multiple_orders_same_pool() { stop_mock_call(mock_positions, selector!("mint_and_deposit_and_clear_both")); stop_mock_call(mock_positions, selector!("mint_and_increase_sell_amount")); stop_mock_call(mock_positions, selector!("increase_sell_amount")); + stop_mock_nft_transfer(mock_positions); } diff --git a/packages/interfaces/src/tokenomics/stream.cairo b/packages/interfaces/src/tokenomics/stream.cairo index 9b1d1eba..d7b8ac73 100644 --- a/packages/interfaces/src/tokenomics/stream.cairo +++ b/packages/interfaces/src/tokenomics/stream.cairo @@ -57,6 +57,8 @@ pub struct LiquidityConfig { pub paired_token_amount: u128, /// Minimum liquidity to accept (slippage protection) pub min_liquidity: u128, + /// Address to receive the LP position NFT (can withdraw/manage liquidity) + pub liquidity_owner: ContractAddress, } /// Parameters for creating a new stream token @@ -115,6 +117,9 @@ pub trait IStreamToken { /// Get the liquidity position ID fn get_liquidity_position_id(self: @TContractState) -> u64; + /// Get the liquidity owner address (receives LP position NFT) + fn get_liquidity_owner(self: @TContractState) -> ContractAddress; + /// Get the primary pool ID (for the liquidity pool) fn get_pool_id(self: @TContractState) -> u256; diff --git a/packages/presets/src/tests/test_stream_token.cairo b/packages/presets/src/tests/test_stream_token.cairo index f76ad2bd..14ce089e 100644 --- a/packages/presets/src/tests/test_stream_token.cairo +++ b/packages/presets/src/tests/test_stream_token.cairo @@ -102,6 +102,7 @@ fn deploy_stream_token() -> StreamTokenSetup { stream_token_amount: 1000_u128 * ERC20_UNIT, paired_token_amount: 100_u128 * ERC20_UNIT, min_liquidity: 1, + liquidity_owner: OWNER(), }; // Distribution orders @@ -165,6 +166,7 @@ fn deploy_stream_token_with_multiple_orders() -> StreamTokenSetup { stream_token_amount: 1000_u128 * ERC20_UNIT, paired_token_amount: 100_u128 * ERC20_UNIT, min_liquidity: 1, + liquidity_owner: OWNER(), }; // Multiple distribution orders diff --git a/scripts/create_stream_token.sh b/scripts/create_stream_token.sh index f22be325..e63f7aae 100755 --- a/scripts/create_stream_token.sh +++ b/scripts/create_stream_token.sh @@ -515,6 +515,7 @@ PAIRED_TOKEN=$(echo "$CONFIG" | jq -r '.liquidity_config.paired_token') POOL_FEE=$(echo "$CONFIG" | jq -r '.liquidity_config.fee') STREAM_TOKEN_AMOUNT=$(echo "$CONFIG" | jq -r '.liquidity_config.stream_token_amount') PAIRED_TOKEN_AMOUNT=$(echo "$CONFIG" | jq -r '.liquidity_config.paired_token_amount') +LIQUIDITY_OWNER=$(echo "$CONFIG" | jq -r '.liquidity_config.liquidity_owner') # Validate liquidity config if [ "$PAIRED_TOKEN" == "null" ] || [ -z "$PAIRED_TOKEN" ]; then @@ -533,6 +534,12 @@ if [ "$PAIRED_TOKEN_AMOUNT" == "null" ] || [ -z "$PAIRED_TOKEN_AMOUNT" ]; then exit 1 fi +if [ "$LIQUIDITY_OWNER" == "null" ] || [ -z "$LIQUIDITY_OWNER" ]; then + print_error "Missing required field: liquidity_config.liquidity_owner" + exit 1 +fi +validate_address "$LIQUIDITY_OWNER" "liquidity_config.liquidity_owner" + # Convert human-readable amounts to wei (18 decimals) TOTAL_SUPPLY_WEI=$(to_wei "$TOTAL_SUPPLY") STREAM_TOKEN_AMOUNT_WEI=$(to_wei "$STREAM_TOKEN_AMOUNT") @@ -597,6 +604,7 @@ cat << EOF Paired Token Amount: $PAIRED_TOKEN_AMOUNT tokens Implied Price: 1 $TOKEN_SYMBOL = $PRICE_RATIO paired tokens Min Liquidity: $MIN_LIQUIDITY + Liquidity Owner: $LIQUIDITY_OWNER Distribution Orders: $ORDER_COUNT EOF @@ -690,6 +698,7 @@ print_step "Step 2: Creating stream token..." # stream_token_amount: u128 # paired_token_amount: u128 # min_liquidity: u128 +# liquidity_owner: ContractAddress # distribution_orders: Span # len, [buy_token, fee, start_time, end_time, amount, recipient]... # premint_allocations: Span @@ -701,7 +710,7 @@ ORDERS_CALLDATA=$(build_orders_calldata "$CONFIG") PREMINTS_CALLDATA=$(build_premints_calldata "$CONFIG") # Full calldata as space-separated felts -CREATE_CALLDATA="$NAME_CALLDATA $SYMBOL_CALLDATA $TOTAL_SUPPLY_WEI $PAIRED_TOKEN $POOL_FEE $STREAM_TOKEN_AMOUNT_WEI $PAIRED_TOKEN_AMOUNT_WEI $MIN_LIQUIDITY $ORDERS_CALLDATA $PREMINTS_CALLDATA" +CREATE_CALLDATA="$NAME_CALLDATA $SYMBOL_CALLDATA $TOTAL_SUPPLY_WEI $PAIRED_TOKEN $POOL_FEE $STREAM_TOKEN_AMOUNT_WEI $PAIRED_TOKEN_AMOUNT_WEI $MIN_LIQUIDITY $LIQUIDITY_OWNER $ORDERS_CALLDATA $PREMINTS_CALLDATA" print_verbose "Create calldata: $CREATE_CALLDATA" @@ -789,7 +798,8 @@ cat > "$DEPLOYMENT_FILE" << EOF "stream_token_amount": "$STREAM_TOKEN_AMOUNT", "paired_token_amount": "$PAIRED_TOKEN_AMOUNT", "implied_price": "$PRICE_RATIO", - "min_liquidity": "$MIN_LIQUIDITY" + "min_liquidity": "$MIN_LIQUIDITY", + "liquidity_owner": "$LIQUIDITY_OWNER" }, "distribution_orders_count": $ORDER_COUNT, "premint_allocations_count": $PREMINT_COUNT diff --git a/scripts/examples/summit_attack_potion.json b/scripts/examples/summit_attack_potion.json new file mode 100644 index 00000000..6cbd782a --- /dev/null +++ b/scripts/examples/summit_attack_potion.json @@ -0,0 +1,28 @@ +{ + "_comment": "All token amounts assume 18 decimals. Use human-readable values (e.g., '1000000' for 1M tokens). total_supply is auto-calculated from LP + distributions + premints.", + + "name": "Attack", + "symbol": "ATTACK", + + "liquidity_config": { + "paired_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "stream_token_amount": "50000", + "paired_token_amount": "2000", + "liquidity_owner": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + }, + + "distribution_orders": [ + { + "_comment": "TWAMM end_time must be aligned to powers of 16. Script validates and suggests valid times.", + "buy_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "start_time": 1771433984, + "end_time": 1779433472, + "amount": "9000000", + "proceeds_recipient": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + } + ], + + "premint_allocations": [] +} diff --git a/scripts/examples/summit_poison_potion.json b/scripts/examples/summit_poison_potion.json new file mode 100644 index 00000000..ba14bea5 --- /dev/null +++ b/scripts/examples/summit_poison_potion.json @@ -0,0 +1,28 @@ +{ + "_comment": "All token amounts assume 18 decimals. Use human-readable values (e.g., '1000000' for 1M tokens). total_supply is auto-calculated from LP + distributions + premints.", + + "name": "Poison", + "symbol": "POISON", + + "liquidity_config": { + "paired_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "stream_token_amount": "50000", + "paired_token_amount": "2000", + "liquidity_owner": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + }, + + "distribution_orders": [ + { + "_comment": "TWAMM end_time must be aligned to powers of 16. Script validates and suggests valid times.", + "buy_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "start_time": 1771433984, + "end_time": 1779433472, + "amount": "9000000", + "proceeds_recipient": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + } + ], + + "premint_allocations": [] +} diff --git a/scripts/examples/summit_revive_potion.json b/scripts/examples/summit_revive_potion.json new file mode 100644 index 00000000..8fd3e621 --- /dev/null +++ b/scripts/examples/summit_revive_potion.json @@ -0,0 +1,28 @@ +{ + "_comment": "All token amounts assume 18 decimals. Use human-readable values (e.g., '1000000' for 1M tokens). total_supply is auto-calculated from LP + distributions + premints.", + + "name": "Revive", + "symbol": "REVIVE", + + "liquidity_config": { + "paired_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "stream_token_amount": "50000", + "paired_token_amount": "2000", + "liquidity_owner": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + }, + + "distribution_orders": [ + { + "_comment": "TWAMM end_time must be aligned to powers of 16. Script validates and suggests valid times.", + "buy_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "start_time": 1771433984, + "end_time": 1779433472, + "amount": "9000000", + "proceeds_recipient": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + } + ], + + "premint_allocations": [] +} diff --git a/scripts/examples/summit_xlife_potion.json b/scripts/examples/summit_xlife_potion.json new file mode 100644 index 00000000..e02965d2 --- /dev/null +++ b/scripts/examples/summit_xlife_potion.json @@ -0,0 +1,28 @@ +{ + "_comment": "All token amounts assume 18 decimals. Use human-readable values (e.g., '1000000' for 1M tokens).", + + "name": "Extra Life", + "symbol": "XLIFE", + + "liquidity_config": { + "paired_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "stream_token_amount": "2500", + "paired_token_amount": "2000", + "liquidity_owner": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + }, + + "distribution_orders": [ + { + "_comment": "TWAMM end_time must be aligned to powers of 16. Script validates and suggests valid times.", + "buy_token": "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "fee": "17014118346046924117642026945517453312", + "start_time": 1771433984, + "end_time": 1779433472, + "amount": "450000", + "proceeds_recipient": "0x041bb7729efa185f2cab327de0a668886302f1d4969e3edf504c4741648f858b" + } + ], + + "premint_allocations": [] +}