From d705539dc0fdaf5ef8287bc6e39a4e41a5646ab5 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Mon, 15 Dec 2025 21:29:38 +0530 Subject: [PATCH 01/14] Add redemption router --- my_scripts/contracts.json | 2 +- my_scripts/deploy.ts | 44 +- my_scripts/pnpm-lock.yaml | 4 +- nohup.log | 316 +++++ packages/vault/Scarb.toml | 2 +- packages/vault/src/lib.cairo | 7 + .../src/redemption_router/INTEGRATION.md | 18 + .../vault/src/redemption_router/TEST_PLAN.md | 671 ++++++++++ .../vault/src/redemption_router/errors.cairo | 67 + .../src/redemption_router/interface.cairo | 69 + .../redemption_router/redemption_router.cairo | 1010 ++++++++++++++ .../src/test/units/redemption_router.cairo | 1178 +++++++++++++++++ packages/vault/src/test/utils.cairo | 2 +- packages/vault_allocator/src/lib.cairo | 1 + .../src/mocks/mock_avnu_exchange.cairo | 143 ++ 15 files changed, 3523 insertions(+), 11 deletions(-) create mode 100644 nohup.log create mode 100644 packages/vault/src/redemption_router/INTEGRATION.md create mode 100644 packages/vault/src/redemption_router/TEST_PLAN.md create mode 100644 packages/vault/src/redemption_router/errors.cairo create mode 100644 packages/vault/src/redemption_router/interface.cairo create mode 100644 packages/vault/src/redemption_router/redemption_router.cairo create mode 100644 packages/vault/src/test/units/redemption_router.cairo create mode 100644 packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo diff --git a/my_scripts/contracts.json b/my_scripts/contracts.json index 8535f87c..31494bb3 100644 --- a/my_scripts/contracts.json +++ b/my_scripts/contracts.json @@ -1 +1 @@ -{"class_hashes":{"Vault":"0x5f3e4d3a6f6ea274d0288b7320428e9960298b9db1f6848b7805d14ace6413e","VaultAllocator":"0x7608b7b98f28a18841285367907f2c8bb924949e9f610f370b190e106cd3c3f","RedeemRequest":"0x7421d403cf73830acc41ebc9646c74f4a931f747ac275e32d82fc3a7c7a9aef","Manager":"0x7dbf4bc6ebf73952e77d60a7da8b1ec1523cfd3477ed99253403a3e0cab40d0","SimpleDecoderAndSanitizer":"0x19c38fa79f607d0935596802bcee103b88bc81dfa4b9f2d8bf0e398b19ec3c8"},"contracts":{"Vault":"0x6a346bda4e723d3f4763513007d4b8ef0029f491ed0a1e0626db6d7f3af3c01","RedeemRequest":"0x66c5f84e5fc6c20545737cc187289adccacfeb9829da6fd4ddf6121b32573dc","VaultAllocator":"0x292503a0bff97ad8818481cca50f5f9298537d0e0e9dc6a7ac9bbb8a93f71a3","Manager":"0x2d7822d1616f5e0b556c0f7467eb968bd2acd0f2b2b8623adff96a51e5516db","SimpleDecoderAndSanitizer":"0x7b6f98311af8aa425278570e62abf523e6462eaa01a38c1feab9b2f416492e2","aum_oracle":"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345"}} \ No newline at end of file +{"class_hashes":{"Vault":"0x5f3e4d3a6f6ea274d0288b7320428e9960298b9db1f6848b7805d14ace6413e","VaultAllocator":"0x7608b7b98f28a18841285367907f2c8bb924949e9f610f370b190e106cd3c3f","RedeemRequest":"0x7421d403cf73830acc41ebc9646c74f4a931f747ac275e32d82fc3a7c7a9aef","Manager":"0x7dbf4bc6ebf73952e77d60a7da8b1ec1523cfd3477ed99253403a3e0cab40d0","SimpleDecoderAndSanitizer":"0x19c38fa79f607d0935596802bcee103b88bc81dfa4b9f2d8bf0e398b19ec3c8","UsdtFixer":"0x19b362ba0e70d94185ebb986ea80039ec19ffca410f200f971fe5731ffd5a21","RedemptionRouter":"0x7cee2a1b936a2f3ff6bb3028e3ac13f40c8a7a2bae21c1129b069a7391db814"},"contracts":{"Vault":"0x6a346bda4e723d3f4763513007d4b8ef0029f491ed0a1e0626db6d7f3af3c01","RedeemRequest":"0x66c5f84e5fc6c20545737cc187289adccacfeb9829da6fd4ddf6121b32573dc","VaultAllocator":"0x292503a0bff97ad8818481cca50f5f9298537d0e0e9dc6a7ac9bbb8a93f71a3","Manager":"0x2d7822d1616f5e0b556c0f7467eb968bd2acd0f2b2b8623adff96a51e5516db","SimpleDecoderAndSanitizer":"0x7b6f98311af8aa425278570e62abf523e6462eaa01a38c1feab9b2f416492e2","aum_oracle":"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345","UsdtFixer":"0x7954afaca4c706f9f30658777e55b7f8e264c8974b8a2638d5ebb359280816","RedemptionRouter":"0x67d41ef9c2e119053a5a457451eb4c37d36fdfc3ba854fbb0e31e3f1ff9c4aa"}} \ No newline at end of file diff --git a/my_scripts/deploy.ts b/my_scripts/deploy.ts index 070a26e7..d4aebe26 100644 --- a/my_scripts/deploy.ts +++ b/my_scripts/deploy.ts @@ -316,6 +316,35 @@ async function pause(strategy: UniversalStrategy) { await Deployer.executeTransactions([pauseCall], acc, provider, 'Pause'); } +async function deployUsdtFixer() { + const provider = config.provider; + const calls = await Deployer.prepareMultiDeployContracts([{ + contract_name: 'UsdtFixer', + package_name: VAULT_PACKAGE, + constructorData: [] + }], config, acc); + await Deployer.executeDeployCalls(calls, acc, provider); +} + +async function deployRedemptionRouter() { + const provider = config.provider; + const strategy = HyperLSTStrategies.find(u => u.name.includes('xWBTC'))!; + const calls = await Deployer.prepareMultiDeployContracts([{ + contract_name: 'RedemptionRouter', + package_name: VAULT_PACKAGE, + constructorData: [ + OWNER, + strategy.additionalInfo.vaultAddress.address, + strategy.additionalInfo.redeemRequestNFT.address, + Global.getDefaultTokens().find(t => t.symbol === 'WBTC')?.address!, + "0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f", + OWNER, + "0", + ] + }], config, acc); + await Deployer.executeDeployCalls(calls, acc, provider); +} + async function unpause(strategy: UniversalStrategy) { const provider = config.provider; const cls = await provider.getClassAt(strategy.address.address.toString()); @@ -371,7 +400,7 @@ if (require.main === module) { // deployStrategy(); // deployAUMOracle("0x437ef1e7d0f100b2e070b7a65cafec0b2be31b0290776da8b4112f5473d8d9") - const strategy = HyperLSTStrategies.find(u => u.name.includes('xsBTC'))!; + const strategy = HyperLSTStrategies.find(u => u.name.includes('xSTRK'))!; // const vaultStrategy = new UniversalStrategy(config, pricer, strategy); const vaultStrategy = new UniversalLstMultiplierStrategy(config, pricer, strategy); const vaultContracts = { @@ -380,14 +409,17 @@ if (require.main === module) { vaultAllocator: strategy.additionalInfo.vaultAllocator, manager: strategy.additionalInfo.manager } + + // deployUsdtFixer(); + // deployRedemptionRouter(); async function setConfig() { - await upgrade('Vault', VAULT_PACKAGE, vaultContracts.vault.toString()); - await upgrade('VaultAllocator', VAULT_ALLOCATOR_PACKAGE, vaultContracts.vaultAllocator.toString()); - await upgrade('Manager', VAULT_ALLOCATOR_PACKAGE, vaultContracts.manager.toString()); - await upgrade('RedeemRequest', VAULT_PACKAGE, vaultContracts.redeemRequest.toString()); + // await upgrade('Vault', VAULT_PACKAGE, vaultContracts.vault.toString()); + // await upgrade('VaultAllocator', VAULT_ALLOCATOR_PACKAGE, vaultContracts.vaultAllocator.toString()); + // await upgrade('Manager', VAULT_ALLOCATOR_PACKAGE, vaultContracts.manager.toString()); + // await upgrade('RedeemRequest', VAULT_PACKAGE, vaultContracts.redeemRequest.toString()); // await configureSettings(vaultContracts); - // await setManagerRoot(vaultStrategy, ContractAddr.from(RELAYER)); + await setManagerRoot(vaultStrategy, ContractAddr.from(RELAYER)); // await grantRole(vaultStrategy, hash.getSelectorFromName('ORACLE_ROLE'), strategy.additionalInfo.aumOracle.address); // await setMaxDelta(vaultStrategy, getMaxDelta(200, CommonSettings.vault.default_settings.report_delay * 6)); diff --git a/my_scripts/pnpm-lock.yaml b/my_scripts/pnpm-lock.yaml index 5da4d42d..5a6b261a 100644 --- a/my_scripts/pnpm-lock.yaml +++ b/my_scripts/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^2.0.0 version: 2.0.1 '@strkfarm/sdk': - specifier: link:../../../Library/pnpm/global/5/node_modules/@strkfarm/sdk - version: link:../../../Library/pnpm/global/5/node_modules/@strkfarm/sdk + specifier: link:../../sdk-ts + version: link:../../sdk-ts axios: specifier: ^1.12.2 version: 1.12.2 diff --git a/nohup.log b/nohup.log new file mode 100644 index 00000000..a56180f5 --- /dev/null +++ b/nohup.log @@ -0,0 +1,316 @@ + Compiling snforge_scarb_plugin v0.48.0 +warning: hiding a lifetime that's elided elsewhere is confusing + --> src/args.rs:79:43 + | +79 | pub fn unnamed_only(&self) -> Result { + | ^^^^^ ----------- the same lifetime is hidden here + | | + | the lifetime is elided here + | + = help: the same lifetime is referred to in inconsistent ways, making the signature confusing + = note: `#[warn(mismatched_lifetime_syntaxes)]` on by default +help: use `'_` for type paths + | +79 | pub fn unnamed_only(&self) -> Result, Diagnostic> { + | ++++ +warning: hiding a lifetime that's elided elsewhere is confusing + --> src/args.rs:88:20 + | +88 | pub fn unnamed(&self) -> UnnamedArgs { + | ^^^^^ ----------- the same lifetime is hidden here + | | + | the lifetime is elided here + | + = help: the same lifetime is referred to in inconsistent ways, making the signature confusing +help: use `'_` for type paths + | +88 | pub fn unnamed(&self) -> UnnamedArgs<'_> { + | ++++ +warning: `snforge_scarb_plugin` (lib) generated 2 warnings + Finished `release` profile [optimized] target(s) in 0.15s + Compiling test(starknet_vault_kit_unittest) starknet_vault_kit v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/Scarb.toml) + Compiling test(vault_unittest) vault v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/packages/vault/Scarb.toml) +warn: Unused import: `vault::temp::usdt_fixer::UsdtFixer::get_contract_address` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault/src/temp/usdt_fixer.cairo:15:37 + use starknet::{ContractAddress, get_contract_address}; + ^^^^^^^^^^^^^^^^^^^^ + + Compiling test(vault_allocator_unittest) vault_allocator v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/Scarb.toml) +warn: Unused import: `vault_allocator::middlewares::avnu_middleware::avnu_middleware::AvnuMiddleware::Map` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo:15:9 + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + ^^^ + +warn: Unused import: `vault_allocator::middlewares::avnu_middleware::avnu_middleware::AvnuMiddleware::StorageMapReadAccess` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo:15:14 + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + ^^^^^^^^^^^^^^^^^^^^ + +warn: Unused import: `vault_allocator::middlewares::avnu_middleware::avnu_middleware::AvnuMiddleware::StorageMapWriteAccess` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo:15:36 + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + ^^^^^^^^^^^^^^^^^^^^^ + +warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::get_caller_address` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:5:33 +use starknet::{ContractAddress, get_caller_address, get_contract_address}; + ^^^^^^^^^^^^^^^^^^ + +warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::get_contract_address` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:5:53 +use starknet::{ContractAddress, get_caller_address, get_contract_address}; + ^^^^^^^^^^^^^^^^^^^^ + +warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::ERC20ABIDispatcher` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:6:39 +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + ^^^^^^^^^^^^^^^^^^ + +warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::ERC20ABIDispatcherTrait` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:6:59 +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + ^^^^^^^^^^^^^^^^^^^^^^^ + +warn: Unused import: `vault_allocator::test::integrations::vault_bring_liquidity::IERC4626Dispatcher` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo:6:40 +use openzeppelin::interfaces::erc4626::IERC4626Dispatcher; + ^^^^^^^^^^^^^^^^^^ + + Finished `dev` profile target(s) in 19 seconds + + +Collected 0 test(s) from starknet_vault_kit package +Running 0 test(s) from src/ +Tests: 0 passed, 0 failed, 0 ignored, 0 filtered out + + +Collected 15 test(s) from vault package +Running 15 test(s) from src/ +test_claim_sequential_enforcement started +deploying redemption router +deploying redemption router with calldata +constructor called +deploying redemption router +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +deploying redemption router with calldata +constructor called +initializing components +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +access_control initialized +role hierarchy set +NFT counter initialized +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +owner role granted +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +deploying redemption router with calldata +addresses stored +swap counters initialized +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +constructor called +initializing components +access_control initialized +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +deploying redemption router with calldata +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +constructor called +role hierarchy set +NFT counter initialized +owner role granted +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +addresses stored +swap counters initialized +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +deploying redemption router with calldata +deploying redemption router with calldata +[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_to_asset (l1_gas: ~0, l1_data_gas: ~96, l2_gas: ~480000) +constructor called +initializing components +constructor called +initializing components +access_control initialized +access_control initialized +role hierarchy set +NFT counter initialized +role hierarchy set +NFT counter initialized +owner role granted +addresses stored +swap counters initialized +owner role granted +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +addresses stored +swap counters initialized +RELAYER_ROLE granted +minting old NFTs +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +deploying redemption router with calldata +constructor called +initializing components +access_control initialized +role hierarchy set +deploying redemption router with calldata +NFT counter initialized +owner role granted +addresses stored +swap counters initialized +RELAYER_ROLE granted +approving NFTs +approving NFT 1 +constructor called +initializing components +access_control initialized +RELAYER_ROLE granted +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +role hierarchy set +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +NFT counter initialized +owner role granted +addresses stored +swap counters initialized +approving NFT 2 +deploying redemption router with calldata +constructor called +initializing components +access_control initialized +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +role hierarchy set +NFT counter initialized +RELAYER_ROLE granted +RELAYER_ROLE granted +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +RELAYER_ROLE granted +owner role granted +addresses stored +swap counters initialized +deploying redemption router with calldata +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +constructor called +initializing components +access_control initialized +subscribed NFT 2 +RELAYER_ROLE granted +setup done +fulfilled old NFTs +transferred assets +minted to_asset +role hierarchy set +NFT counter initialized +owner role granted +addresses stored +swap counters initialized +claiming NFT 0 +addresses stored correctly +roles set +swap_id and unsettled_swap_id start at 1 +NFT counter starts at 0 +swapped +attempting to claim NFT 2 before NFT 1 +claiming NFT 1 +from_rem: 200000000000000000000 +deploying redemption router with calldata +to_rem: 400000000000000000000 +constructor called +initializing components +access_control initialized +role hierarchy set +NFT counter initialized +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +owner role granted +addresses stored +swap counters initialized +claiming NFT 1 +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +RELAYER_ROLE granted +RELAYER_ROLE granted +[PASS] vault::test::units::redemption_router::test_swap_reverts_when_not_relayer (l1_gas: ~0, l1_data_gas: ~5184, l2_gas: ~12897920) +[PASS] vault::test::units::redemption_router::test_claim_two_subscribes_one_swap_two_claims (l1_gas: ~0, l1_data_gas: ~6816, l2_gas: ~36420160) +[PASS] vault::test::units::redemption_router::test_swap_reverts_on_insufficient_balance (l1_gas: ~0, l1_data_gas: ~5088, l2_gas: ~12136960) +[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_redeem_request (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) +[PASS] vault::test::units::redemption_router::test_constructor_initializes_correctly (l1_gas: ~0, l1_data_gas: ~5088, l2_gas: ~13536960) +[PASS] vault::test::units::redemption_router::test_subscribe_transfers_old_nft_and_mints_new (l1_gas: ~0, l1_data_gas: ~6336, l2_gas: ~17337600) +[PASS] vault::test::units::redemption_router::test_swap_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~5184, l2_gas: ~12712320) +[PASS] vault::test::units::redemption_router::test_swap_executes_successfully (l1_gas: ~0, l1_data_gas: ~5664, l2_gas: ~17069760) +[PASS] vault::test::units::redemption_router::test_subscribe_increments_nft_counter (l1_gas: ~0, l1_data_gas: ~7104, l2_gas: ~21458240) +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +[PASS] vault::test::units::redemption_router::test_claim_sequential_enforcement (l1_gas: ~0, l1_data_gas: ~7200, l2_gas: ~29253440) +deploying redemption router with calldata +constructor called +initializing components +access_control initialized +role hierarchy set +NFT counter initialized +owner role granted +addresses stored +swap counters initialized +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +RELAYER_ROLE granted +deploying redemption router +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +deploying redemption router with calldata +constructor called +avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +[PASS] vault::test::units::redemption_router::test_swap_uses_actual_received_amount (l1_gas: ~0, l1_data_gas: ~5664, l2_gas: ~17069760) +deploying redemption router with calldata +constructor called +initializing components +access_control initialized +role hierarchy set +NFT counter initialized +owner role granted +addresses stored +swap counters initialized +deploying redemption router with calldata +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +constructor called +initializing components +access_control initialized +role hierarchy set +NFT counter initialized +owner role granted +addresses stored +swap counters initialized +RELAYER_ROLE granted +router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_vault (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) +RELAYER_ROLE granted +claiming NFT 0 +[PASS] vault::test::units::redemption_router::test_subscribe_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~5760, l2_gas: ~14574720) +[PASS] vault::test::units::redemption_router::test_claim_single_subscribe_single_swap_single_claim (l1_gas: ~0, l1_data_gas: ~6144, l2_gas: ~25864960) +Tests: 15 passed, 0 failed, 0 ignored, 87 filtered out +[ERROR] data did not match any variant of untagged enum JsonRpcResponse diff --git a/packages/vault/Scarb.toml b/packages/vault/Scarb.toml index 17f37870..b2cd858a 100644 --- a/packages/vault/Scarb.toml +++ b/packages/vault/Scarb.toml @@ -34,7 +34,7 @@ snforge_std.workspace = true [lib] [[target.starknet-contract]] -build-external-contracts = ["vault_allocator::vault_allocator::vault_allocator::VaultAllocator", "vault_allocator::mocks::erc20::Erc20Mock", "vault_allocator::mocks::counter::Counter"] +build-external-contracts = ["vault_allocator::vault_allocator::vault_allocator::VaultAllocator", "vault_allocator::mocks::erc20::Erc20Mock", "vault_allocator::mocks::counter::Counter", "vault_allocator::mocks::mock_avnu_exchange::MockAvnuExchange"] allowed-libfuncs-list.name = "experimental" sierra = true casm = true diff --git a/packages/vault/src/lib.cairo b/packages/vault/src/lib.cairo index 12aa3603..c20aab18 100644 --- a/packages/vault/src/lib.cairo +++ b/packages/vault/src/lib.cairo @@ -26,11 +26,18 @@ pub mod redeem_request { pub mod redeem_request; } +pub mod redemption_router { + pub mod errors; + pub mod interface; + pub mod redemption_router; +} + #[cfg(test)] pub mod test { pub mod utils; pub mod units { pub mod redeem_request; pub mod vault; + pub mod redemption_router; } } diff --git a/packages/vault/src/redemption_router/INTEGRATION.md b/packages/vault/src/redemption_router/INTEGRATION.md new file mode 100644 index 00000000..c90cfbdd --- /dev/null +++ b/packages/vault/src/redemption_router/INTEGRATION.md @@ -0,0 +1,18 @@ +# Redemption Router Integration Guidelines + +## Subscribe Flow + +When a user wants to redeem vault shares and receive assets in a different token via the Redemption Router: + +1. **Request Redemption**: Call `vault.request_redeem(shares, receiver, owner)` to receive a RedeemRequest NFT +2. **Approve & Subscribe**: In the same multicall transaction: + - Approve the RedeemRequest NFT to the RedemptionRouter contract + - Call `redemptionRouter.subscribe(nft_id, receiver)` to transfer the redemption NFT to the router +3. **Receive New NFT**: The router transfers the user's RedeemRequest NFT to itself and mints a new RedemptionRouter NFT to the user +4. **Claim Assets**: Once the relayer has swapped assets and settled claims, the user calls `redemptionRouter.claim(new_nft_id)` which burns the NFT and transfers the swapped `to_asset` tokens to the user + +**Note**: The subscribe operation should be done atomically with the approval in a multicall to ensure the NFT is immediately subscribed after redemption request, preventing any intermediate state where the NFT could be lost or mishandled. + + + + diff --git a/packages/vault/src/redemption_router/TEST_PLAN.md b/packages/vault/src/redemption_router/TEST_PLAN.md new file mode 100644 index 00000000..80efce61 --- /dev/null +++ b/packages/vault/src/redemption_router/TEST_PLAN.md @@ -0,0 +1,671 @@ +# Redemption Router Test Plan + +## Overview +This document outlines comprehensive unit tests for the `RedemptionRouter` contract. Tests will use: +- **Mock Call Cheatcodes**: Use `mock_call` and `start_mock_call` from snforge to mock vault functions +- **RedeemRequest Contract**: Deploy actual `RedeemRequest` contract with dummy vault address +- **Avnu Exchange**: Deploy mock contract from `vault_allocator/src/mocks/mock_avnu_exchange.cairo` +- **ERC20 Tokens**: Use `Erc20Mock` from `vault_allocator/src/mocks/erc20.cairo` for from_asset and to_asset + +## Test Setup & Mocking Strategy + +### 1. Vault Contract Mocking (using `mock_call`) +- **No mock contract needed** - use `mock_call` cheatcode to mock vault functions +- Mock `due_assets_from_id(id: u256) -> u256`: + ```cairo + let due_amount: u256 = 100; // example + mock_call(VAULT_ADDRESS, selector!("due_assets_from_id"), due_amount, 1); + ``` +- Mock `asset() -> ContractAddress`: + ```cairo + mock_call(VAULT_ADDRESS, selector!("asset"), from_asset_address, 1); + ``` +- **Important**: If old NFT is not burnt/claimed, `due_assets_from_id` returns non-zero. If burnt, returns 0. + +### 2. RedeemRequest Contract +- **Deploy actual contract** with dummy vault address +- To mint NFT to user (simulate subscription): + - Cheat caller as vault: `cheat_caller_address(redeem_request_address, VAULT_ADDRESS)` + - Call `mint(user_address, redeem_request_info)` on redeem_request + - User approves router: `approve(router_address, nft_id)` + - User calls `subscribe(nft_id, receiver)` on router +- To burn NFT (simulate claim_redeem completion): + - Cheat caller as vault: `cheat_caller_address(redeem_request_address, VAULT_ADDRESS)` + - Call `burn(nft_id)` on redeem_request + - After burn, `due_assets_from_id` should return 0 (mocked) + +### 3. Avnu Exchange Mock +- **Deploy mock contract** from `vault_allocator/src/mocks/mock_avnu_exchange.cairo` +- The mock automatically transfers `sell_token_amount` from caller and `buy_token_min_amount` to beneficiary +- Returns `true` on success + +### 4. ERC20 Tokens +- Deploy `Erc20Mock` from `vault_allocator/src/mocks/erc20.cairo` for: + - `from_asset`: Asset token that vault uses + - `to_asset`: Target token for swaps +- Mint tokens to router contract for swap operations + +### 5. Test Flow Summary + +**Setup:** +1. Deploy from_asset and to_asset ERC20 mocks +2. Deploy Avnu exchange mock +3. Deploy redeem_request with dummy vault address +4. Deploy redemption_router with all addresses +5. Set up roles (grant RELAYER_ROLE) + +**Simulating User Subscription:** +1. Cheat caller as vault → mint NFT to user via redeem_request +2. User approves router for NFT +3. User calls `subscribe(nft_id, receiver)` on router +4. Router transfers old NFT and mints new NFT + +**Simulating Swap (after subscription):** +1. First, fulfill the old NFT (simulate claim_redeem): + - Transfer vault assets to router (from_asset tokens) + - Cheat caller as vault → burn old NFT on redeem_request + - Mock `due_assets_from_id` to return 0 for this NFT (since it's burnt) +2. Router has from_asset balance +3. Relayer calls `swap()` with routes and amounts +4. Mock Avnu transfers tokens appropriately + +**Simulating Claim:** +1. Ensure old NFT is fulfilled (burnt on redeem_request) +2. Mock `due_assets_from_id` for the old NFT with the amount user was owed (even though NFT is burnt, this represents historical due amount) +3. User calls `claim(new_nft_id)` on router +4. Router calls `vault.due_assets_from_id(old_nft_id)` - this should return the mocked amount +5. Router iterates pools and transfers proportional to_asset to user + +**Important**: The router's `claim` function calls `vault.due_assets_from_id(old_nft_id)` to determine what the user was owed. Even if the old NFT is burnt, we need to mock this to return the historical due amount so the router can calculate proportional swap amounts correctly. + +--- + +## Test Categories + +### 1. Constructor & Initialization Tests + +#### `test_constructor_initializes_correctly` +- Verify all addresses are stored correctly +- Verify roles are set (OWNER_ROLE, RELAYER_ROLE) +- Verify `swap_id` and `unsettled_swap_id` start at 1 +- Verify `nft_id_counter` starts at 0 +- Verify `min_subscribe_amount` is set correctly +- Verify `last_settled_epoch` is initialized from vault's `handled_epoch_len` +- Verify NFT contract initialized with name "RedemptionRouter", symbol "RR" + +#### `test_constructor_reverts_on_zero_addresses` +- Test each address parameter (vault, redeem_request, to_asset, avnu_exchange, integrator_fee_recipient) +- Verify appropriate error is raised + +--- + +### 2. Subscribe Function Tests + +#### `test_subscribe_transfers_old_nft_and_mints_new` +- User subscribes with old NFT ID +- Verify old NFT transferred from user to router +- Verify new NFT minted to receiver +- Verify `new_nft_request_map` stores correct `old_nft_id` and `is_claimed = false` +- Verify `Subscribed` event emitted + +#### `test_subscribe_increments_nft_counter` +- Multiple subscriptions +- Verify NFT IDs are sequential (0, 1, 2, ...) + +#### `test_subscribe_reverts_when_paused` +- Pause contract +- Attempt subscribe +- Verify revert + +#### `test_subscribe_reverts_on_too_small_amount` +- Set `min_subscribe_amount` to 100 +- Attempt subscribe with NFT that has `due_amount` < 100 +- Verify `too_small_subscribe_amount` error + +#### `test_subscribe_snapshots_epoch_data` +- Subscribe with NFT from epoch 5 +- Verify `epoch_redeem_assets` and `epoch_redeem_nominal` are stored for epoch 5 +- Verify `epoch_offset_factor` defaults to WAD if not set +- Verify `epoch_wise_nominals` accumulates nominal amount + +--- + +### 3. Swap Function Tests + +#### `test_swap_executes_successfully` +- Router has balance of from_asset +- Execute swap +- Verify Avnu `multi_route_swap` called with correct parameters +- Verify `swap_info` stores `(from_amount, to_amount)` correctly +- Verify `swap_id` increments +- Verify `Swapped` event emitted with `swap_id`, `from_amount`, `to_amount`, and `last_settled_epoch` +- Verify epochs are settled correctly based on `from_amount` + +#### `test_swap_reverts_on_insufficient_balance` +- Router has insufficient from_asset balance +- Attempt swap +- Verify `insufficient_from_amount` error + +#### `test_swap_reverts_when_not_relayer` +- Non-relayer attempts swap +- Verify access control revert + +#### `test_swap_reverts_when_paused` +- Pause contract +- Attempt swap +- Verify revert + +#### `test_swap_reverts_on_avnu_failure` +- Mock Avnu returns `false` +- Verify `swap_failed` error + +#### `test_swap_uses_actual_received_amount` +- Mock Avnu returns different amount than `min_amount_out` +- Verify `swap_info` stores actual received amount (balance_delta) + +--- + +### 4. Basic Claim Scenarios + +#### `test_claim_single_subscribe_single_swap_single_claim` +- 1 subscribe → 1 swap → 1 claim +- Verify user receives correct proportional amount: `due_assets * to_amount / from_amount` +- Verify NFT burned +- Verify `is_claimed = true` +- Verify `Claimed` event emitted with `new_nft_id`, `old_nft_id`, `receivable`, and `swap_id` +- Verify pool fully consumed, `unsettled_swap_id` advances + +#### `test_claim_two_subscribes_one_swap_two_claims` +- 2 subscribes → 1 swap → 2 claims +- User 1 due: 100, User 2 due: 200, Swap: 300 from → 600 to +- Verify User 1 gets: 100 * 600 / 300 = 200 +- Assert remaining swap info is updated correctly, correct all swap IDs +- Verify User 2 gets: 200 * 600 / 300 = 400 +- Verify both claims succeed (both epochs must be settled) +- Verify pool fully consumed after both claims + +#### `test_claim_requires_epoch_settled` +- User subscribes to epoch 5 +- Execute swap but epoch 5 not fully settled yet +- Attempt to claim +- Verify `claim_not_allowed` error +- Complete epoch settlement +- Now claim should succeed + +--- + +### 5. Multi-Pool Claim Scenarios + +#### `test_claim_spans_multiple_pools_single_user` +- User subscribes with due: 500 +- Swap 1: 200 from → 400 to +- Swap 2: 300 from → 600 to +- Claim should drain: + - From pool 1: 200 from → 400 to + - From pool 2: 300 from → 600 to +- Verify total received: 1000 +- Verify both pools consumed +- Verify `unsettled_swap_id` advances to 3 + +#### `test_claim_partial_pool_consumption` +- User 1 subscribes with due: 100 +- User 2 subscribes with due: 200 +- Swap 1: 300 from → 600 to +- Claim User 1: Takes 100 from → 200 to, pool remains (200 from, 400 to) +- Claim User 2: Takes remaining 200 from → 400 to, pool emptied +- Verify User 1 gets 200, User 2 gets 400 +- Verify pool state updated correctly after each claim + +#### `test_claim_multiple_swaps_before_claims` +- 4 subscribes (User 0-3) +- Swap 1: 100 from → 200 to +- Swap 2: 200 from → 400 to +- Swap 3: 300 from → 600 to +- All users have due: 150 each +- Claims in order: + - User 0: 100 from pool 1 (100 from → 200 to), 50 from pool 2 (50 from → 100 to) = 300 total + - User 1: 50 from pool 2 (50 from → 100 to), 100 from pool 3 (100 from → 200 to) = 300 total + - User 2: 200 from pool 3 (200 from → 400 to) = 400 total + - User 3: Claims from remaining pools +- Verify each user gets correct proportional amounts +- Verify pools consumed in order + +--- + +### 6. Fairness Scenarios (Different Due Amounts) + +#### `test_fairness_different_vault_due_amounts` +- User A subscribes: vault due = 100 +- User B subscribes: vault due = 99 (reporting loss) +- Swap: 199 from → 398 to (2:1 ratio) +- Claim User A: 100 * 398 / 199 = 200 +- Claim User B: 99 * 398 / 199 = 198 (rounded down) +- Verify User B gets exactly 99/100 of User A's amount +- Verify fairness: User B gets less because they received less from vault + +#### `test_fairness_across_multiple_pools` +- User A: due = 100 +- User B: due = 50 +- Swap 1: 50 from → 100 to (for User B) +- Swap 2: 100 from → 200 to (for User A) +- Claim User A: 100 from pool 2 → 200 +- Claim User B: 50 from pool 1 → 100 +- Verify User B gets exactly half of User A (proportional) + +#### `test_fairness_with_rounding_dust` +- User A: due = 100 +- User B: due = 100 +- Swap: 199 from → 399 to (rounding will leave dust) +- Claim User A: 100 * 399 / 199 = 200 (rounded) +- Claim User B: 99 * 399 / 199 = 198 (rounded) +- Verify remaining dust stays in pool or goes to last claimer +- Verify no value leakage + +--- + +### 7. Complex Ordering Scenarios + +#### `test_complex_ordering_4_subscribes_2_swaps_4_claims` +- Subscribe 4 NFTs (IDs 0-3) +- Swap 1: 200 from → 400 to (intended for NFTs 2-3, but available globally) +- Swap 2: 200 from → 400 to (intended for NFTs 0-1, but available globally) +- Claims in original sequence (0, 1, 2, 3): + - NFT 0: Claims from pool 1 (global head), drains 200 → 400 + - NFT 1: Claims from pool 2 (next), drains 200 → 400 + - NFT 2: Claims from pool 1 (if still available) or pool 2 + - NFT 3: Claims from remaining pools +- Verify all claims succeed sequentially +- Verify users get proportional amounts based on their vault due + +#### `test_swaps_between_subscribes` +- Subscribe NFT 0 +- Swap 1: 100 from → 200 to +- Subscribe NFT 1 +- Swap 2: 100 from → 200 to +- Subscribe NFT 2 +- Claim NFT 0: Gets from pool 1 +- Claim NFT 1: Gets from pool 2 (if pool 1 drained) or pool 1 (if available) +- Claim NFT 2: Gets from remaining pools +- Verify sequential claiming still works + +#### `test_claims_after_multiple_swaps` +- Subscribe 3 NFTs +- Swap 1: 100 from → 200 to +- Swap 2: 100 from → 200 to +- Swap 3: 100 from → 200 to +- Claim NFT 0: Drains pool 1 entirely or partially +- Claim NFT 1: Continues from where NFT 0 left off +- Claim NFT 2: Continues from remaining pools +- Verify global head advances correctly + +--- + +### 8. Edge Cases + +#### `test_claim_with_zero_due_assets` +- User subscribes but vault returns 0 due (mock `due_assets_from_id` to return 0) +- Attempt claim +- Verify `invalid_nft_id` error + +**Note**: This can happen if old NFT is already burnt but router hasn't processed it yet + +#### `test_claim_when_no_swaps_exist` +- Subscribe NFT +- Fulfill old NFT (burn it, mock due_assets = 0) +- Mock `due_assets_from_id` to return desired amount +- Attempt claim before any swaps +- Verify claim succeeds but returns 0 (no pools available, but old NFT is valid) + +#### `test_claim_insufficient_pools` +- User subscribes with due: 1000 +- Fulfill old NFT (burn it) +- Mock `due_assets_from_id` to return 1000 +- Swap: 100 from → 200 to +- Attempt claim +- Verify claim succeeds but only gets 200 (from available pool) +- Verify user can claim remaining 900 in future when more swaps occur + +#### `test_claim_empty_pools_skipped` +- Swap 1: 100 from → 200 to (fully consumed) +- Swap 2: 100 from → 200 to (fully consumed) +- Swap 3: 100 from → 200 to (available) +- User subscribes and claims +- Verify skips empty pools 1 and 2, uses pool 3 +- Verify `unsettled_swap_id` advances correctly + +#### `test_claim_invalid_nft_id` +- Attempt claim with non-existent NFT +- Verify `invalid_old_nft_id` error + +#### `test_claim_already_claimed_nft` +- Subscribe and claim NFT +- Attempt claim again +- Verify `nft_already_claimed` error (from `_validate_request_info`) + +#### `test_claim_reverts_on_insufficient_to_asset_balance` +- Subscribe and swap successfully +- Router has insufficient to_asset balance +- Attempt claim +- Verify `insufficient_balance_for_withdrawal` error + +--- + +### 9. Unsubscribe Function Tests + +#### `test_unsubscribe_original_nft_not_fulfilled` +- User subscribes +- Original NFT not fulfilled (router still owns it) +- Epoch not settled +- Call `unsubscribe(nft_id, receiver)` +- Verify original NFT transferred to receiver +- Verify new NFT burned +- Verify `Unsubscribed` event with `is_old_nft_returned = true` +- Verify `is_original_assets_returned = false` + +#### `test_unsubscribe_original_nft_fulfilled` +- User subscribes +- Original NFT fulfilled (burnt on redeem_request) +- Epoch not settled +- Router has from_asset balance +- Call `unsubscribe(nft_id, receiver)` +- Verify from_asset transferred to receiver (proportional to user's share) +- Verify new NFT burned +- Verify `Unsubscribed` event with `is_old_nft_returned = false` +- Verify `is_original_assets_returned = true` and `original_assets_returned` amount + +#### `test_unsubscribe_reverts_when_epoch_settled` +- User subscribes +- Epoch fully settled +- Attempt unsubscribe +- Verify `cannot_unsubscribe_partial_swap` error + +#### `test_unsubscribe_reverts_when_partial_settlement` +- User subscribes +- Original NFT fulfilled +- Epoch partially settled (some swaps occurred) +- Attempt unsubscribe +- Verify `cannot_unsubscribe_partial_swap` error + +#### `test_unsubscribe_reverts_when_not_owner` +- User subscribes +- Another user attempts to unsubscribe +- Verify `not_owner` error (router must own the NFT) + +#### `test_unsubscribe_reverts_on_insufficient_balance` +- User subscribes +- Original NFT fulfilled +- Router has insufficient from_asset balance +- Attempt unsubscribe +- Verify `insufficient_balance_for_withdrawal` error + +#### `test_unsubscribe_reverts_when_paused` +- Pause contract +- Attempt unsubscribe +- Verify revert + +--- + +### 10. Access Control & Authorization Tests + +#### `test_set_integrator_fee_recipient_only_owner` +- Non-owner attempts to set fee recipient +- Verify revert + +#### `test_set_integrator_fee_amount_bps_only_owner` +- Non-owner attempts to set fee BPS +- Verify revert + +#### `test_set_integrator_fee_recipient_zero_address` +- Owner attempts to set zero address +- Verify `zero_address` error + +#### `test_set_min_subscribe_amount_only_owner` +- Non-owner attempts to set min subscribe amount +- Verify revert + +#### `test_set_epoch_offset_only_relayer` +- Non-relayer attempts to set epoch offset +- Verify revert + +#### `test_swap_only_relayer` +- Non-relayer attempts swap +- Verify access control revert + +#### `test_report_only_relayer` +- Non-relayer attempts report +- Verify access control revert + +--- + +#### `test_subscribe_reverts_when_paused` +- Pause contract +- Attempt subscribe +- Verify revert + +#### `test_swap_reverts_when_paused` +- Pause contract +- Attempt swap +- Verify revert + +#### `test_claim_reverts_when_paused` +- Pause contract +- Attempt claim +- Verify revert + +--- + +### 11. Pausable Tests + +#### `test_subscribe_reverts_when_paused` +- Pause contract +- Attempt subscribe +- Verify revert + +#### `test_swap_reverts_when_paused` +- Pause contract +- Attempt swap +- Verify revert + +#### `test_claim_reverts_when_paused` +- Pause contract +- Attempt claim +- Verify revert + +#### `test_unsubscribe_reverts_when_paused` +- Pause contract +- Attempt unsubscribe +- Verify revert + +--- + +### 14. Integration Flow Tests + +#### `test_full_redemption_flow` +- User requests redemption from vault (mock) +- User subscribes to router with redemption NFT +- Relayer swaps assets +- User claims swapped assets +- Verify entire flow works end-to-end + +#### `test_batch_redemption_flow` +- Multiple users subscribe +- Multiple swaps executed +- All users claim in sequence +- Verify all receive correct proportional amounts + +#### `test_unsubscribe_flow` +- User subscribes +- User changes mind and unsubscribes before epoch settled +- Verify original NFT or assets returned correctly + +### 12. View Function Tests + +#### `test_view_functions_return_correct_values` +- Test all view functions: + - `vault()`, `redeem_request()`, `to_asset()`, `avnu_exchange()` + - `integrator_fee_recipient()`, `integrator_fee_amount_bps()` + - `swap_id()`, `unsettled_swap_id()` + - `new_nft_request_info()`, `swap_info()` + - `last_settled_epoch()`, `epoch_settled_amounts(epoch)` + - `expected_receivable(nft_id)`, `last_nft_id()` +- Verify all return expected values + +--- + +### 13. Epoch Settlement & Sync Tests + +#### `test_settle_epochs_from_amount` +- Multiple users subscribe to different epochs +- Execute swap with from_amount +- Verify epochs are settled in order (skipping epochs without subscriptions) +- Verify `epoch_settled_amounts` updated correctly +- Verify `last_settled_epoch` updated + +#### `test_settle_epochs_skips_empty_epochs` +- Users subscribe to epochs 1, 3, 5 (epochs 2, 4 have no subscriptions) +- Execute swap +- Verify epochs 1, 3, 5 are settled +- Verify epochs 2, 4 are skipped + +#### `test_sync_settled_epochs` +- Multiple epochs with subscriptions +- Some epochs fully settled, some partially settled +- Call `sync_settled_epochs(max_epochs_to_check)` +- Verify `last_settled_epoch` updated to highest fully settled epoch +- Verify respects `max_epochs_to_check` limit + +#### `test_sync_settled_epochs_with_limit` +- Many epochs to check +- Call `sync_settled_epochs(10)` to limit to 10 epochs +- Verify only checks up to 10 epochs +- Verify doesn't run out of gas + +#### `test_sync_settled_epochs_reverts_when_paused` +- Pause contract +- Attempt sync_settled_epochs +- Verify revert + +--- + +### 14. Integration Flow Tests + +#### `test_full_redemption_flow` +- User requests redemption from vault (mock) +- User subscribes to router with redemption NFT +- Relayer swaps assets +- User claims swapped assets +- Verify entire flow works end-to-end + +#### `test_batch_redemption_flow` +- Multiple users subscribe +- Multiple swaps executed +- All users claim in sequence +- Verify all receive correct proportional amounts + +#### `test_unsubscribe_flow` +- User subscribes +- User changes mind and unsubscribes before epoch settled +- Verify original NFT or assets returned correctly + +--- + +## Test Utilities Needed + +### Helper Functions +- `deploy_redemption_router()`: Deploy router with all dependencies (vault, redeem_request, to_asset, avnu_exchange addresses, integrator_fee_recipient, integrator_fee_amount_bps, min_subscribe_amount) +- `deploy_redeem_request(dummy_vault_address)`: Deploy actual RedeemRequest contract +- `deploy_mock_avnu_exchange()`: Deploy mock Avnu exchange from vault_allocator mocks +- `deploy_erc20_mock(name, symbol, initial_supply)`: Deploy ERC20 mock for from_asset/to_asset +- `setup_test_environment()`: Complete test setup with all contracts +- `execute_unsubscribe(router, nft_id, receiver)`: Helper to execute unsubscribe (requires router to own NFT) +- `mint_old_nft_to_user(user, redeem_request_info)`: + - Cheat caller as vault + - Mint NFT to user via redeem_request.mint() + - Return minted NFT ID +- `fulfill_old_nft(old_nft_id, asset_amount)`: + - Transfer asset_amount from vault to router (from_asset tokens) + - Cheat caller as vault → burn old NFT on redeem_request + - Mock `due_assets_from_id` to return 0 (since NFT is burnt) +- `mock_vault_due_assets(old_nft_id, amount)`: + - Use `mock_call` to set `due_assets_from_id` return value + - Use `mock_call(VAULT_ADDRESS, selector!("due_assets_from_id"), amount, 1)` +- `mock_vault_asset(asset_address)`: + - Use `mock_call` to set `asset()` return value +- `execute_swap(router, from_amount, to_amount, routes)`: Helper to execute swap (requires relayer role) + +--- + +## Test File Structure + +``` +packages/vault/src/redemption_router/ +└── test/ + └── redemption_router_test.cairo + +packages/vault_allocator/src/mocks/ +├── mock_avnu_exchange.cairo (for Avnu exchange mocking) +├── erc20.cairo (for ERC20 token mocking) +└── (other existing mocks) +``` + +## Mock Call Cheatcode Reference + +From [Starknet Foundry Documentation](https://foundry-rs.github.io/starknet-foundry/appendix/cheatcodes/mock_call.html): + +- `mock_call(contract_address, function_selector, ret_data, n_times)`: Mock a function call for `n_times` calls +- `start_mock_call(contract_address, function_selector, ret_data)`: Mock a function call indefinitely +- `stop_mock_call(contract_address, function_selector)`: Stop mocking a function + +Example usage: +```cairo +use snforge_std::{mock_call, start_mock_call, stop_mock_call}; + +// Mock due_assets_from_id to return 100 for 1 call +let due_amount: u256 = 100; +mock_call(VAULT_ADDRESS, selector!("due_assets_from_id"), due_amount, 1); + +// Mock asset() indefinitely +start_mock_call(VAULT_ADDRESS, selector!("asset"), from_asset_address); +``` + +--- + +## Notes + +1. **Epoch-Based Claiming**: Claims require the epoch to be fully settled. Users can only claim after their epoch is settled. +2. **Global Head**: All claims start from `unsettled_swap_id` (global head) +3. **Fairness**: Users with less vault due should get proportionally less swapped assets +4. **Rounding**: Use floor rounding for calculations, verify no value leakage +5. **Events**: Verify all events are emitted with correct parameters: + - `Subscribed`: `new_nft_id`, `old_nft_id`, `receiver` + - `Swapped`: `swap_id`, `from_amount`, `to_amount`, `last_settled_epoch` + - `Claimed`: `new_nft_id`, `old_nft_id`, `receivable`, `swap_id` + - `Unsubscribed`: `new_nft_id`, `old_nft_id`, `owner`, `is_old_nft_returned`, `is_original_assets_returned`, `original_assets_returned` +6. **State Updates**: Verify storage is updated correctly after each operation +7. **Mock Call Limitations**: + - `mock_call` only works for entry point calls, not internal calls + - Use `n_times = 1` for single-call scenarios, or `start_mock_call` for indefinite mocking + - Remember to mock `due_assets_from_id` AFTER burning old NFT (should return 0) or BEFORE (should return desired amount) +8. **Old NFT State & Due Assets Mocking**: + - **During subscription**: Old NFT exists, not burnt yet. `due_assets_from_id` returns non-zero (used for validation) + - **After fulfillment**: Old NFT is burnt on redeem_request (vault called `burn`). + - **During claim**: Router calls `vault.due_assets_from_id(old_nft_id)` to get what user was owed. + - **Mock this** to return the historical due amount (e.g., 100 tokens) even though NFT is burnt + - This represents "what was the user owed before redemption was fulfilled" + - Router uses this to calculate proportional swap amounts + - If old NFT is NOT burnt and we try to claim: Mock `due_assets_from_id` to return non-zero + - If old NFT IS burnt and we try to claim: Mock `due_assets_from_id` to return the historical amount (not 0, unless user was owed 0) +9. **Test Flow Order**: + - Setup contracts → Mint old NFT to user → User subscribes → Fulfill old NFT (burn) → Execute swaps → Mock `due_assets_from_id` with historical amount → Claim +10. **Epoch Settlement**: + - Epochs are settled in order (1, 2, 3...), but only epochs with subscriptions matter + - Epochs without subscriptions are skipped + - `last_settled_epoch` tracks the highest fully settled epoch (may not be sequential) +11. **Unsubscribe**: + - Requires router to own the NFT (not the original subscriber) + - Returns original NFT if not fulfilled, or from_assets if fulfilled + - Cannot unsubscribe if epoch is settled or partially settled +12. **Min Subscribe Amount**: + - Subscriptions below `min_subscribe_amount` are rejected to save gas + - Set by owner via `set_min_subscribe_amount` + diff --git a/packages/vault/src/redemption_router/errors.cairo b/packages/vault/src/redemption_router/errors.cairo new file mode 100644 index 00000000..601e116e --- /dev/null +++ b/packages/vault/src/redemption_router/errors.cairo @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +pub mod Errors { + pub fn zero_address() { + panic!("Zero address"); + } + + pub fn invalid_swap_id() { + panic!("Invalid swap id"); + } + + pub fn swap_not_settled() { + panic!("Swap not settled"); + } + + pub fn insufficient_from_amount() { + panic!("Insufficient from amount"); + } + + pub fn invalid_nft_id() { + panic!("Invalid NFT id"); + } + + pub fn invalid_old_nft_id() { + panic!("Invalid old NFT id"); + } + + pub fn claim_not_allowed() { + panic!("Claim not allowed"); + } + + pub fn swap_failed() { + panic!("Swap failed"); + } + + pub fn nft_already_claimed() { + panic!("NFT already claimed"); + } + + pub fn nft_already_withdrawn() { + panic!("NFT already withdrawn"); + } + + pub fn insufficient_balance_for_withdrawal() { + panic!("Insufficient balance for withdrawal"); + } + + pub fn cannot_unsubscribe_partial_swap() { + panic!("Cannot unsubscribe: swaps have partially consumed assets"); + } + + pub fn invalid_swap_from_amount() { + panic!("Invalid swap from amount"); + } + + pub fn too_small_subscribe_amount() { + panic!("Too small subscribe amount"); + } + + pub fn not_owner() { + panic!("Not owner"); + } +} + + diff --git a/packages/vault/src/redemption_router/interface.cairo b/packages/vault/src/redemption_router/interface.cairo new file mode 100644 index 00000000..7f755df3 --- /dev/null +++ b/packages/vault/src/redemption_router/interface.cairo @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; + +#[derive(Drop, Copy, starknet::Store, Serde, starknet::Event)] +pub struct RequestInfo { + pub old_nft_id: u256, + pub is_claimed: bool, + pub epoch: u256, + pub due_amount_approximate: u256, + pub unsubscribed: bool, // if true, the original NFT has been unsubscribed +} + +#[starknet::interface] +pub trait IRedemptionRouter { + fn subscribe(ref self: TContractState, nft_id: u256, receiver: ContractAddress) -> u256; + fn swap( + ref self: TContractState, + routes: Array, + from_amount: u256, + min_amount_out: u256, + ) -> u256; + fn claim(ref self: TContractState, nft_id: u256) -> u256; + + // Transfer the original NFT to receiver (contract is the owner itself) + // Reverts if epoch is fully or partially settled in swap + fn unsubscribe_for_nft(ref self: TContractState, nft_id: u256, receiver: ContractAddress); + + // Return underlying assets proportional to user's share + // Checks if epoch < handled_epoch_len, computes assets based on current offset factor + // Reverts if epoch is fully or partially settled in swap + fn unsubscribe_for_underlying(ref self: TContractState, nft_id: u256, receiver: ContractAddress); + + // Setters + fn set_integrator_fee_recipient(ref self: TContractState, recipient: ContractAddress); + fn set_integrator_fee_amount_bps(ref self: TContractState, fee_bps: u128); + fn set_epoch_offset(ref self: TContractState, epoch: u256, offset_factor: u256); + fn report(ref self: TContractState, new_aum: u256); + fn set_min_subscribe_amount(ref self: TContractState, min_subscribe_amount: u256); + + // View functions + fn vault(self: @TContractState) -> ContractAddress; + fn redeem_request(self: @TContractState) -> ContractAddress; + fn to_asset(self: @TContractState) -> ContractAddress; + fn avnu_exchange(self: @TContractState) -> ContractAddress; + fn integrator_fee_recipient(self: @TContractState) -> ContractAddress; + fn integrator_fee_amount_bps(self: @TContractState) -> u128; + fn swap_id(self: @TContractState) -> u256; + fn unsettled_swap_id(self: @TContractState) -> u256; + fn new_nft_request_info(self: @TContractState, new_nft_id: u256) -> RequestInfo; + fn swap_info(self: @TContractState, swap_id: u256) -> (u256, u256); // Returns (from_amount, to_amount) + fn last_nft_id(self: @TContractState) -> u256; + fn expected_receivable(self: @TContractState, nft_id: u256) -> u256; + fn last_settled_epoch(self: @TContractState) -> u256; + fn epoch_settled_amounts(self: @TContractState, epoch: u256) -> u256; + + // Sync settled epochs state by checking which epochs are fully settled + // max_epochs_to_check limits how many epochs to check to prevent excessive gas usage + // - swap function also call this, but incase it goes of out gas, caller can call this to sync settled epochs + fn sync_settled_epochs(ref self: TContractState, max_epochs_to_check: u256); + + fn pause(ref self: TContractState); + fn unpause(ref self: TContractState); +} + + diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo new file mode 100644 index 00000000..5494b8cc --- /dev/null +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -0,0 +1,1010 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +// Helps users redeem their NFTs in a different asset than the one configured in the vault +#[starknet::contract] +pub mod RedemptionRouter { + // Role constants + pub const OWNER_ROLE: felt252 = selector!("OWNER_ROLE"); + pub const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE"); + pub const RELAYER_ROLE: felt252 = selector!("RELAYER_ROLE"); + + // Mathematical constants + pub const WAD: u256 = 1_000_000_000_000_000_000; // 1e18 + + use core::num::traits::Zero; + use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; + use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; + use openzeppelin::interfaces::upgrades::IUpgradeable; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::security::pausable::PausableComponent; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use openzeppelin::utils::math; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, + }; + use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use vault::redemption_router::errors::Errors; + use vault::redemption_router::interface::{IRedemptionRouter, RequestInfo}; + use vault::redeem_request::interface::{IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait}; + use vault::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; + use vault_allocator::integration_interfaces::avnu::IAvnuExchangeDispatcher; + use vault_allocator::integration_interfaces::avnu::IAvnuExchangeDispatcherTrait; + + // --- OpenZeppelin Component Integrations --- + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + #[abi(embed_v0)] + impl AccessControlImpl = AccessControlComponent::AccessControlImpl; + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + access_control: AccessControlComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pausable: PausableComponent::Storage, + + // constants for the redemption router + vault: ContractAddress, + redeem_request: ContractAddress, + to_asset: ContractAddress, + avnu_exchange: ContractAddress, + + // modifiable parameters + integrator_fee_recipient: ContractAddress, + integrator_fee_amount_bps: u128, + + // state variables + swap_id: u256, + unsettled_swap_id: u256, + nft_id_counter: u256, // Counter for new NFT IDs + new_nft_request_map: Map, + swap_info: Map, // swap_id -> (from_remaining, to_remaining) + + // Epoch offset tracking + epoch_offset_factor: Map, // epoch -> offset_factor (defaults to WAD) + epoch_redeem_assets: Map, // epoch -> snapshot of redeem_assets at subscribe time + epoch_redeem_nominal: Map, // epoch -> snapshot of redeem_nominal at subscribe time + epoch_wise_nominals: Map, // epoch -> from_amount (nominal amount subscribed for this epoch) + epoch_settled_amounts: Map, // epoch -> actual settled from_amount received + last_settled_epoch: u256, // Highest epoch number that has been fully settled (may not be sequential if some epochs have no subscriptions) + old_nft_fulfilled: Map, // old_nft_id -> whether the old NFT has been fulfilled (burned) + + min_subscribe_amount: u256, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + PausableEvent: PausableComponent::Event, + Subscribed: Subscribed, + Swapped: Swapped, + Claimed: Claimed, + RequestInfo: RequestInfo, + Unsubscribed: Unsubscribed, + } + + #[derive(Drop, starknet::Event)] + pub struct Subscribed { + pub new_nft_id: u256, + pub old_nft_id: u256, + pub receiver: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + pub struct Unsubscribed { + pub new_nft_id: u256, + pub old_nft_id: u256, + pub owner: ContractAddress, + pub is_old_nft_returned: bool, // true if old NFT returned as is + pub is_original_assets_returned: bool, // true if original assets returned proportional to user's share + pub original_assets_returned: u256, // original assets returned proportional to user's share + } + + #[derive(Drop, starknet::Event)] + pub struct Swapped { + pub swap_id: u256, + pub from_amount: u256, + pub to_amount: u256, + pub last_settled_epoch: u256, + } + + #[derive(Drop, starknet::Event)] + pub struct Claimed { + pub new_nft_id: u256, + pub old_nft_id: u256, + pub receivable: u256, + pub swap_id: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + vault: ContractAddress, + redeem_request: ContractAddress, + to_asset: ContractAddress, + avnu_exchange: ContractAddress, + integrator_fee_recipient: ContractAddress, + integrator_fee_amount_bps: u128, + min_subscribe_amount: u256, + ) { + // Non-zero checks + if (vault.is_zero()) { + Errors::zero_address(); + } + if (redeem_request.is_zero()) { + Errors::zero_address(); + } + if (to_asset.is_zero()) { + Errors::zero_address(); + } + if (avnu_exchange.is_zero()) { + Errors::zero_address(); + } + if (integrator_fee_recipient.is_zero()) { + Errors::zero_address(); + } + + // Initialize components + self.erc721.initializer("RedemptionRouter", "RR", "none"); + self.access_control.initializer(); + + // Set up role hierarchy - OWNER_ROLE is admin for all roles + self.access_control.set_role_admin(OWNER_ROLE, OWNER_ROLE); + self.access_control.set_role_admin(PAUSER_ROLE, OWNER_ROLE); + self.access_control.set_role_admin(RELAYER_ROLE, OWNER_ROLE); + // Initialize NFT counter + self.nft_id_counter.write(0); + // Grant owner role to owner + self.access_control._grant_role(OWNER_ROLE, owner); + // Store addresses + self.vault.write(vault); + self.redeem_request.write(redeem_request); + self.to_asset.write(to_asset); + self.avnu_exchange.write(avnu_exchange); + self.integrator_fee_recipient.write(integrator_fee_recipient); + self.integrator_fee_amount_bps.write(integrator_fee_amount_bps); + // Initialize swap counters (starting from 1) + self.swap_id.write(1); + self.unsettled_swap_id.write(1); + // Read the latest handled epoch from the vault + let vault_dispatcher = IVaultDispatcher { contract_address: self.vault.read() }; + let latest_handled_epoch = vault_dispatcher.handled_epoch_len(); + if (latest_handled_epoch > 0) { + self.last_settled_epoch.write(latest_handled_epoch - 1); + } + self.min_subscribe_amount.write(min_subscribe_amount); + } + + // Internal implementation for helper functions + #[generate_trait] + impl RedemptionRouterInternalImpl of RedemptionRouterInternalTrait { + fn _transfer_original_nft(self: @ContractState, nft_id: u256, from_address: ContractAddress, to_address: ContractAddress) { + let redeem_request_dispatcher = ERC721ABIDispatcher { + contract_address: self.redeem_request.read(), + }; + redeem_request_dispatcher.transfer_from(from_address, to_address, nft_id); + } + + // Get effective offset factor for an epoch (defaults to WAD if 0) + fn _get_effective_offset_factor(self: @ContractState, epoch: u256) -> u256 { + let offset_factor = self.epoch_offset_factor.read(epoch); + if (offset_factor == 0) { WAD } else { offset_factor } + } + + // Calculate expected settled amount for an epoch + fn _calculate_expected_settled(self: @ContractState, epoch: u256) -> u256 { + let epoch_nominal = self.epoch_redeem_nominal.read(epoch); + if (epoch_nominal == 0) { + return 0; + } + let effective_offset = self._get_effective_offset_factor(epoch); + math::u256_mul_div( + epoch_nominal, + effective_offset, + WAD, + math::Rounding::Floor + ) + } + + // Check if an epoch is fully settled + fn _is_epoch_settled(self: @ContractState, epoch: u256) -> bool { + let expected_settled = self._calculate_expected_settled(epoch); + if (expected_settled == 0) { + return false; + } + let settled_amount = self.epoch_settled_amounts.read(epoch); + settled_amount >= expected_settled + } + + // Calculate adjusted due amount with epoch offset factor + fn _calculate_adjusted_due_amount(self: @ContractState, stored_due_amount: u256, epoch: u256) -> u256 { + let effective_offset = self._get_effective_offset_factor(epoch); + math::u256_mul_div( + stored_due_amount, + effective_offset, + WAD, + math::Rounding::Floor + ) + } + + // Validate request info (not claimed, not unsubscribed, valid) + fn _validate_request_info(self: @ContractState, request_info: RequestInfo) { + if (request_info.is_claimed) { + Errors::nft_already_claimed(); + } + if (request_info.unsubscribed) { + Errors::nft_already_withdrawn(); + } + if (request_info.due_amount_approximate == 0) { + Errors::invalid_nft_id(); + } + } + + // Validate epoch exists + fn _validate_epoch(self: @ContractState, epoch: u256) { + let epoch_nominal = self.epoch_redeem_nominal.read(epoch); + if (epoch_nominal == 0) { + Errors::invalid_nft_id(); + } + } + + fn _update_request_info(ref self: ContractState, nft_id: u256, request_info: RequestInfo) { + self.new_nft_request_map.write(nft_id, request_info); + self.emit(request_info); + } + + // Snapshot epoch data if not already stored + fn _snapshot_epoch_data(ref self: ContractState, epoch: u256, vault_dispatcher: IVaultDispatcher) { + let redeem_assets = vault_dispatcher.redeem_assets(epoch); + let redeem_nominal = vault_dispatcher.redeem_nominal(epoch); + + self.epoch_redeem_assets.write(epoch, redeem_assets); + self.epoch_redeem_nominal.write(epoch, redeem_nominal); + + // Initialize epoch_offset_factor to WAD if not set + if (self.epoch_offset_factor.read(epoch) == 0) { + self.epoch_offset_factor.write(epoch, WAD); + } + } + + // Process swap pool iteration to calculate receivable (read-only) + fn _calculate_receivable_from_pools( + self: @ContractState, + mut remaining_due: u256 + ) -> u256 { + let mut pool_id = self.unsettled_swap_id.read(); + let end_pool = self.swap_id.read(); + let mut total_to: u256 = 0; + + while (remaining_due > 0 && pool_id < end_pool) { + let (from_remaining, to_remaining) = self.swap_info.read(pool_id); + + if (from_remaining == 0) { + pool_id = pool_id + 1; + continue; + } + + let take_from = if (remaining_due > from_remaining) { from_remaining } else { remaining_due }; + let take_to = math::u256_mul_div(take_from, to_remaining, from_remaining, math::Rounding::Floor); + + remaining_due = remaining_due - take_from; + total_to = total_to + take_to; + pool_id = pool_id + 1; + } + + total_to + } + + // Process swap pool iteration and update state (write) + fn _process_swap_pools_for_claim( + ref self: ContractState, + mut remaining_due: u256 + ) -> u256 { + let mut pool_id = self.unsettled_swap_id.read(); + let end_pool = self.swap_id.read(); + let mut total_to: u256 = 0; + + while (remaining_due > 0 && pool_id < end_pool) { + let (from_remaining, to_remaining) = self.swap_info.read(pool_id); + + if (from_remaining == 0) { + if (self.unsettled_swap_id.read() == pool_id) { + self.unsettled_swap_id.write(pool_id + 1); + } + pool_id = pool_id + 1; + continue; + } + + let take_from = if (remaining_due > from_remaining) { from_remaining } else { remaining_due }; + let take_to = math::u256_mul_div(take_from, to_remaining, from_remaining, math::Rounding::Floor); + + let new_from = from_remaining - take_from; + let new_to = to_remaining - take_to; + self.swap_info.write(pool_id, (new_from, new_to)); + + if (new_from == 0 && self.unsettled_swap_id.read() == pool_id) { + self.unsettled_swap_id.write(pool_id + 1); + } + + remaining_due = remaining_due - take_from; + total_to = total_to + take_to; + pool_id = pool_id + 1; + } + + total_to + } + + // todo what if a subscription comes in after an epoch was handled but redeem not claimed? + + // Settle epochs and/or sync settled epochs state + // - If remaining_from > 0: settles epochs by allocating remaining_from to them + // - After settling (or if remaining_from == 0): syncs by checking which epochs are fully settled + // - max_epochs_to_check limits how many epochs to check during sync (0 means check all) + // Epochs are processed in order (1, 2, 3...), but only epochs with subscriptions matter + // Subscriptions can come in any order, so we skip epochs without subscriptions + fn _settle_and_sync_epochs( + ref self: ContractState, + mut remaining_from: u256, + max_epochs_to_check: u256 + ) -> u256 { + // Get the vault's current epoch to know the upper bound + let vault_dispatcher = IVaultDispatcher { contract_address: self.vault.read() }; + let max_epoch = vault_dispatcher.handled_epoch_len() - 1; // -1 because we are using 0-indexed epochs + + // Start from last_settled_epoch + 1 to avoid re-checking already settled epochs + let last_settled_epoch = self.last_settled_epoch.read(); + let mut current_epoch = last_settled_epoch + 1; + let mut highest_settled_epoch: u256 = last_settled_epoch; + let mut epochs_checked: u256 = 0; + + // If max_epochs_to_check is 0, check all epochs (no limit) + let check_all = max_epochs_to_check == 0; + + // Phase 1: Settle epochs with remaining_from (if any) + while (remaining_from > 0 && current_epoch <= max_epoch) { + let epoch_nominal = self.epoch_redeem_nominal.read(current_epoch); + + epochs_checked = epochs_checked + 1; + // designed to prevent excessive gas usage + if (!check_all && epochs_checked > max_epochs_to_check) { + break; + } + + // Skip epochs without subscriptions + if (epoch_nominal == 0) { + // since this function is intended to be called by the swap function, + // the assumption is there is atleast one epoch with subscriptions + // that is waiting to be settled + // - so we can continue to the next epoch, at some point we should + // update the last_settled_epoch + current_epoch = current_epoch + 1; + continue; + } + + let effective_offset = self._get_effective_offset_factor(current_epoch); + let expected_settled = math::u256_mul_div( + epoch_nominal, + effective_offset, + WAD, + math::Rounding::Floor + ); + + let already_settled = self.epoch_settled_amounts.read(current_epoch); + let remaining_to_settle = expected_settled - already_settled; + + // Skip if epoch is already fully settled + if (remaining_to_settle == 0) { + if (current_epoch > highest_settled_epoch) { + highest_settled_epoch = current_epoch; + } + current_epoch = current_epoch + 1; + continue; + } + + let settle_amount = if (remaining_from >= remaining_to_settle) { + remaining_to_settle + } else { + remaining_from + }; + + let new_settled = already_settled + settle_amount; + self.epoch_settled_amounts.write(current_epoch, new_settled); + remaining_from = remaining_from - settle_amount; + + // If epoch is now fully settled, update highest_settled_epoch + if (new_settled >= expected_settled) { + if (current_epoch > highest_settled_epoch) { + highest_settled_epoch = current_epoch; + } + current_epoch = current_epoch + 1; + } else { + // Epoch partially settled, stop here (don't move to next epoch) + break; + } + } + + // ideally, only sync until the last settled epoch + let mut last_settled_epoch = highest_settled_epoch; + + // but if called wanted to do check limited blocks, update till then + if (!check_all && current_epoch > highest_settled_epoch) { + last_settled_epoch = current_epoch - 1; + } + self.last_settled_epoch.write(last_settled_epoch); + + last_settled_epoch // return the last settled epoch + } + + // Execute swap via Avnu exchange + fn _execute_avnu_swap( + ref self: ContractState, + routes: Array, + from_amount: u256, + min_amount_out: u256, + ) -> u256 { + let from_asset_address = self._get_from_asset_address(); + let to_asset_address = self.to_asset.read(); + let avnu_exchange_address = self.avnu_exchange.read(); + let this = get_contract_address(); + + // Check balance + let erc20_dispatcher = ERC20ABIDispatcher { contract_address: from_asset_address }; + let balance = erc20_dispatcher.balance_of(this); + if (from_amount > balance) { + Errors::insufficient_from_amount(); + } + + // Get balance before swap + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset_address }; + let balance_before = to_asset_dispatcher.balance_of(this); + + // Approve Avnu exchange + erc20_dispatcher.approve(avnu_exchange_address, from_amount); + + // Execute swap + let avnu_dispatcher = IAvnuExchangeDispatcher { contract_address: avnu_exchange_address }; + let swapped = avnu_dispatcher.multi_route_swap( + from_asset_address, + from_amount, + to_asset_address, + 0, + min_amount_out, + this, + self.integrator_fee_amount_bps.read(), + self.integrator_fee_recipient.read(), + routes, + ); + + if (!swapped) { + Errors::swap_failed(); + } + + // Get actual amount received + let balance_after = to_asset_dispatcher.balance_of(this); + balance_after - balance_before + } + + // Get from asset address from vault + fn _get_from_asset_address(self: @ContractState) -> ContractAddress { + let vault_dispatcher = IERC4626Dispatcher { contract_address: self.vault.read() }; + vault_dispatcher.asset() + } + + // Handle unsubscribe when original NFT not fulfilled + fn _handle_unsubscribe_original_nft( + ref self: ContractState, + nft_id: u256, + request_info: RequestInfo, + receiver: ContractAddress, + ) { + let epoch = request_info.epoch; + if (self._is_epoch_settled(epoch)) { + Errors::cannot_unsubscribe_partial_swap(); + } + + self._transfer_original_nft(nft_id: request_info.old_nft_id, from_address: get_contract_address(), to_address: receiver); + + let mut updated = request_info; + updated.unsubscribed = true; + self._update_request_info(nft_id, updated); + self.erc721.burn(nft_id); + + self.emit(Unsubscribed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id, owner: receiver, is_old_nft_returned: true, is_original_assets_returned: false, original_assets_returned: 0 }); + } + + // Handle unsubscribe when original NFT fulfilled + fn _handle_unsubscribe_fulfilled_nft( + ref self: ContractState, + nft_id: u256, + request_info: RequestInfo, + receiver: ContractAddress, + ) { + let epoch = request_info.epoch; + if (self._is_epoch_settled(epoch)) { + Errors::cannot_unsubscribe_partial_swap(); + } + + let settled_amount = self.epoch_settled_amounts.read(epoch); + let expected_settled = self._calculate_expected_settled(epoch); + + if (settled_amount > 0 && settled_amount < expected_settled) { + Errors::cannot_unsubscribe_partial_swap(); + } + + // Compute assets based on current offset factor + // due_amount_approximate is the asset amount, adjust it by offset factor + let user_expected_amount = self._calculate_adjusted_due_amount(request_info.due_amount_approximate, epoch); + + let from_asset_address = self._get_from_asset_address(); + let erc20_dispatcher = ERC20ABIDispatcher { contract_address: from_asset_address }; + let this = get_contract_address(); + let contract_balance = erc20_dispatcher.balance_of(this); + + if (user_expected_amount > contract_balance) { + Errors::insufficient_balance_for_withdrawal(); + } + + erc20_dispatcher.transfer(receiver, user_expected_amount); + + let mut updated = request_info; + updated.unsubscribed = true; + self._update_request_info(nft_id, updated); + self.erc721.burn(nft_id); + + self.emit(Unsubscribed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id, owner: receiver, is_old_nft_returned: false, is_original_assets_returned: true, original_assets_returned: user_expected_amount }); + } + + // Validate common prerequisites for unsubscribe operations + fn _validate_unsubscribe_prerequisites( + self: @ContractState, + nft_id: u256, + ) -> RequestInfo { + let request_info = self.new_nft_request_map.read(nft_id); + + // Validate request info + self._validate_request_info(request_info); + + // Validate epoch exists + let epoch = request_info.epoch; + self._validate_epoch(epoch); + + // Validate owner is the owner of the NFT + let owner = self.erc721.owner_of(nft_id); + if (owner != get_caller_address()) { + Errors::not_owner(); + } + + request_info + } + + // Assert that epoch is not settled (fully or partially) in swap + fn _assert_epoch_not_settled(self: @ContractState, epoch: u256) { + if (self._is_epoch_settled(epoch)) { + Errors::cannot_unsubscribe_partial_swap(); + } + + let settled_amount = self.epoch_settled_amounts.read(epoch); + let expected_settled = self._calculate_expected_settled(epoch); + + if (settled_amount > 0 && settled_amount < expected_settled) { + Errors::cannot_unsubscribe_partial_swap(); + } + } + + // Finalize unsubscribe: mark as unsubscribed, burn NFT, and emit event + fn _finalize_unsubscribe( + ref self: ContractState, + nft_id: u256, + request_info: RequestInfo, + receiver: ContractAddress, + is_old_nft_returned: bool, + original_assets_returned: u256, + ) { + let mut updated = request_info; + updated.unsubscribed = true; + self._update_request_info(nft_id, updated); + self.erc721.burn(nft_id); + + self.emit(Unsubscribed { + new_nft_id: nft_id, + old_nft_id: request_info.old_nft_id, + owner: receiver, + is_old_nft_returned, + is_original_assets_returned: !is_old_nft_returned, + original_assets_returned + }); + } + } + + #[abi(embed_v0)] + impl RedemptionRouterImpl of IRedemptionRouter { + fn subscribe(ref self: ContractState, nft_id: u256, receiver: ContractAddress) -> u256 { + self.pausable.assert_not_paused(); + + let caller = get_caller_address(); + + // Read epoch and due_amount from old NFT before transferring + let redeem_request_interface = IRedeemRequestDispatcher { + contract_address: self.redeem_request.read(), + }; + let redeem_request_info = redeem_request_interface.id_to_info(nft_id); + let epoch = redeem_request_info.epoch; + + // Get due_amount from vault (before NFT is transferred/burned) + let vault_dispatcher = IVaultDispatcher { contract_address: self.vault.read() }; + let due_amount = vault_dispatcher.due_assets_from_id(nft_id); + if (due_amount == 0) { + // nothing to settle for any NFT with due_amount == 0 + Errors::invalid_nft_id(); + } + + // avoid processing very small subscriptions to save gas + if (due_amount < self.min_subscribe_amount.read()) { + Errors::too_small_subscribe_amount(); + } + + // Snapshot epoch data (required for fairly distributing redeemed assets to subscribers) + self._snapshot_epoch_data(epoch, vault_dispatcher); + + // Transfer original NFT from caller to this contract + self._transfer_original_nft(nft_id: nft_id, from_address: caller, to_address: get_contract_address()); + // Mark old NFT as not fulfilled yet (router owns it now) + self.old_nft_fulfilled.write(nft_id, false); + + // Mint new NFT to receiver + let new_nft_id = self.nft_id_counter.read(); + self.erc721.mint(receiver, new_nft_id); + self.nft_id_counter.write(new_nft_id + 1); + + // Store mapping with epoch and due_amount_approximate + let request_info = RequestInfo { + old_nft_id: nft_id, + is_claimed: false, + epoch, + due_amount_approximate: due_amount, + unsubscribed: false, + }; + self._update_request_info(new_nft_id, request_info); + + // Update epoch_wise_nominals (accumulate nominal for this epoch) + // Use to compute how much of the epoch is settled + let current_epoch_amount = self.epoch_wise_nominals.read(epoch); + self.epoch_wise_nominals.write(epoch, current_epoch_amount + redeem_request_info.nominal); + + // Emit event + self.emit(Subscribed { new_nft_id, old_nft_id: nft_id, receiver }); + + new_nft_id + } + + fn swap( + ref self: ContractState, + routes: Array, + from_amount: u256, + min_amount_out: u256, + ) -> u256 { + self.pausable.assert_not_paused(); + self.access_control.assert_only_role(RELAYER_ROLE); + + if (from_amount == 0) { + Errors::invalid_swap_from_amount(); + } + + // Execute swap via Avnu exchange + let to_amount = self._execute_avnu_swap(routes, from_amount, min_amount_out); + + // Settle epochs based on from_amount received + let last_settled_epoch = self._settle_and_sync_epochs(from_amount, 0); + + // Store swap info + let current_swap_id = self.swap_id.read(); + self.swap_info.write(current_swap_id, (from_amount, to_amount)); + self.swap_id.write(current_swap_id + 1); + + // Emit event + self.emit(Swapped { swap_id: current_swap_id, from_amount, to_amount, last_settled_epoch }); + + current_swap_id + } + + fn claim(ref self: ContractState, nft_id: u256) -> u256 { + self.pausable.assert_not_paused(); + + let owner = self.erc721.owner_of(nft_id); + let request_info = self.new_nft_request_map.read(nft_id); + + // Validate request info + self._validate_request_info(request_info); + + // Validate epoch exists + let epoch = request_info.epoch; + self._validate_epoch(epoch); + + // Check if epoch has been settled + if (!self._is_epoch_settled(epoch)) { + Errors::claim_not_allowed(); + } + + // Calculate adjusted due amount with epoch offset factor + let remaining_due = self._calculate_adjusted_due_amount(request_info.due_amount_approximate, epoch); + + // Process swap pools and calculate receivable + let total_to = self._process_swap_pools_for_claim(remaining_due); + + // Burn NFT and mark claimed + self.erc721.burn(nft_id); + let mut updated = request_info; + updated.is_claimed = true; + self._update_request_info(nft_id, updated); + + // Transfer payout + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: self.to_asset.read() }; + let this = get_contract_address(); + let router_balance = to_asset_dispatcher.balance_of(this); + if (total_to > router_balance) { + Errors::insufficient_balance_for_withdrawal(); + } + to_asset_dispatcher.transfer(owner, total_to); + + self.emit(Claimed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id, receivable: total_to, swap_id: self.unsettled_swap_id.read() }); + + total_to + } + + + fn unsubscribe_for_nft(ref self: ContractState, nft_id: u256, receiver: ContractAddress) { + self.pausable.assert_not_paused(); + + let request_info = self._validate_unsubscribe_prerequisites(nft_id); + let epoch = request_info.epoch; + + // Revert if epoch is fully or partially settled in swap + self._assert_epoch_not_settled(epoch); + + // Transfer original NFT to receiver + self._transfer_original_nft(nft_id: request_info.old_nft_id, from_address: get_contract_address(), to_address: receiver); + + // Finalize unsubscribe + self._finalize_unsubscribe(nft_id, request_info, receiver, true, 0); + } + + fn unsubscribe_for_underlying(ref self: ContractState, nft_id: u256, receiver: ContractAddress) { + self.pausable.assert_not_paused(); + + let request_info = self._validate_unsubscribe_prerequisites(nft_id); + let epoch = request_info.epoch; + + // Check if epoch < handled_epoch_len (meaning epoch has been handled by vault) + let vault_dispatcher = IVaultDispatcher { contract_address: self.vault.read() }; + let handled_epoch_len = vault_dispatcher.handled_epoch_len(); + + if (epoch >= handled_epoch_len) { + Errors::invalid_nft_id(); // Epoch not yet handled by vault + } + + // Revert if epoch is fully or partially settled in swap + self._assert_epoch_not_settled(epoch); + + // Compute assets based on current offset factor + // due_amount_approximate is the asset amount, adjust it by offset factor + let user_expected_amount = self._calculate_adjusted_due_amount(request_info.due_amount_approximate, epoch); + + // Return assets if contract has balance + let from_asset_address = self._get_from_asset_address(); + let erc20_dispatcher = ERC20ABIDispatcher { contract_address: from_asset_address }; + let this = get_contract_address(); + let contract_balance = erc20_dispatcher.balance_of(this); + + if (user_expected_amount > contract_balance) { + Errors::insufficient_balance_for_withdrawal(); + } + + erc20_dispatcher.transfer(receiver, user_expected_amount); + + // Finalize unsubscribe + self._finalize_unsubscribe(nft_id, request_info, receiver, false, user_expected_amount); + } + + fn set_integrator_fee_recipient(ref self: ContractState, recipient: ContractAddress) { + self.access_control.assert_only_role(OWNER_ROLE); + if (recipient.is_zero()) { + Errors::zero_address(); + } + self.integrator_fee_recipient.write(recipient); + } + + fn set_integrator_fee_amount_bps(ref self: ContractState, fee_bps: u128) { + self.access_control.assert_only_role(OWNER_ROLE); + self.integrator_fee_amount_bps.write(fee_bps); + } + + fn set_epoch_offset(ref self: ContractState, epoch: u256, offset_factor: u256) { + self.access_control.assert_only_role(RELAYER_ROLE); + self.epoch_offset_factor.write(epoch, offset_factor); + } + + fn report(ref self: ContractState, new_aum: u256) { + self.access_control.assert_only_role(RELAYER_ROLE); + + let vault_dispatcher = IVaultDispatcher { contract_address: self.vault.read() }; + + // Read handled_epoch_len before calling report + let handled_epochs_before = vault_dispatcher.handled_epoch_len(); + + // Call vault's report function + vault_dispatcher.report(new_aum); + + // Read handled_epoch_len after calling report + let handled_epochs_after = vault_dispatcher.handled_epoch_len(); + + // For each newly handled epoch, compute and update offset factor + let mut epoch = handled_epochs_before + 1; + while (epoch <= handled_epochs_after) { + // Read new redeem_assets and redeem_nominal after report + let new_redeem_assets = vault_dispatcher.redeem_assets(epoch); + let new_redeem_nominal = vault_dispatcher.redeem_nominal(epoch); + + // Read old snapshots + let old_redeem_assets = self.epoch_redeem_assets.read(epoch); + let old_redeem_nominal = self.epoch_redeem_nominal.read(epoch); + + // Compute new offset factor: WAD * (new_redeem_assets / new_redeem_nominal) / (old_redeem_assets / old_redeem_nominal) + // This simplifies to: WAD * new_redeem_assets * old_redeem_nominal / (new_redeem_nominal * old_redeem_assets) + if (old_redeem_assets > 0 && old_redeem_nominal > 0 && new_redeem_nominal > 0) { + // Compute numerator: WAD * new_redeem_assets * old_redeem_nominal + let numerator = WAD * new_redeem_assets * old_redeem_nominal; + // Compute denominator: new_redeem_nominal * old_redeem_assets + let denominator = new_redeem_nominal * old_redeem_assets; + // Compute: numerator / denominator + let new_offset_factor = math::u256_mul_div( + numerator, + 1, + denominator, + math::Rounding::Floor + ); + self.epoch_offset_factor.write(epoch, new_offset_factor); + } + + epoch = epoch + 1; + } + } + + fn set_min_subscribe_amount(ref self: ContractState, min_subscribe_amount: u256) { + self.access_control.assert_only_role(OWNER_ROLE); + self.min_subscribe_amount.write(min_subscribe_amount); + } + + fn pause(ref self: ContractState) { + self.access_control.assert_only_role(PAUSER_ROLE); + self.pausable.pause(); + } + + fn unpause(ref self: ContractState) { + self.access_control.assert_only_role(OWNER_ROLE); + self.pausable.unpause(); + } + + fn vault(self: @ContractState) -> ContractAddress { + self.vault.read() + } + + fn redeem_request(self: @ContractState) -> ContractAddress { + self.redeem_request.read() + } + + fn to_asset(self: @ContractState) -> ContractAddress { + self.to_asset.read() + } + + fn avnu_exchange(self: @ContractState) -> ContractAddress { + self.avnu_exchange.read() + } + + fn integrator_fee_recipient(self: @ContractState) -> ContractAddress { + self.integrator_fee_recipient.read() + } + + fn integrator_fee_amount_bps(self: @ContractState) -> u128 { + self.integrator_fee_amount_bps.read() + } + + fn swap_id(self: @ContractState) -> u256 { + self.swap_id.read() + } + + fn unsettled_swap_id(self: @ContractState) -> u256 { + self.unsettled_swap_id.read() + } + + fn new_nft_request_info(self: @ContractState, new_nft_id: u256) -> RequestInfo { + self.new_nft_request_map.read(new_nft_id) + } + + fn swap_info(self: @ContractState, swap_id: u256) -> (u256, u256) { + self.swap_info.read(swap_id) + } + + fn last_nft_id(self: @ContractState) -> u256 { + let counter = self.nft_id_counter.read(); + if (counter == 0) { + 0 + } else { + counter - 1 + } + } + + fn expected_receivable(self: @ContractState, nft_id: u256) -> u256 { + let request_info = self.new_nft_request_map.read(nft_id); + + // If already claimed or unsubscribed, return 0 + if (request_info.is_claimed || request_info.unsubscribed || request_info.due_amount_approximate == 0) { + return 0; + } + + // Calculate adjusted due amount with epoch offset factor + let remaining_due = self._calculate_adjusted_due_amount(request_info.due_amount_approximate, request_info.epoch); + + // Calculate receivable from swap pools (read-only) + self._calculate_receivable_from_pools(remaining_due) + } + + fn last_settled_epoch(self: @ContractState) -> u256 { + self.last_settled_epoch.read() + } + + fn epoch_settled_amounts(self: @ContractState, epoch: u256) -> u256 { + self.epoch_settled_amounts.read(epoch) + } + + // Useful when there are many empty epochs. + // Allows batching their settlement without running out of gas. + fn sync_settled_epochs(ref self: ContractState, max_epochs_to_check: u256) { + self.pausable.assert_not_paused(); + self._settle_and_sync_epochs(remaining_from: 0, max_epochs_to_check: max_epochs_to_check); + } + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { + self.access_control.assert_only_role(OWNER_ROLE); + self.upgradeable.upgrade(new_class_hash); + } + } +} + diff --git a/packages/vault/src/test/units/redemption_router.cairo b/packages/vault/src/test/units/redemption_router.cairo new file mode 100644 index 00000000..fdfbf28b --- /dev/null +++ b/packages/vault/src/test/units/redemption_router.cairo @@ -0,0 +1,1178 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use openzeppelin::interfaces::accesscontrol::{ + IAccessControlDispatcher, IAccessControlDispatcherTrait, +}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; +use snforge_std::{ + CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, + start_mock_call, store, map_entry_address, +}; +use starknet::ContractAddress; +use core::array::ArrayTrait; +use vault::redeem_request::interface::{ + IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait, RedeemRequestInfo, +}; +use vault::redemption_router::interface::{ + IRedemptionRouterDispatcher, IRedemptionRouterDispatcherTrait, +}; +use vault::redemption_router::redemption_router::RedemptionRouter; +use vault::test::utils::{OWNER, USER1, USER2, WAD, deploy_erc20_mock, deploy_redeem_request}; +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; +use vault_allocator::mocks::mock_avnu_exchange::IAvnuExchangeDispatcher; + +const RELAYER: ContractAddress = 0x1234567890.try_into().unwrap(); + +fn deploy_redemption_router( + vault: ContractAddress, + redeem_request: ContractAddress, + to_asset: ContractAddress, + avnu_exchange: ContractAddress, + integrator_fee_recipient: ContractAddress, + integrator_fee_amount_bps: u128, + min_subscribe_amount: u256, +) -> IRedemptionRouterDispatcher { + println!("deploying redemption router"); + let router = declare("RedemptionRouter").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + OWNER().serialize(ref calldata); + vault.serialize(ref calldata); + redeem_request.serialize(ref calldata); + to_asset.serialize(ref calldata); + avnu_exchange.serialize(ref calldata); + integrator_fee_recipient.serialize(ref calldata); + integrator_fee_amount_bps.serialize(ref calldata); + min_subscribe_amount.serialize(ref calldata); + println!("deploying redemption router with calldata"); + // let (router_address, _) = router.deploy(@calldata).unwrap(); + let res = router.deploy(@calldata); + match res { + Ok((router_address, _)) => IRedemptionRouterDispatcher { contract_address: router_address }, + Err(e) => { + let err = *e.at(2); + // to ensure exact error of panic is thrown + assert(false, err); + // just a fallback for compiling purposes + panic!("error deploying redemption router"); + } + } +} + +fn deploy_mock_avnu_exchange() -> IAvnuExchangeDispatcher { + let avnu = declare("MockAvnuExchange").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + let (avnu_address, _) = avnu.deploy(@calldata).unwrap(); + println!("avnu_address: {:?}", avnu_address); + IAvnuExchangeDispatcher { contract_address: avnu_address } +} + +fn set_up() -> ( + ContractAddress, // vault (dummy address) + ContractAddress, // from_asset + ContractAddress, // to_asset + IRedeemRequestDispatcher, // redeem_request + IAvnuExchangeDispatcher, // avnu_exchange + IRedemptionRouterDispatcher, // router +) { + let dummy_vault = 'DUMMY_VAULT'.try_into().unwrap(); + let from_asset = deploy_erc20_mock(); + let to_asset = deploy_erc20_mock(); + let redeem_request = deploy_redeem_request(dummy_vault); + let avnu_exchange = deploy_mock_avnu_exchange(); + println!("avnu_exchange deployed"); + let integrator_fee_recipient = 'FEE_RECIPIENT'.try_into().unwrap(); + let integrator_fee_amount_bps: u128 = 100; // 1% + let min_subscribe_amount: u256 = 0; // No minimum by default for tests + println!("integrator_fee_recipient and integrator_fee_amount_bps set"); + + // Mock vault's handled_epoch_len for constructor initialization + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 0_u256); + + let router = deploy_redemption_router( + dummy_vault, + redeem_request.contract_address, + to_asset, + avnu_exchange.contract_address, + integrator_fee_recipient, + integrator_fee_amount_bps, + min_subscribe_amount, + ); + + println!("router: {:?}", router.contract_address); + + // Grant RELAYER_ROLE and PAUSER_ROLE + let access_control = IAccessControlDispatcher { + contract_address: router.contract_address, + }; + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + access_control.grant_role(RedemptionRouter::RELAYER_ROLE, RELAYER); + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + access_control.grant_role(selector!("PAUSER_ROLE"), OWNER()); + println!("RELAYER_ROLE granted"); + (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) +} + +fn mint_old_nft_to_user( + redeem_request: IRedeemRequestDispatcher, vault_address: ContractAddress, user: ContractAddress, + epoch: u256, nominal: u256, +) -> u256 { + let redeem_request_info = RedeemRequestInfo { epoch, nominal }; + cheat_caller_address( + redeem_request.contract_address, vault_address, span: CheatSpan::TargetCalls(1), + ); + redeem_request.mint(user, redeem_request_info) +} + +fn fulfill_old_nft( + redeem_request: IRedeemRequestDispatcher, vault_address: ContractAddress, nft_id: u256, +) { + cheat_caller_address( + redeem_request.contract_address, vault_address, span: CheatSpan::TargetCalls(1), + ); + redeem_request.burn(nft_id); +} + +fn mark_old_nft_fulfilled(router_address: ContractAddress, old_nft_id: u256) { + // Mark old NFT as fulfilled in router's storage + let mut cheat_calldata_key = ArrayTrait::new(); + old_nft_id.serialize(ref cheat_calldata_key); + let mut cheat_calldata_value = ArrayTrait::new(); + true.serialize(ref cheat_calldata_value); + let map_entry = map_entry_address(selector!("old_nft_fulfilled"), cheat_calldata_key.span()); + store(router_address, map_entry, cheat_calldata_value.span()); +} + +// ============================================================================ +// 1. Constructor & Initialization Tests +// ============================================================================ + +#[test] +fn test_constructor_initializes_correctly() { + let (dummy_vault, _, to_asset, redeem_request, avnu_exchange, router) = set_up(); + println!("setup done"); + + // Verify addresses are stored correctly + assert(router.vault() == dummy_vault, 'Vault address incorrect'); + assert(router.redeem_request() == redeem_request.contract_address, 'Redeem request incorrect'); + assert(router.to_asset() == to_asset, 'To asset incorrect'); + assert(router.avnu_exchange() == avnu_exchange.contract_address, 'Avnu exchange incorrect'); + println!("addresses stored correctly"); + + // Verify roles are set + let access_control = IAccessControlDispatcher { + contract_address: router.contract_address, + }; + let has_owner_role = access_control.has_role(selector!("OWNER_ROLE"), OWNER()); + assert(has_owner_role, 'Owner role not set'); + println!("roles set"); + // Verify swap_id and unsettled_swap_id start at 1 + assert(router.swap_id() == 1, 'swap_id should start at 1'); + assert(router.unsettled_swap_id() == 1, 'unsettled_swap_id != 1'); + println!("swap_id and unsettled_swap_id start at 1"); + // Verify NFT counter starts at 0 (but we can't directly read it, so check via first mint) + // Actually, we can't verify this without minting, but the contract code shows it's initialized to 0 + println!("NFT counter starts at 0"); + // Verify NFT contract initialized + let erc721 = ERC721ABIDispatcher { contract_address: router.contract_address }; + assert(erc721.name() == "RedemptionRouter", 'NFT name incorrect'); + assert(erc721.symbol() == "RR", 'NFT symbol incorrect'); + + // Verify last_settled_epoch initialized (should be 0 when handled_epoch_len is 0) + assert(router.last_settled_epoch() == 0, 'last_settled_epoch should be 0'); +} + +#[test] +#[should_panic(expected: ('Zero address',))] +fn test_constructor_reverts_zero_vault() { + let zero_vault: ContractAddress = core::num::traits::Zero::zero(); + let to_asset = deploy_erc20_mock(); + // Note: deploy_redeem_request will fail with zero vault, so we skip it + // and pass zero directly to router constructor which should check it + let dummy_redeem_request: ContractAddress = 'DUMMY_RR'.try_into().unwrap(); + let avnu_exchange: ContractAddress = 'AVNU_EXCHANGE'.try_into().unwrap(); + let integrator_fee_recipient = 'FEE_RECIPIENT'.try_into().unwrap(); + deploy_redemption_router( + zero_vault, + dummy_redeem_request, + to_asset, + avnu_exchange, + integrator_fee_recipient, + 100, + 0, + ); +} + +#[test] +#[should_panic(expected: ('Zero address',))] +fn test_constructor_reverts_zero_redeem_request() { + let dummy_vault = 'DUMMY_VAULT'.try_into().unwrap(); + let to_asset = deploy_erc20_mock(); + let avnu_exchange: ContractAddress = 'AVNU_EXCHANGE'.try_into().unwrap(); + let integrator_fee_recipient = 'FEE_RECIPIENT'.try_into().unwrap(); + let zero_redeem_request: ContractAddress = core::num::traits::Zero::zero(); + deploy_redemption_router(dummy_vault, zero_redeem_request, to_asset, avnu_exchange, integrator_fee_recipient, 100, 0); +} + +#[test] +#[should_panic(expected: ('Zero address',))] +fn test_constructor_reverts_zero_to_asset() { + let dummy_vault = 'DUMMY_VAULT'.try_into().unwrap(); + let redeem_request: ContractAddress = 'DUMMY_RR'.try_into().unwrap(); + let avnu_exchange = 'AVNU_EXCHANGE'.try_into().unwrap(); + let integrator_fee_recipient = 'FEE_RECIPIENT'.try_into().unwrap(); + let zero_to_asset: ContractAddress = core::num::traits::Zero::zero(); + deploy_redemption_router( + dummy_vault, + redeem_request, + zero_to_asset, + avnu_exchange, + integrator_fee_recipient, + 100, + 0, + ); +} + +// ============================================================================ +// 2. Subscribe Function Tests +// ============================================================================ + +#[test] +fn test_subscribe_transfers_old_nft_and_mints_new() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // Mint old NFT to user + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let epoch: u256 = 1; + let nominal: u256 = 100; + let due_amount: u256 = WAD * nominal; // due_amount equals nominal in WAD + + // Mock vault functions needed by subscribe + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * nominal); // Total redeem assets for epoch + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * nominal); // Total redeem nominal for epoch + + // User approves router + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + + // User subscribes + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.subscribe(old_nft_id, USER1()); + + // Verify new NFT was minted + let router_erc721 = ERC721ABIDispatcher { contract_address: router.contract_address }; + assert(router_erc721.owner_of(new_nft_id) == USER1(), 'New NFT owner incorrect'); + + // Verify old NFT was transferred to router (on redeem_request contract) + let redeem_request_erc721 = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + assert(redeem_request_erc721.owner_of(old_nft_id) == router.contract_address, 'Old NFT not transferred'); + + // Verify mapping stored correctly + let request_info = router.new_nft_request_info(new_nft_id); + assert(request_info.old_nft_id == old_nft_id, 'Old NFT ID mapping incorrect'); + assert(request_info.is_claimed == false, 'is_claimed should be false'); + assert(request_info.epoch == epoch, 'Epoch stored incorrectly'); + assert(request_info.due_amount_approximate == due_amount, 'Due amount stored incorrectly'); + + // Verify new_nft_id is 0 (first NFT) + assert(new_nft_id == 0, 'First NFT ID should be 0'); +} + +#[test] +fn test_subscribe_increments_nft_counter() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // Subscribe first NFT + let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount_1: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_1); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); + assert(new_nft_id_1 == 0, 'First NFT ID should be 0'); + + // Subscribe second NFT + let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + let due_amount_2: u256 = WAD * 200; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); + // Note: redeem_assets and redeem_nominal should now include both users (300 total) + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); + + cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_2); + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); + assert(new_nft_id_2 == 1, 'Second NFT ID should be 1'); +} + +#[test] +#[should_panic(expected: ('Pausable: paused',))] +fn test_subscribe_reverts_when_paused() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // Pause contract + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.pause(); + + // Attempt subscribe + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.subscribe(old_nft_id, USER1()); +} + +#[test] +#[should_panic(expected: "Too small subscribe amount")] +fn test_subscribe_reverts_on_too_small_amount() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // Set min_subscribe_amount + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.set_min_subscribe_amount(WAD * 100); + + // Attempt subscribe with amount below minimum + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 50); // 50 < 100 + let due_amount: u256 = WAD * 50; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.subscribe(old_nft_id, USER1()); +} + +// ============================================================================ +// 3. Swap Function Tests +// ============================================================================ + +#[test] +fn test_swap_executes_successfully() { + let (dummy_vault, from_asset, to_asset, _, avnu_exchange, router) = set_up(); + + // Mock vault asset and epoch functions + // handled_epoch_len must be at least 1 to avoid underflow (even if no epochs are handled) + start_mock_call(dummy_vault, selector!("asset"), from_asset); + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 1_u256); // At least 1 to avoid underflow + start_mock_call(dummy_vault, selector!("epoch"), 0_u256); // Current epoch is 0 + + // Mint from_asset tokens to router + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + let router_address = router.contract_address; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router_address, WAD * 10); // 10 tokens + + // Mint to_asset tokens to mock exchange so it can transfer them back + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; + cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 100); + + // Execute swap + let routes: Array = array![]; + let from_amount: u256 = WAD * 5; // 5 tokens + let min_amount_out: u256 = WAD * 4; // 4 tokens (2:1 ratio for simplicity) + + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + let swap_id = router.swap(routes, from_amount, min_amount_out); + + // Verify swap_id is 1 (first swap) + assert(swap_id == 1, 'swap_id should be 1'); + + // Verify swap_info stores correct amounts + let (stored_from, stored_to) = router.swap_info(swap_id); + assert(stored_from == from_amount, 'Stored from_amount incorrect'); + assert(stored_to == min_amount_out, 'Stored to_amount incorrect'); + + // Verify swap_id incremented + assert(router.swap_id() == 2, 'swap_id should increment to 2'); +} + +#[test] +#[should_panic(expected: "Insufficient from amount")] +fn test_swap_reverts_on_insufficient_balance() { + let (dummy_vault, from_asset, _, _, _, router) = set_up(); + + // Mock vault asset + start_mock_call(dummy_vault, selector!("asset"), from_asset); + + // Don't mint any tokens to router (has 0 balance) + + // Attempt swap + let routes: Array = array![]; + let from_amount: u256 = WAD * 5; + let min_amount_out: u256 = WAD * 4; + + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.swap(routes, from_amount, min_amount_out); +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_swap_reverts_when_not_relayer() { + let (dummy_vault, from_asset, _, _, _, router) = set_up(); + + // Mock vault asset + start_mock_call(dummy_vault, selector!("asset"), from_asset); + + // Mint tokens to router + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 10); + + // Attempt swap as non-relayer + let routes: Array = array![]; + let from_amount: u256 = WAD * 5; + let min_amount_out: u256 = WAD * 4; + + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.swap(routes, from_amount, min_amount_out); +} + +#[test] +#[should_panic(expected: ('Pausable: paused',))] +fn test_swap_reverts_when_paused() { + let (dummy_vault, from_asset, _, _, _, router) = set_up(); + + // Pause contract + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.pause(); + + // Mock vault asset + start_mock_call(dummy_vault, selector!("asset"), from_asset); + + // Attempt swap + let routes: Array = array![]; + let from_amount: u256 = WAD * 5; + let min_amount_out: u256 = WAD * 4; + + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.swap(routes, from_amount, min_amount_out); +} + +// Note: test_swap_reverts_on_avnu_failure is skipped because: +// - The mock_avnu_exchange always returns true +// - To test failure, we would need a variant mock or different approach +// - The implementation correctly checks for swapped == false and reverts with "Swap failed" +// - This test case is documented in the test plan but requires mock modification to implement + +#[test] +fn test_swap_uses_actual_received_amount() { + let (dummy_vault, from_asset, to_asset, _, avnu_exchange, router) = set_up(); + + // Mock vault asset and epoch functions + // handled_epoch_len must be at least 1 to avoid underflow (even if no epochs are handled) + start_mock_call(dummy_vault, selector!("asset"), from_asset); + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 1_u256); // At least 1 to avoid underflow + + // Mint from_asset tokens to router + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 10); + + // Mint to_asset tokens to mock exchange so it can transfer them + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; + cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 100); + + // Get initial to_asset balance (not used but kept for reference) + let _initial_to_balance = to_asset_dispatcher.balance_of(router.contract_address); + + // Execute swap with min_amount_out = 4 + let routes: Array = array![]; + let from_amount: u256 = WAD * 5; + let min_amount_out: u256 = WAD * 4; + + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + let swap_id = router.swap(routes, from_amount, min_amount_out); + + // Verify swap_info stores actual received amount (balance delta) + let (stored_from, stored_to) = router.swap_info(swap_id); + assert(stored_from == from_amount, 'Stored from_amount incorrect'); + // Verify stored to_amount matches what was actually received (min_amount_out) + assert(stored_to == min_amount_out, 'Stored to_amount invalid'); +} + +// ============================================================================ +// 4. Basic Claim Scenarios +// ============================================================================ + +#[test] +fn test_claim_single_subscribe_single_swap_single_claim() { + let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + + // 1. Subscribe + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.subscribe(old_nft_id, USER1()); + + // 2. Fulfill old NFT (burn it) + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + + // 3. Transfer assets to router (simulate vault fulfilling redemption) + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 100); // 100 tokens + + // 4. Mint to_asset to mock exchange for swap + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; + cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 300); + + // 5. Swap + // handled_epoch_len should be 2 if epoch 1 is handled (epochs are 0-indexed, len is count) + start_mock_call(dummy_vault, selector!("asset"), from_asset); + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); // Epochs 0 and 1 handled + start_mock_call(dummy_vault, selector!("epoch"), 1_u256); + let routes: Array = array![]; + let from_amount: u256 = WAD * 100; + let min_amount_out: u256 = WAD * 200; // 2:1 ratio + + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.swap(routes, from_amount, min_amount_out); + + // 6. Claim (epoch should be settled now) + // Offset factor defaults to WAD, so due_amount remains the same + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let receivable = router.claim(new_nft_id); + + // Verify user received correct proportional amount: 100 * 200 / 100 = 200 + let expected_receivable = WAD * 200; + assert(receivable == expected_receivable, 'Receivable amount incorrect'); + + // Verify NFT is burned + let _pending_redeem_assetsrouter_erc721 = ERC721ABIDispatcher { contract_address: router.contract_address }; + // Should panic if trying to check owner of burned NFT, but we can check is_claimed + let request_info = router.new_nft_request_info(new_nft_id); + assert(request_info.is_claimed == true, 'NFT should be marked as claimed'); + + // Verify pool fully consumed, unsettled_swap_id advanced + assert(router.unsettled_swap_id() == 2, 'unsettled_swap_id != 2'); +} + +#[test] +fn test_claim_two_subscribes_one_swap_two_claims() { + let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + + // 1. Two subscribes + let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + + // User 1 subscribes + let due_amount_1: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_1); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); + + // User 2 subscribes + let due_amount_2: u256 = WAD * 200; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); // Total for both users + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); + + cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_2); + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); + + // 2. Fulfill both old NFTs + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_1); + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_2); + + // 3. Transfer assets to router + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 300); // 300 tokens total + + // 4. Mint to_asset to mock exchange + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; + cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 1000); + + // 5. One swap: 300 from → 600 to (2:1 ratio) + // handled_epoch_len should be 2 if epoch 1 is handled (epochs are 0-indexed, len is count) + start_mock_call(dummy_vault, selector!("asset"), from_asset); + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); // Epochs 0 and 1 handled + start_mock_call(dummy_vault, selector!("epoch"), 1_u256); + let routes: Array = array![]; + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.swap(routes, WAD * 300, WAD * 600); + + // 6. Claim User 1: due = 100, should get 100 * 600 / 300 = 200 + // (no need to mock due_assets_from_id - it's stored in RequestInfo) + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let receivable_1 = router.claim(new_nft_id_1); + assert(receivable_1 == WAD * 200, 'User 1 receivable incorrect'); + + // Verify swap info updated correctly + let (from_rem, to_rem) = router.swap_info(1); + println!("from_rem: {}", from_rem); + println!("to_rem: {}", to_rem); + assert(from_rem == WAD * 200, 'Remaining from_amount incorrect'); + assert(to_rem == WAD * 400, 'Remaining to_amount incorrect'); + + // 7. Claim User 2: due = 200, should get 200 * 600 / 300 = 400 + // But since pool has remaining: 200 from, 400 to, user gets 400 + // (no need to mock due_assets_from_id - it's stored in RequestInfo) + + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + let receivable_2 = router.claim(new_nft_id_2); + assert(receivable_2 == WAD * 400, 'User 2 receivable incorrect'); + + // Verify pool fully consumed + assert(router.unsettled_swap_id() == 2, 'unsettled_swap_id != 2'); +} + +#[test] +#[should_panic(expected: "Claim not allowed")] +fn test_claim_requires_epoch_settled() { + let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + + // Subscribe to epoch 5 + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 5, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.subscribe(old_nft_id, USER1()); + + // Fulfill old NFT + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + + // Transfer assets and swap (but not enough to settle epoch 5) + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 50); // Only 50, not enough for epoch 5 + + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; + cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 1000); + + // handled_epoch_len should be 6 if epoch 5 is handled (epochs 0-5 are handled, len = 6) + start_mock_call(dummy_vault, selector!("asset"), from_asset); + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 6_u256); // Epochs 0-5 handled + start_mock_call(dummy_vault, selector!("epoch"), 5_u256); + + let routes: Array = array![]; + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.swap(routes, WAD * 50, WAD * 100); // Swap 50, epoch 5 needs 100, so not fully settled + + // Attempt to claim - should fail because epoch not fully settled + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.claim(new_nft_id); // Should fail - epoch not settled +} + +// ============================================================================ +// 5. Unsubscribe Function Tests +// ============================================================================ + +#[test] +fn test_unsubscribe_original_nft_not_fulfilled_returns_nft() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // 1. Subscribe + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.subscribe(old_nft_id, USER1()); + + // Verify old NFT is owned by router + let redeem_request_erc721 = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + assert(redeem_request_erc721.owner_of(old_nft_id) == router.contract_address, 'Old NFT owned by router'); + + // 2. Unsubscribe (original NFT not fulfilled) + // Use unsubscribe_for_nft since old NFT is not fulfilled + // Caller must own the NFT (USER1 already owns it) + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_nft(new_nft_id, USER1()); + + // Verify old NFT returned to user + assert(redeem_request_erc721.owner_of(old_nft_id) == USER1(), 'Old NFT returned to user'); + + // Verify new NFT is burned and marked as unsubscribed + let request_info = router.new_nft_request_info(new_nft_id); + assert(request_info.unsubscribed == true, 'NFT marked as unsubscribed'); +} + +#[test] +fn test_unsubscribe_original_nft_fulfilled_but_not_swapped_returns_assets() { + let (dummy_vault, from_asset, _, redeem_request, _, router) = set_up(); + + // 1. Subscribe + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.subscribe(old_nft_id, USER1()); + + // 2. Fulfill old NFT (burn it) + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + // Mark old NFT as fulfilled in router's storage + mark_old_nft_fulfilled(router.contract_address, old_nft_id); + + // 3. Transfer assets to router (simulate vault fulfilling redemption) + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 100); + + // Get initial user balance + let user_balance_before = from_asset_dispatcher.balance_of(USER1()); + + // 4. Unsubscribe (original NFT fulfilled but not swapped) + // Use unsubscribe_for_underlying since old NFT is fulfilled + // Mock vault functions needed by unsubscribe_for_underlying + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); + start_mock_call(dummy_vault, selector!("asset"), from_asset); + // Caller must own the NFT (USER1 already owns it) + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_underlying(new_nft_id, USER1()); + + // Verify user received from_assets + let user_balance_after = from_asset_dispatcher.balance_of(USER1()); + assert(user_balance_after == user_balance_before + WAD * 100, 'User should receive from_assets'); + + // Verify new NFT is burned and marked as unsubscribed + let request_info = router.new_nft_request_info(new_nft_id); + assert(request_info.unsubscribed == true, 'NFT marked as unsubscribed'); +} + +#[test] +#[should_panic(expected: "Cannot unsubscribe: swaps have partially consumed assets")] +fn test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts() { + let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + + // 1. Subscribe + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.subscribe(old_nft_id, USER1()); + + // 2. Fulfill old NFT + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + // Mark old NFT as fulfilled in router's storage + mark_old_nft_fulfilled(router.contract_address, old_nft_id); + + // 3. Transfer assets to router + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 100); + + // 4. Partial swap (swap 50 out of 100) + // Mock handled_epoch_len as 2 so epoch 1 can be processed (epochs are 0-indexed) + start_mock_call(dummy_vault, selector!("asset"), from_asset); + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); + start_mock_call(dummy_vault, selector!("epoch"), 1_u256); + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; + cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 200); + + let routes: Array = array![]; + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.swap(routes, WAD * 50, WAD * 100); // Swap 50, leaving 50 remaining (epoch needs 100 total) + + // 5. Attempt unsubscribe - should revert because swaps have partially consumed + // Use unsubscribe_for_underlying since old NFT is fulfilled + // Mock vault functions needed by unsubscribe_for_underlying + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); + start_mock_call(dummy_vault, selector!("asset"), from_asset); + // Caller must own the NFT (USER1 already owns it) + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_underlying(new_nft_id, USER1()); +} + +#[test] +fn test_unsubscribe_second_user_before_swaps() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // 1. Two users subscribe + let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + + // User 1 subscribes + let due_amount_1: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_1); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); + + // User 2 subscribes + let due_amount_2: u256 = WAD * 200; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); + + cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_2); + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); + + // 2. User 2 unsubscribes (original NFT not fulfilled) + let redeem_request_erc721 = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + // Use unsubscribe_for_nft since old NFT is not fulfilled + // Caller must own the NFT (USER2 already owns it) + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_nft(new_nft_id_2, USER2()); + + // Verify User 2's old NFT returned + assert(redeem_request_erc721.owner_of(old_nft_id_2) == USER2(), 'User 2 NFT returned'); + + // Verify User 1's old NFT still owned by router + assert(redeem_request_erc721.owner_of(old_nft_id_1) == router.contract_address, 'User 1 NFT in router'); + + // Verify User 1 can still claim later (after swaps) + let request_info_1 = router.new_nft_request_info(new_nft_id_1); + assert(request_info_1.unsubscribed == false, 'User 1 not unsubscribed'); +} + +#[test] +fn test_unsubscribe_third_user_after_second_withdrawn() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // 1. Three users subscribe + let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + let old_nft_id_3 = mint_old_nft_to_user(redeem_request, dummy_vault, 'USER3'.try_into().unwrap(), 1, 300); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + let user3: ContractAddress = 'USER3'.try_into().unwrap(); + + // User 1 subscribes + let due_amount_1: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_1); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let _new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); + + // User 2 subscribes + let due_amount_2: u256 = WAD * 200; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); + + cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_2); + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); + + // User 3 subscribes + let due_amount_3: u256 = WAD * 300; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_3); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 600); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 600); + + cheat_caller_address(redeem_request.contract_address, user3, span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_3); + cheat_caller_address(router.contract_address, user3, span: CheatSpan::TargetCalls(1)); + let new_nft_id_3 = router.subscribe(old_nft_id_3, user3); + + // 2. User 2 unsubscribes + // Use unsubscribe_for_nft since old NFT is not fulfilled + // Caller must own the NFT (USER2 already owns it) + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_nft(new_nft_id_2, USER2()); + + // 3. User 3 unsubscribes (should work even though User 2 withdrew) + let redeem_request_erc721 = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + // Use unsubscribe_for_nft since old NFT is not fulfilled + // Caller must own the NFT (user3 already owns it) + cheat_caller_address(router.contract_address, user3, span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_nft(new_nft_id_3, user3); + + // Verify User 3's old NFT returned + assert(redeem_request_erc721.owner_of(old_nft_id_3) == user3, 'User 3 NFT returned'); + + // Verify User 1's old NFT still owned by router + assert(redeem_request_erc721.owner_of(old_nft_id_1) == router.contract_address, 'User 1 NFT in router'); +} + +#[test] +fn test_unsubscribe_second_user_after_fulfillment_but_before_swaps() { + let (dummy_vault, from_asset, _, redeem_request, _, router) = set_up(); + + // 1. Two users subscribe + let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + + // User 1 subscribes + let due_amount_1: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_1); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); + + // User 2 subscribes + let due_amount_2: u256 = WAD * 200; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); + + cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id_2); + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); + + // 2. Fulfill both old NFTs + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_1); + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_2); + // Mark old NFTs as fulfilled in router's storage + mark_old_nft_fulfilled(router.contract_address, old_nft_id_1); + mark_old_nft_fulfilled(router.contract_address, old_nft_id_2); + + // 3. Transfer assets to router (300 total: 100 for user1, 200 for user2) + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 300); + + // Get User 2 balance before + let user2_balance_before = from_asset_dispatcher.balance_of(USER2()); + + // 4. User 2 unsubscribes (original NFT fulfilled but not swapped) + // Use unsubscribe_for_underlying since old NFT is fulfilled + // Mock vault functions needed by unsubscribe_for_underlying + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); + start_mock_call(dummy_vault, selector!("asset"), from_asset); + // Caller must own the NFT (USER2 already owns it) + cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_underlying(new_nft_id_2, USER2()); + + // Verify User 2 received from_assets (200) + let user2_balance_after = from_asset_dispatcher.balance_of(USER2()); + assert(user2_balance_after == user2_balance_before + WAD * 200, 'User 2 received 200'); + + // Verify User 1 can still claim later + let request_info_1 = router.new_nft_request_info(new_nft_id_1); + assert(request_info_1.unsubscribed == false, 'User 1 not unsubscribed'); +} + +#[test] +#[should_panic(expected: "NFT already withdrawn")] +fn test_unsubscribe_twice_reverts() { + let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + + // Subscribe + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.subscribe(old_nft_id, USER1()); + + // Unsubscribe first time + // Use unsubscribe_for_nft since old NFT is not fulfilled + // Caller must own the NFT (USER1 already owns it) + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_nft(new_nft_id, USER1()); + + // Attempt unsubscribe second time - should revert + // Note: NFT is already burned, so we can't transfer it again + // But we can try to call unsubscribe again which should fail + // Use unsubscribe_for_nft since old NFT is not fulfilled + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.unsubscribe_for_nft(new_nft_id, USER1()); +} + +// ============================================================================ +// 6. Access Control & Setter Tests +// ============================================================================ + +#[test] +fn test_set_min_subscribe_amount_only_owner() { + let (_, _, _, _, _, router) = set_up(); + + // Owner can set min_subscribe_amount + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.set_min_subscribe_amount(WAD * 100); + + // Verify it was set (we can't directly read it, but we can test it works) + // by trying to subscribe with amount below minimum +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_min_subscribe_amount_reverts_when_not_owner() { + let (_, _, _, _, _, router) = set_up(); + + // Non-owner attempts to set min_subscribe_amount + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.set_min_subscribe_amount(WAD * 100); +} + +// ============================================================================ +// 7. Epoch Settlement & Sync Tests +// ============================================================================ + +#[test] +fn test_sync_settled_epochs() { + let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + + // Subscribe to epoch 1 + let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); + let due_amount: u256 = WAD * 100; + start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); + start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let _new_nft_id = router.subscribe(old_nft_id, USER1()); + + // Fulfill old NFT + fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + + // Transfer assets and swap enough to settle epoch 1 + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + from_asset_dispatcher.transfer(router.contract_address, WAD * 100); + + let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; + cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); + to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 1000); + + // handled_epoch_len should be 2 if epoch 1 is handled (epochs are 0-indexed, len is count) + start_mock_call(dummy_vault, selector!("asset"), from_asset); + start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); // Epochs 0 and 1 handled + start_mock_call(dummy_vault, selector!("epoch"), 1_u256); + + let routes: Array = array![]; + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.swap(routes, WAD * 100, WAD * 200); + + // Sync settled epochs + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.sync_settled_epochs(10); // Check up to 10 epochs + + // Verify last_settled_epoch updated + assert(router.last_settled_epoch() == 1, 'last_settled_epoch should be 1'); +} + +#[test] +#[should_panic(expected: ('Pausable: paused',))] +fn test_sync_settled_epochs_reverts_when_paused() { + let (_, _, _, _, _, router) = set_up(); + + // Pause contract + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.pause(); + + // Attempt sync + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.sync_settled_epochs(10); +} + diff --git a/packages/vault/src/test/utils.cairo b/packages/vault/src/test/utils.cairo index bd3dc487..70973b50 100644 --- a/packages/vault/src/test/utils.cairo +++ b/packages/vault/src/test/utils.cairo @@ -116,7 +116,7 @@ pub fn deploy_counter() -> (ICounterDispatcher, ClassHash) { pub fn deploy_erc20_mock() -> ContractAddress { let erc20 = declare("Erc20Mock").unwrap().contract_class(); let mut calldata = ArrayTrait::new(); - (WAD * 100).serialize(ref calldata); + (WAD * 1000).serialize(ref calldata); OWNER().serialize(ref calldata); OWNER().serialize(ref calldata); let (erc20_address, _) = erc20.deploy(@calldata).unwrap(); diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 5701d8b5..27a8ba77 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -80,6 +80,7 @@ pub mod mocks { pub mod erc20; pub mod erc4626; pub mod vault; + pub mod mock_avnu_exchange; } #[cfg(test)] diff --git a/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo b/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo new file mode 100644 index 00000000..eeecee3e --- /dev/null +++ b/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::{ContractAddress, get_caller_address, get_contract_address}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; + +#[starknet::interface] +pub trait IAvnuExchange { + fn multi_route_swap( + ref self: T, + sell_token_address: ContractAddress, + sell_token_amount: u256, + buy_token_address: ContractAddress, + buy_token_amount: u256, + buy_token_min_amount: u256, + beneficiary: ContractAddress, + integrator_fee_amount_bps: u128, + integrator_fee_recipient: ContractAddress, + routes: Array, + ) -> bool; + fn swap_exact_token_to( + ref self: T, + sell_token_address: ContractAddress, + sell_token_amount: u256, + sell_token_max_amount: u256, + buy_token_address: ContractAddress, + buy_token_amount: u256, + beneficiary: ContractAddress, + routes: Array, + ) -> bool; +} + +#[starknet::contract] +pub mod MockAvnuExchange { + use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; + use super::IAvnuExchange; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Swapped: Swapped, + } + + #[derive(Drop, starknet::Event)] + struct Swapped { + pub sell_token: ContractAddress, + pub sell_amount: u256, + pub buy_token: ContractAddress, + pub buy_amount: u256, + pub beneficiary: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState) { + // Empty constructor + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn execute_swap( + ref self: ContractState, + sell_token_address: ContractAddress, + sell_token_amount: u256, + buy_token_address: ContractAddress, + buy_token_min_amount: u256, + beneficiary: ContractAddress, + ) { + let caller = get_caller_address(); + let this = get_contract_address(); + + // Transfer sell_token_amount from caller (router) to this contract + let sell_token_dispatcher = ERC20ABIDispatcher { + contract_address: sell_token_address, + }; + sell_token_dispatcher.transfer_from(caller, this, sell_token_amount); + + // Transfer buy_token_min_amount to beneficiary (router) + let buy_token_dispatcher = ERC20ABIDispatcher { + contract_address: buy_token_address, + }; + buy_token_dispatcher.transfer(beneficiary, buy_token_min_amount); + + // Emit event + self.emit(Swapped { + sell_token: sell_token_address, + sell_amount: sell_token_amount, + buy_token: buy_token_address, + buy_amount: buy_token_min_amount, + beneficiary, + }); + } + } + + #[abi(embed_v0)] + impl MockAvnuExchangeImpl of IAvnuExchange { + /// Mock implementation of multi_route_swap + /// Transfers sell_token_amount from caller and transfers buy_token_min_amount to beneficiary + fn multi_route_swap( + ref self: ContractState, + sell_token_address: ContractAddress, + sell_token_amount: u256, + buy_token_address: ContractAddress, + buy_token_amount: u256, + buy_token_min_amount: u256, + beneficiary: ContractAddress, + integrator_fee_amount_bps: u128, + integrator_fee_recipient: ContractAddress, + routes: Array, + ) -> bool { + InternalImpl::execute_swap( + ref self, + sell_token_address, + sell_token_amount, + buy_token_address, + buy_token_min_amount, + beneficiary, + ); + true + } + + fn swap_exact_token_to( + ref self: ContractState, + sell_token_address: ContractAddress, + sell_token_amount: u256, + sell_token_max_amount: u256, + buy_token_address: ContractAddress, + buy_token_amount: u256, + beneficiary: ContractAddress, + routes: Array, + ) -> bool { + // Not used in redemption router, but required by interface + false + } + } +} + From a0e10e48b765a13bb9930af5d2a5dad5434985b5 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Mon, 29 Dec 2025 17:13:16 +0530 Subject: [PATCH 02/14] add keys to events [RR] and minor fixes --- my_scripts/contracts.json | 2 +- my_scripts/deploy.ts | 9 +- my_scripts/package.json | 4 +- my_scripts/pnpm-lock.yaml | 2608 ++++++++++++++++- nohup.log | 363 +-- package.json | 5 + .../src/redemption_router/INTEGRATION.md | 139 +- .../src/redemption_router/interface.cairo | 1 + .../redemption_router/redemption_router.cairo | 81 +- .../src/test/units/redemption_router.cairo | 471 ++- pnpm-lock.yaml | 13 + 11 files changed, 3178 insertions(+), 518 deletions(-) create mode 100644 package.json create mode 100644 pnpm-lock.yaml diff --git a/my_scripts/contracts.json b/my_scripts/contracts.json index 31494bb3..299e7f20 100644 --- a/my_scripts/contracts.json +++ b/my_scripts/contracts.json @@ -1 +1 @@ -{"class_hashes":{"Vault":"0x5f3e4d3a6f6ea274d0288b7320428e9960298b9db1f6848b7805d14ace6413e","VaultAllocator":"0x7608b7b98f28a18841285367907f2c8bb924949e9f610f370b190e106cd3c3f","RedeemRequest":"0x7421d403cf73830acc41ebc9646c74f4a931f747ac275e32d82fc3a7c7a9aef","Manager":"0x7dbf4bc6ebf73952e77d60a7da8b1ec1523cfd3477ed99253403a3e0cab40d0","SimpleDecoderAndSanitizer":"0x19c38fa79f607d0935596802bcee103b88bc81dfa4b9f2d8bf0e398b19ec3c8","UsdtFixer":"0x19b362ba0e70d94185ebb986ea80039ec19ffca410f200f971fe5731ffd5a21","RedemptionRouter":"0x7cee2a1b936a2f3ff6bb3028e3ac13f40c8a7a2bae21c1129b069a7391db814"},"contracts":{"Vault":"0x6a346bda4e723d3f4763513007d4b8ef0029f491ed0a1e0626db6d7f3af3c01","RedeemRequest":"0x66c5f84e5fc6c20545737cc187289adccacfeb9829da6fd4ddf6121b32573dc","VaultAllocator":"0x292503a0bff97ad8818481cca50f5f9298537d0e0e9dc6a7ac9bbb8a93f71a3","Manager":"0x2d7822d1616f5e0b556c0f7467eb968bd2acd0f2b2b8623adff96a51e5516db","SimpleDecoderAndSanitizer":"0x7b6f98311af8aa425278570e62abf523e6462eaa01a38c1feab9b2f416492e2","aum_oracle":"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345","UsdtFixer":"0x7954afaca4c706f9f30658777e55b7f8e264c8974b8a2638d5ebb359280816","RedemptionRouter":"0x67d41ef9c2e119053a5a457451eb4c37d36fdfc3ba854fbb0e31e3f1ff9c4aa"}} \ No newline at end of file +{"class_hashes":{"Vault":"0x5f3e4d3a6f6ea274d0288b7320428e9960298b9db1f6848b7805d14ace6413e","VaultAllocator":"0x7608b7b98f28a18841285367907f2c8bb924949e9f610f370b190e106cd3c3f","RedeemRequest":"0x7421d403cf73830acc41ebc9646c74f4a931f747ac275e32d82fc3a7c7a9aef","Manager":"0x7dbf4bc6ebf73952e77d60a7da8b1ec1523cfd3477ed99253403a3e0cab40d0","SimpleDecoderAndSanitizer":"0x19c38fa79f607d0935596802bcee103b88bc81dfa4b9f2d8bf0e398b19ec3c8","UsdtFixer":"0x19b362ba0e70d94185ebb986ea80039ec19ffca410f200f971fe5731ffd5a21","RedemptionRouter":"0x3f8f13e8146a875ba5cdffb510c51b3691d4b5401baf23b6bd5746881c4095f"},"contracts":{"Vault":"0x6a346bda4e723d3f4763513007d4b8ef0029f491ed0a1e0626db6d7f3af3c01","RedeemRequest":"0x66c5f84e5fc6c20545737cc187289adccacfeb9829da6fd4ddf6121b32573dc","VaultAllocator":"0x292503a0bff97ad8818481cca50f5f9298537d0e0e9dc6a7ac9bbb8a93f71a3","Manager":"0x2d7822d1616f5e0b556c0f7467eb968bd2acd0f2b2b8623adff96a51e5516db","SimpleDecoderAndSanitizer":"0x7b6f98311af8aa425278570e62abf523e6462eaa01a38c1feab9b2f416492e2","aum_oracle":"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345","UsdtFixer":"0x7954afaca4c706f9f30658777e55b7f8e264c8974b8a2638d5ebb359280816","RedemptionRouter":"0x6ea649f402898f69baf775c1afdd08522c071c640b9c4460192070ec2b96417"}} \ No newline at end of file diff --git a/my_scripts/deploy.ts b/my_scripts/deploy.ts index d4aebe26..03f29108 100644 --- a/my_scripts/deploy.ts +++ b/my_scripts/deploy.ts @@ -328,6 +328,7 @@ async function deployUsdtFixer() { async function deployRedemptionRouter() { const provider = config.provider; + // ! set strategy const strategy = HyperLSTStrategies.find(u => u.name.includes('xWBTC'))!; const calls = await Deployer.prepareMultiDeployContracts([{ contract_name: 'RedemptionRouter', @@ -336,10 +337,12 @@ async function deployRedemptionRouter() { OWNER, strategy.additionalInfo.vaultAddress.address, strategy.additionalInfo.redeemRequestNFT.address, + // ! set to_asset Global.getDefaultTokens().find(t => t.symbol === 'WBTC')?.address!, "0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f", OWNER, "0", + uint256.bnToUint256(0) // min subscribe amount ] }], config, acc); await Deployer.executeDeployCalls(calls, acc, provider); @@ -400,7 +403,7 @@ if (require.main === module) { // deployStrategy(); // deployAUMOracle("0x437ef1e7d0f100b2e070b7a65cafec0b2be31b0290776da8b4112f5473d8d9") - const strategy = HyperLSTStrategies.find(u => u.name.includes('xSTRK'))!; + const strategy = HyperLSTStrategies.find(u => u.name.includes('xWBTC'))!; // const vaultStrategy = new UniversalStrategy(config, pricer, strategy); const vaultStrategy = new UniversalLstMultiplierStrategy(config, pricer, strategy); const vaultContracts = { @@ -437,7 +440,7 @@ if (require.main === module) { // const netAPY = await vaultStrategy.netAPY(); // console.log(netAPY); } - setConfig(); + // setConfig(); // configurePriceRouter(); // deployPriceRouter(); // deployAvnuMiddleware(); @@ -447,7 +450,7 @@ if (require.main === module) { // asset: u.depositTokens[0].address.address // }))) // deploySanitizer(); - // upgrade('Vault', VAULT_PACKAGE, vaultContracts.vault.toString()); + upgrade('RedemptionRouter', VAULT_PACKAGE, '0x6ea649f402898f69baf775c1afdd08522c071c640b9c4460192070ec2b96417'); // grantRole(vaultStrategy, hash.getSelectorFromName('ORACLE_ROLE'), '0x2edf4edbed3f839e7f07dcd913e92299898ff4cf0ba532f8c572c66c5b331b2') // setMaxDelta(vaultStrategy, getMaxDelta(15, CommonSettings.vault.default_settings.report_delay * 24)); } \ No newline at end of file diff --git a/my_scripts/package.json b/my_scripts/package.json index d34ea35d..965dff44 100644 --- a/my_scripts/package.json +++ b/my_scripts/package.json @@ -16,11 +16,11 @@ "dependencies": { "@ericnordelo/strk-merkle-tree": "^1.0.0", "@noble/curves": "^2.0.0", - "@strkfarm/sdk": "link:../../sdk-ts", + "@strkfarm/sdk": "^1.2.0", "axios": "^1.12.2", "dotenv": "^17.2.1", "react": "^19.1.1", - "starknet": "8.5.3" + "starknet": "9.2.1" }, "devDependencies": { "@types/node": "^24.3.1", diff --git a/my_scripts/pnpm-lock.yaml b/my_scripts/pnpm-lock.yaml index 5a6b261a..762e0309 100644 --- a/my_scripts/pnpm-lock.yaml +++ b/my_scripts/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^2.0.0 version: 2.0.1 '@strkfarm/sdk': - specifier: link:../../sdk-ts - version: link:../../sdk-ts + specifier: ^1.2.0 + version: 1.2.0(@types/react@19.2.7)(axios@1.12.2)(moment@2.30.1)(qs@6.14.0)(react@19.2.0)(request@2.88.2)(starknet@9.2.1) axios: specifier: ^1.12.2 version: 1.12.2 @@ -30,8 +30,8 @@ importers: specifier: ^19.1.1 version: 19.2.0 starknet: - specifier: 8.5.3 - version: 8.5.3 + specifier: 9.2.1 + version: 9.2.1 devDependencies: '@types/node': specifier: ^24.3.1 @@ -42,6 +42,53 @@ importers: packages: + '@adraffy/ens-normalize@1.10.1': + resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} + + '@apollo/client@3.11.8': + resolution: {integrity: sha512-CgG1wbtMjsV2pRGe/eYITmV5B8lXUCYljB2gB/6jWTFQcrvirUVvKg7qtFdjYkQSFbIffU1IDyxgeaN81eTjbA==} + peerDependencies: + graphql: ^15.0.0 || ^16.0.0 + graphql-ws: ^5.5.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 + subscriptions-transport-ws: ^0.9.0 || ^0.11.0 + peerDependenciesMeta: + graphql-ws: + optional: true + react: + optional: true + react-dom: + optional: true + subscriptions-transport-ws: + optional: true + + '@avnu/avnu-sdk@3.0.2': + resolution: {integrity: sha512-N8McoXYEcp1uhSZ4XlEi5BcTpvJDDhUw4kYnlYmPrd7fWezfGy4UHyoFT/A4gqEM4nL3vtENczDNFS1AVfdTnA==} + engines: {node: '>=18'} + peerDependencies: + ethers: ^6.13.0 + moment: ^2.30.1 + qs: ^6.13.0 + starknet: ^6.11.0 + + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@cypress/request-promise@5.0.0': + resolution: {integrity: sha512-eKdYVpa9cBEw2kTBlHeu1PP16Blwtum6QHg/u9s/MoHkZfuo1pRGka1VlUHXF5kdew82BvOJVVGk0x8X0nbp+w==} + engines: {node: '>=0.10.0'} + peerDependencies: + '@cypress/request': ^3.0.0 + + '@cypress/request@3.0.9': + resolution: {integrity: sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==} + engines: {node: '>= 6'} + + '@dabh/diagnostics@2.0.8': + resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + '@ericnordelo/strk-merkle-tree@1.0.0': resolution: {integrity: sha512-iKY8uZ84W6pOzUrq09QHb+jRU7LcB8b/h2kpmX1gEVVsGrQI84xqTjVQCyU2Pzl7h8loc3SL835yMUKdoXWjrQ==} @@ -255,44 +302,244 @@ packages: '@ethersproject/web@5.8.0': resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@inquirer/checkbox@2.5.0': + resolution: {integrity: sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==} + engines: {node: '>=18'} + + '@inquirer/confirm@3.2.0': + resolution: {integrity: sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==} + engines: {node: '>=18'} + + '@inquirer/core@9.2.1': + resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==} + engines: {node: '>=18'} + + '@inquirer/editor@2.2.0': + resolution: {integrity: sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==} + engines: {node: '>=18'} + + '@inquirer/expand@2.3.0': + resolution: {integrity: sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@2.3.0': + resolution: {integrity: sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==} + engines: {node: '>=18'} + + '@inquirer/number@1.1.0': + resolution: {integrity: sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==} + engines: {node: '>=18'} + + '@inquirer/password@2.2.0': + resolution: {integrity: sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==} + engines: {node: '>=18'} + + '@inquirer/prompts@5.5.0': + resolution: {integrity: sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog==} + engines: {node: '>=18'} + + '@inquirer/rawlist@2.3.0': + resolution: {integrity: sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==} + engines: {node: '>=18'} + + '@inquirer/search@1.1.0': + resolution: {integrity: sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==} + engines: {node: '>=18'} + + '@inquirer/select@2.5.0': + resolution: {integrity: sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==} + engines: {node: '>=18'} + + '@inquirer/type@1.5.5': + resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} + engines: {node: '>=18'} + + '@inquirer/type@2.0.0': + resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==} + engines: {node: '>=18'} + + '@noble/curves@1.2.0': + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + '@noble/curves@1.7.0': resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + '@noble/curves@2.0.1': resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} engines: {node: '>= 20.19.0'} + '@noble/hashes@1.3.2': + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + '@noble/hashes@1.6.0': resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@2.0.1': resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.6.1': + resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==} + engines: {node: '>=14'} + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.7': + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.2.0': + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.1.0': + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + '@scure/base@1.2.1': resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + '@scure/starknet@1.1.0': resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} + '@scure/starknet@2.0.0': + resolution: {integrity: sha512-uDGDGleYeqsYQTpPU2IZdO7WeCeYO2sORIJdLnjX/vuENxnu8WRhabFKHjoQGQf0sPuMRD5nKyQBxKWy45W5cg==} + engines: {node: '>= 20.19.0'} + + '@so-ric/colorspace@1.1.6': + resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + + '@starknet-io/get-starknet-wallet-standard@5.0.0': + resolution: {integrity: sha512-isDNGDlp16W24HE4IuweYXLDRZN0JbsDnazAieeKXE87Mn+jqhsjgTsMxcwWTjX7v906Bjz39FiDjGUddnr36g==} + + '@starknet-io/types-js@0.10.0': + resolution: {integrity: sha512-7ALSydz6pq3YIOpq5a7OkkxqwJciMc9Nlph0OGjhcC3xX0xH30XgizmziLyYVN10oO9+BJk8M9KbJjpzdbtRSw==} + '@starknet-io/types-js@0.7.10': resolution: {integrity: sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==} - '@starknet-io/types-js@0.8.4': - resolution: {integrity: sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ==} - '@starknet-io/types-js@0.9.2': resolution: {integrity: sha512-vWOc0FVSn+RmabozIEWcEny1I73nDGTvOrLYJsR1x7LGA3AZmqt4i/aW69o/3i2NN5CVP8Ok6G1ayRQJKye3Wg==} + '@strkfarm/sdk@1.2.0': + resolution: {integrity: sha512-rHYyHR9rZtngZY2YTtO6Ez673e2XnA1Vk2M9Jdl3LkCGeqhgidn//dyYgme85tRWo9hIMhjIaaseKurq8q3bNQ==} + hasBin: true + peerDependencies: + '@types/react': ^19.1.2 + axios: ^1.7.2 + react: 19.1.2 + starknet: 9.2.1 + + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + + '@types/node@22.19.3': + resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} + + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/node@24.9.1': resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==} + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@types/wrap-ansi@3.0.0': + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + + '@wallet-standard/base@1.1.0': + resolution: {integrity: sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ==} + engines: {node: '>=16'} + + '@wallet-standard/features@1.1.0': + resolution: {integrity: sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg==} + engines: {node: '>=16'} + + '@wry/caches@1.0.1': + resolution: {integrity: sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==} + engines: {node: '>=8'} + + '@wry/context@0.7.4': + resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==} + engines: {node: '>=8'} + + '@wry/equality@0.5.7': + resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==} + engines: {node: '>=8'} + + '@wry/trie@0.5.0': + resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==} + engines: {node: '>=8'} + abi-wan-kanabi@2.2.4: resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} hasBin: true + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + aes-js@4.0.0-beta.5: + resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -304,12 +551,60 @@ packages: ansicolors@0.3.2: resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array.prototype.findindex@2.2.4: + resolution: {integrity: sha512-LLm4mhxa9v8j0A/RPnpQAP4svXToJFh+Hp1pNYl5ZD5qpB4zdx/D4YjpVcETkhFbUKWO3iGMVLvrOnnmkAJT6A==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + axios@1.12.2: resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + + bignumber.js@4.0.4: + resolution: {integrity: sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg==} + + bl@1.2.3: + resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + bn.js@4.12.2: resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} @@ -319,29 +614,119 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + browser-assert@1.2.1: + resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} + + color@5.0.3: + resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} + engines: {node: '>=18'} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -354,12 +739,28 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -376,6 +777,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.25.11: resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} engines: {node: '>=18'} @@ -390,9 +799,46 @@ packages: engines: {node: '>=4'} hasBin: true + ethers@6.16.0: + resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} + engines: {node: '>=14.0.0'} + + eventemitter3@3.1.2: + resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fetch-cookie@3.0.1: resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} + file-type@3.9.0: + resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} + engines: {node: '>=0.10.0'} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -402,6 +848,17 @@ packages: debug: optional: true + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + + form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -418,6 +875,21 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -430,9 +902,20 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -440,6 +923,40 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphql-tag@2.12.6: + resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + + graphql@16.9.0: + resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + + har-validator@5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -458,75 +975,365 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + + http-signature@1.4.0: + resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} + engines: {node: '>=0.10'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + inquirer@10.2.2: + resolution: {integrity: sha512-tyao/4Vo36XnUItZ7DnUXX4f1jVao2mSrleV/5IPtW/XAEA26hRVsbc68nuTEKWcr5vMP/1mVoT2O7u8H4v1Vg==} + engines: {node: '>=18'} - isomorphic-fetch@3.0.0: - resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} - lossless-json@4.3.0: - resolution: {integrity: sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==} + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + + jsprim@2.0.2: + resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} + engines: {'0': node >=0.6.0} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lossless-json@4.3.0: + resolution: {integrity: sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: encoding: ^0.1.0 peerDependenciesMeta: encoding: optional: true + node-telegram-bot-api@0.66.0: + resolution: {integrity: sha512-s4Hrg5q+VPl4/tJVG++pImxF6eb8tNJNj4KnDqAOKL6zGU34lo9RXmyAN158njwGN+v8hdNf8s9fWIYW9hPb5A==} + engines: {node: '>=0.12'} + + oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + optimism@0.18.1: + resolution: {integrity: sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + ox@0.4.4: + resolution: {integrity: sha512-oJPEeCDs9iNiPs6J0rTx+Y0KGeCGyCAA3zo94yZhm8G5WpOxrwUtn2Ie/Y8IyARSqqY/j9JTKA3Fc1xs1DvFnw==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + pump@2.0.1: + resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + qs@6.5.3: + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} + querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react@19.2.0: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + redis@4.7.1: + resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + rehackt@0.1.0: + resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==} + peerDependencies: + '@types/react': '*' + react: '*' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + request-promise-core@1.1.3: + resolution: {integrity: sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==} + engines: {node: '>=0.10.0'} + peerDependencies: + request: ^2.34 + + request@2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -537,39 +1344,237 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + response-iterator@0.2.25: + resolution: {integrity: sha512-15K4tT8X35W0zJ5bv3fAf4eEKqOwS7yzd+Bg6YEE9NLltVbPbuTcYo3J2AP6AMQGMJmJkFCG421+kP2/iCBfDA==} + engines: {node: '>=0.8'} + + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map@0.5.6: + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} + engines: {node: '>=0.10.0'} + + sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + stack-generator@2.0.10: + resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-gps@3.1.2: + resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} + + stacktrace-js@2.0.2: + resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + starknet@6.24.1: resolution: {integrity: sha512-g7tiCt73berhcNi41otlN3T3kxZnIvZhMi8WdC21Y6GC6zoQgbI2z1t7JAZF9c4xZiomlanwVnurcpyfEdyMpg==} - starknet@8.5.3: - resolution: {integrity: sha512-E0Z4Jk3W0hS8FgMFjS4mPown6zrFfR7rUwGBlskgUc4X7ZqEfOO15s90kXIFI2letcM3bI6APRytYfBMhBKoPQ==} + starknet@9.2.1: + resolution: {integrity: sha512-bFJY2sMZ9tsLBhPCm719MWjoz+doabXIwPX/xtW56EHwAJMRAS6mICF6H2dCwOQHJmCMKpOSFBwW0SaiHzcioQ==} engines: {node: '>=22'} + stealthy-require@1.1.1: + resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} + engines: {node: '>=0.10.0'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + ts-invariant@0.10.3: + resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} + engines: {node: '>=8'} + ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.20.6: resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} engines: {node: '>=18.0.0'} hasBin: true + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -581,9 +1586,28 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -593,14 +1617,60 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} + engines: {node: '>= 12.0.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -609,8 +1679,88 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + zen-observable-ts@1.2.5: + resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==} + + zen-observable@0.8.15: + resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} + snapshots: + '@adraffy/ens-normalize@1.10.1': {} + + '@apollo/client@3.11.8(@types/react@19.2.7)(graphql@16.9.0)(react@19.2.0)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) + '@wry/caches': 1.0.1 + '@wry/equality': 0.5.7 + '@wry/trie': 0.5.0 + graphql: 16.9.0 + graphql-tag: 2.12.6(graphql@16.9.0) + hoist-non-react-statics: 3.3.2 + optimism: 0.18.1 + prop-types: 15.8.1 + rehackt: 0.1.0(@types/react@19.2.7)(react@19.2.0) + response-iterator: 0.2.25 + symbol-observable: 4.0.0 + ts-invariant: 0.10.3 + tslib: 2.8.1 + zen-observable-ts: 1.2.5 + optionalDependencies: + react: 19.2.0 + transitivePeerDependencies: + - '@types/react' + + '@avnu/avnu-sdk@3.0.2(ethers@6.16.0)(moment@2.30.1)(qs@6.14.0)(starknet@9.2.1)': + dependencies: + ethers: 6.16.0 + moment: 2.30.1 + qs: 6.14.0 + starknet: 9.2.1 + + '@colors/colors@1.6.0': {} + + '@cypress/request-promise@5.0.0(@cypress/request@3.0.9)(request@2.88.2)': + dependencies: + '@cypress/request': 3.0.9 + bluebird: 3.7.2 + request-promise-core: 1.1.3(request@2.88.2) + stealthy-require: 1.1.1 + tough-cookie: 4.1.4 + transitivePeerDependencies: + - request + + '@cypress/request@3.0.9': + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.2 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 4.0.4 + http-signature: 1.4.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + performance-now: 2.1.0 + qs: 6.14.0 + safe-buffer: 5.2.1 + tough-cookie: 5.1.2 + tunnel-agent: 0.6.0 + uuid: 8.3.2 + + '@dabh/diagnostics@2.0.8': + dependencies: + '@so-ric/colorspace': 1.1.6 + enabled: 2.0.0 + kuler: 2.0.0 + '@ericnordelo/strk-merkle-tree@1.0.0': dependencies: '@ethersproject/abi': 5.8.0 @@ -814,42 +1964,295 @@ snapshots: '@ethersproject/rlp': 5.8.0 '@ethersproject/signing-key': 5.8.0 - '@ethersproject/web@5.8.0': + '@ethersproject/web@5.8.0': + dependencies: + '@ethersproject/base64': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@graphql-typed-document-node/core@3.2.0(graphql@16.9.0)': + dependencies: + graphql: 16.9.0 + + '@inquirer/checkbox@2.5.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.3 + + '@inquirer/confirm@3.2.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + + '@inquirer/core@9.2.1': + dependencies: + '@inquirer/figures': 1.0.15 + '@inquirer/type': 2.0.0 + '@types/mute-stream': 0.0.4 + '@types/node': 22.19.3 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/editor@2.2.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + external-editor: 3.1.0 + + '@inquirer/expand@2.3.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + yoctocolors-cjs: 2.1.3 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@2.3.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + + '@inquirer/number@1.1.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + + '@inquirer/password@2.2.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + + '@inquirer/prompts@5.5.0': + dependencies: + '@inquirer/checkbox': 2.5.0 + '@inquirer/confirm': 3.2.0 + '@inquirer/editor': 2.2.0 + '@inquirer/expand': 2.3.0 + '@inquirer/input': 2.3.0 + '@inquirer/number': 1.1.0 + '@inquirer/password': 2.2.0 + '@inquirer/rawlist': 2.3.0 + '@inquirer/search': 1.1.0 + '@inquirer/select': 2.5.0 + + '@inquirer/rawlist@2.3.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 1.5.5 + yoctocolors-cjs: 2.1.3 + + '@inquirer/search@1.1.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 1.5.5 + yoctocolors-cjs: 2.1.3 + + '@inquirer/select@2.5.0': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.3 + + '@inquirer/type@1.5.5': + dependencies: + mute-stream: 1.0.0 + + '@inquirer/type@2.0.0': + dependencies: + mute-stream: 1.0.0 + + '@noble/curves@1.2.0': + dependencies: + '@noble/hashes': 1.3.2 + + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/curves@2.0.1': + dependencies: + '@noble/hashes': 2.0.1 + + '@noble/hashes@1.3.2': {} + + '@noble/hashes@1.6.0': {} + + '@noble/hashes@1.8.0': {} + + '@noble/hashes@2.0.1': {} + + '@redis/bloom@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/client@1.6.1': + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + + '@redis/graph@1.1.1(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/json@1.0.7(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/search@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/time-series@1.1.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@scure/base@1.2.1': {} + + '@scure/base@1.2.6': {} + + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/starknet@1.1.0': + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + + '@scure/starknet@2.0.0': + dependencies: + '@noble/curves': 2.0.1 + '@noble/hashes': 2.0.1 + + '@so-ric/colorspace@1.1.6': + dependencies: + color: 5.0.3 + text-hex: 1.0.0 + + '@starknet-io/get-starknet-wallet-standard@5.0.0': + dependencies: + '@starknet-io/types-js': 0.7.10 + '@wallet-standard/base': 1.1.0 + '@wallet-standard/features': 1.1.0 + ox: 0.4.4 + transitivePeerDependencies: + - typescript + - zod + + '@starknet-io/types-js@0.10.0': {} + + '@starknet-io/types-js@0.7.10': {} + + '@starknet-io/types-js@0.9.2': {} + + '@strkfarm/sdk@1.2.0(@types/react@19.2.7)(axios@1.12.2)(moment@2.30.1)(qs@6.14.0)(react@19.2.0)(request@2.88.2)(starknet@9.2.1)': + dependencies: + '@apollo/client': 3.11.8(@types/react@19.2.7)(graphql@16.9.0)(react@19.2.0) + '@avnu/avnu-sdk': 3.0.2(ethers@6.16.0)(moment@2.30.1)(qs@6.14.0)(starknet@9.2.1) + '@ericnordelo/strk-merkle-tree': 1.0.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 2.0.1 + '@scure/starknet': 2.0.0 + '@types/react': 19.2.7 + axios: 1.12.2 + bignumber.js: 4.0.4 + browser-assert: 1.2.1 + chalk: 4.1.2 + commander: 12.1.0 + ethers: 6.16.0 + graphql: 16.9.0 + inquirer: 10.2.2 + node-telegram-bot-api: 0.66.0(request@2.88.2) + proxy-from-env: 1.1.0 + react: 19.2.0 + redis: 4.7.1 + stacktrace-js: 2.0.2 + starknet: 9.2.1 + winston: 3.19.0 + transitivePeerDependencies: + - bufferutil + - encoding + - graphql-ws + - moment + - qs + - react-dom + - request + - subscriptions-transport-ws + - supports-color + - utf-8-validate + + '@types/mute-stream@0.0.4': + dependencies: + '@types/node': 24.9.1 + + '@types/node@22.19.3': dependencies: - '@ethersproject/base64': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/properties': 5.8.0 - '@ethersproject/strings': 5.8.0 + undici-types: 6.21.0 - '@noble/curves@1.7.0': + '@types/node@22.7.5': dependencies: - '@noble/hashes': 1.6.0 + undici-types: 6.19.8 - '@noble/curves@2.0.1': + '@types/node@24.9.1': dependencies: - '@noble/hashes': 2.0.1 + undici-types: 7.16.0 - '@noble/hashes@1.6.0': {} + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 - '@noble/hashes@2.0.1': {} + '@types/triple-beam@1.3.5': {} - '@scure/base@1.2.1': {} + '@types/wrap-ansi@3.0.0': {} - '@scure/starknet@1.1.0': + '@wallet-standard/base@1.1.0': {} + + '@wallet-standard/features@1.1.0': dependencies: - '@noble/curves': 1.7.0 - '@noble/hashes': 1.6.0 + '@wallet-standard/base': 1.1.0 - '@starknet-io/types-js@0.7.10': {} + '@wry/caches@1.0.1': + dependencies: + tslib: 2.8.1 - '@starknet-io/types-js@0.8.4': {} + '@wry/context@0.7.4': + dependencies: + tslib: 2.8.1 - '@starknet-io/types-js@0.9.2': {} + '@wry/equality@0.5.7': + dependencies: + tslib: 2.8.1 - '@types/node@24.9.1': + '@wry/trie@0.5.0': dependencies: - undici-types: 7.16.0 + tslib: 2.8.1 abi-wan-kanabi@2.2.4: dependencies: @@ -858,6 +2261,21 @@ snapshots: fs-extra: 10.1.0 yargs: 17.7.2 + abitype@1.2.3: {} + + aes-js@4.0.0-beta.5: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -866,8 +2284,50 @@ snapshots: ansicolors@0.3.2: {} + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array.prototype.findindex@2.2.4: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} + + async-function@1.0.0: {} + + async@3.2.6: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + aws-sign2@0.7.0: {} + + aws4@1.13.2: {} + axios@1.12.2: dependencies: follow-redirects: 1.15.11 @@ -876,38 +2336,139 @@ snapshots: transitivePeerDependencies: - debug + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + bignumber.js@4.0.4: {} + + bl@1.2.3: + dependencies: + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + + bluebird@3.7.2: {} + bn.js@4.12.2: {} bn.js@5.2.2: {} brorand@1.1.0: {} + browser-assert@1.2.1: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + cardinal@2.1.1: dependencies: ansicolors: 0.3.2 redeyed: 2.1.1 + caseless@0.12.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chardet@0.7.0: {} + + cli-width@4.1.0: {} + cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + cluster-key-slot@1.1.2: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-convert@3.1.3: + dependencies: + color-name: 2.1.0 + color-name@1.1.4: {} + color-name@2.1.0: {} + + color-string@2.1.4: + dependencies: + color-name: 2.1.0 + + color@5.0.3: + dependencies: + color-convert: 3.1.3 + color-string: 2.1.4 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 + commander@12.1.0: {} + + core-util-is@1.0.2: {} + + core-util-is@1.0.3: {} + + csstype@3.2.3: {} + + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + delayed-stream@1.0.0: {} dotenv@17.2.3: {} @@ -918,6 +2479,11 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + elliptic@6.6.1: dependencies: bn.js: 4.12.2 @@ -930,6 +2496,73 @@ snapshots: emoji-regex@8.0.0: {} + enabled@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -945,6 +2578,16 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.25.11: optionalDependencies: '@esbuild/aix-ppc64': 0.25.11 @@ -978,13 +2621,62 @@ snapshots: esprima@4.0.1: {} + ethers@6.16.0: + dependencies: + '@adraffy/ens-normalize': 1.10.1 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 22.7.5 + aes-js: 4.0.0-beta.5 + tslib: 2.7.0 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + eventemitter3@3.1.2: {} + + eventemitter3@5.0.1: {} + + extend@3.0.2: {} + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + extsprintf@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fecha@4.2.3: {} + fetch-cookie@3.0.1: dependencies: set-cookie-parser: 2.7.1 tough-cookie: 4.1.4 + file-type@3.9.0: {} + + fn.name@1.1.0: {} + follow-redirects@1.15.11: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + forever-agent@0.6.1: {} + + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -1004,6 +2696,21 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + generic-pool@3.9.0: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -1024,38 +2731,220 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + get-tsconfig@4.13.0: dependencies: - resolve-pkg-maps: 1.0.0 + resolve-pkg-maps: 1.0.0 + + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphql-tag@2.12.6(graphql@16.9.0): + dependencies: + graphql: 16.9.0 + tslib: 2.8.1 + + graphql@16.9.0: {} + + har-schema@2.0.0: {} + + har-validator@5.1.5: + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + http-signature@1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.18.0 + + http-signature@1.4.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 2.0.2 + sshpk: 1.18.0 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + inquirer@10.2.2: + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/prompts': 5.5.0 + '@inquirer/type': 1.5.5 + '@types/mute-stream': 0.0.4 + ansi-escapes: 4.3.2 + mute-stream: 1.0.0 + run-async: 3.0.0 + rxjs: 7.8.2 + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 - gopd@1.2.0: {} + is-set@2.0.3: {} - graceful-fs@4.2.11: {} + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 - has-symbols@1.1.0: {} + is-stream@2.0.1: {} - has-tostringtag@1.0.2: + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: dependencies: + call-bound: 1.0.4 has-symbols: 1.1.0 + safe-regex-test: 1.1.0 - hash.js@1.1.7: + is-typed-array@1.1.15: dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 + which-typed-array: 1.1.19 - hasown@2.0.2: + is-typedarray@1.0.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: dependencies: - function-bind: 1.1.2 + call-bound: 1.0.4 - hmac-drbg@1.0.1: + is-weakset@2.0.4: dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 - inherits@2.0.4: {} + isarray@1.0.0: {} - is-fullwidth-code-point@3.0.0: {} + isarray@2.0.5: {} isomorphic-fetch@3.0.0: dependencies: @@ -1064,14 +2953,57 @@ snapshots: transitivePeerDependencies: - encoding + isstream@0.1.2: {} + js-sha3@0.8.0: {} + js-tokens@4.0.0: {} + + jsbn@0.1.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema@0.4.0: {} + + json-stringify-safe@5.0.1: {} + jsonfile@6.2.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + jsprim@1.4.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + + jsprim@2.0.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + + kuler@2.0.0: {} + + lodash@4.17.21: {} + + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lossless-json@4.3.0: {} math-intrinsics@1.1.0: {} @@ -1082,40 +3014,338 @@ snapshots: dependencies: mime-db: 1.52.0 + mime@1.6.0: {} + minimalistic-assert@1.0.1: {} minimalistic-crypto-utils@1.0.1: {} + moment@2.30.1: {} + + ms@2.1.3: {} + + mute-stream@1.0.0: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-telegram-bot-api@0.66.0(request@2.88.2): + dependencies: + '@cypress/request': 3.0.9 + '@cypress/request-promise': 5.0.0(@cypress/request@3.0.9)(request@2.88.2) + array.prototype.findindex: 2.2.4 + bl: 1.2.3 + debug: 3.2.7 + eventemitter3: 3.1.2 + file-type: 3.9.0 + mime: 1.6.0 + pump: 2.0.1 + transitivePeerDependencies: + - request + - supports-color + + oauth-sign@0.9.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + + optimism@0.18.1: + dependencies: + '@wry/caches': 1.0.1 + '@wry/context': 0.7.4 + '@wry/trie': 0.5.0 + tslib: 2.8.1 + + os-tmpdir@1.0.2: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + ox@0.4.4: + dependencies: + '@adraffy/ens-normalize': 1.10.1 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3 + eventemitter3: 5.0.1 + transitivePeerDependencies: + - zod + pako@2.1.0: {} + performance-now@2.1.0: {} + + possible-typed-array-names@1.1.0: {} + + process-nextick-args@2.0.1: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + proxy-from-env@1.1.0: {} psl@1.15.0: dependencies: punycode: 2.3.1 + pump@2.0.1: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + qs@6.5.3: {} + querystringify@2.2.0: {} + react-is@16.13.1: {} + react@19.2.0: {} + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + redeyed@2.1.1: dependencies: esprima: 4.0.1 + redis@4.7.1: + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.1) + '@redis/client': 1.6.1 + '@redis/graph': 1.1.1(@redis/client@1.6.1) + '@redis/json': 1.0.7(@redis/client@1.6.1) + '@redis/search': 1.2.0(@redis/client@1.6.1) + '@redis/time-series': 1.1.0(@redis/client@1.6.1) + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + rehackt@0.1.0(@types/react@19.2.7)(react@19.2.0): + optionalDependencies: + '@types/react': 19.2.7 + react: 19.2.0 + + request-promise-core@1.1.3(request@2.88.2): + dependencies: + lodash: 4.17.21 + request: 2.88.2 + + request@2.88.2: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.2 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + require-directory@2.1.1: {} requires-port@1.0.0: {} resolve-pkg-maps@1.0.0: {} + response-iterator@0.2.25: {} + + run-async@3.0.0: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + source-map@0.5.6: {} + + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + + stack-generator@2.0.10: + dependencies: + stackframe: 1.3.4 + + stack-trace@0.0.10: {} + + stackframe@1.3.4: {} + + stacktrace-gps@3.1.2: + dependencies: + source-map: 0.5.6 + stackframe: 1.3.4 + + stacktrace-js@2.0.2: + dependencies: + error-stack-parser: 2.1.4 + stack-generator: 2.0.10 + stacktrace-gps: 3.1.2 + starknet@6.24.1: dependencies: '@noble/curves': 1.7.0 @@ -1132,18 +3362,29 @@ snapshots: transitivePeerDependencies: - encoding - starknet@8.5.3: + starknet@9.2.1: dependencies: '@noble/curves': 1.7.0 '@noble/hashes': 1.6.0 '@scure/base': 1.2.1 '@scure/starknet': 1.1.0 - '@starknet-io/starknet-types-08': '@starknet-io/types-js@0.8.4' + '@starknet-io/get-starknet-wallet-standard': 5.0.0 + '@starknet-io/starknet-types-010': '@starknet-io/types-js@0.10.0' '@starknet-io/starknet-types-09': '@starknet-io/types-js@0.9.2' abi-wan-kanabi: 2.2.4 lossless-json: 4.3.0 pako: 2.1.0 ts-mixer: 6.0.4 + transitivePeerDependencies: + - typescript + - zod + + stealthy-require@1.1.1: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 string-width@4.2.3: dependencies: @@ -1151,10 +3392,64 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-observable@4.0.0: {} + + text-hex@1.0.0: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + tough-cookie@2.5.0: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -1162,10 +3457,24 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + tr46@0.0.3: {} + triple-beam@1.4.1: {} + + ts-invariant@0.10.3: + dependencies: + tslib: 2.8.1 + ts-mixer@6.0.4: {} + tslib@2.7.0: {} + + tslib@2.8.1: {} + tsx@4.20.6: dependencies: esbuild: 0.25.11 @@ -1173,17 +3482,85 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + tweetnacl@0.14.5: {} + + type-fest@0.21.3: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.19.8: {} + + undici-types@6.21.0: {} + undici-types@7.16.0: {} universalify@0.2.0: {} universalify@2.0.1: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + url-parse@1.5.10: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 + util-deprecate@1.0.2: {} + + uuid@3.4.0: {} + + uuid@8.3.2: {} + + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + webidl-conversions@3.0.1: {} whatwg-fetch@3.6.20: {} @@ -1193,14 +3570,87 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.19.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.8 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + wrappy@1.0.2: {} + + ws@8.17.1: {} + y18n@5.0.8: {} + yallist@4.0.0: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -1212,3 +3662,11 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 + + yoctocolors-cjs@2.1.3: {} + + zen-observable-ts@1.2.5: + dependencies: + zen-observable: 0.8.15 + + zen-observable@0.8.15: {} diff --git a/nohup.log b/nohup.log index a56180f5..ec8f2296 100644 --- a/nohup.log +++ b/nohup.log @@ -27,13 +27,21 @@ help: use `'_` for type paths 88 | pub fn unnamed(&self) -> UnnamedArgs<'_> { | ++++ warning: `snforge_scarb_plugin` (lib) generated 2 warnings - Finished `release` profile [optimized] target(s) in 0.15s + Finished `release` profile [optimized] target(s) in 1.41s Compiling test(starknet_vault_kit_unittest) starknet_vault_kit v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/Scarb.toml) Compiling test(vault_unittest) vault v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/packages/vault/Scarb.toml) -warn: Unused import: `vault::temp::usdt_fixer::UsdtFixer::get_contract_address` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault/src/temp/usdt_fixer.cairo:15:37 - use starknet::{ContractAddress, get_contract_address}; - ^^^^^^^^^^^^^^^^^^^^ +warn: Unused import: `vault::test::units::redemption_router::Zero` + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault/src/test/units/redemption_router.cairo:11:24 +use core::num::traits::Zero; + ^^^^ + +warn[E0001]: Unused variable. Consider ignoring by prefixing with `_`. + --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault/src/test/units/redemption_router.cairo:854:9 + let due_amount: u256 = WAD * 100; + ^^^^^^^^^^ +note: this error originates in the attribute macro: `test` +note: this error originates in the attribute macro: `should_panic` +note: this error originates in the attribute macro: `__internal_config_statement` Compiling test(vault_allocator_unittest) vault_allocator v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/Scarb.toml) warn: Unused import: `vault_allocator::middlewares::avnu_middleware::avnu_middleware::AvnuMiddleware::Map` @@ -84,233 +92,250 @@ Running 0 test(s) from src/ Tests: 0 passed, 0 failed, 0 ignored, 0 filtered out -Collected 15 test(s) from vault package -Running 15 test(s) from src/ -test_claim_sequential_enforcement started +Collected 27 test(s) from vault package +Running 27 test(s) from src/ +deploying redemption router deploying redemption router deploying redemption router with calldata -constructor called +deploying redemption router with calldata +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +deploying redemption router with calldata +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +RELAYER_ROLE granted deploying redemption router with calldata -constructor called -initializing components -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -access_control initialized -role hierarchy set -NFT counter initialized -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed -owner role granted integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router deploying redemption router with calldata -addresses stored -swap counters initialized -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -constructor called -initializing components -access_control initialized -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_vault (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) +[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_redeem_request (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +deploying redemption router with calldata +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +RELAYER_ROLE granted +RELAYER_ROLE granted +deploying redemption router with calldata deploying redemption router with calldata +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -constructor called -role hierarchy set -NFT counter initialized -owner role granted -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +RELAYER_ROLE granted +deploying redemption router with calldata +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +RELAYER_ROLE granted +RELAYER_ROLE granted +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +deploying redemption router with calldata +deploying redemption router with calldata +deploying redemption router +RELAYER_ROLE granted +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +RELAYER_ROLE granted +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +RELAYER_ROLE granted +[PASS] vault::test::units::redemption_router::test_sync_settled_epochs_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~16323200) +deploying redemption router with calldata +[PASS] vault::test::units::redemption_router::test_unsubscribe_twice_reverts (l1_gas: ~0, l1_data_gas: ~9120, l2_gas: ~29808960) +[PASS] vault::test::units::redemption_router::test_set_min_subscribe_amount_only_owner (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~15947840) +[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_to_asset (l1_gas: ~0, l1_data_gas: ~96, l2_gas: ~520000) +[FAIL] vault::test::units::redemption_router::test_unsubscribe_original_nft_fulfilled_but_not_swapped_returns_assets + +Failure data: + 0x557365722073686f756c6420726563656976652066726f6d5f617373657473 ('User should receive from_assets') + +[PASS] vault::test::units::redemption_router::test_unsubscribe_second_user_before_swaps (l1_gas: ~0, l1_data_gas: ~9792, l2_gas: ~41260160) +[PASS] vault::test::units::redemption_router::test_claim_requires_epoch_settled (l1_gas: ~0, l1_data_gas: ~9600, l2_gas: ~40604160) +[PASS] vault::test::units::redemption_router::test_unsubscribe_original_nft_not_fulfilled_returns_nft (l1_gas: ~0, l1_data_gas: ~9120, l2_gas: ~30208960) +[FAIL] vault::test::units::redemption_router::test_claim_two_subscribes_one_swap_two_claims + +Failure data: + "Claim not allowed" + + + "Claim not allowed" + +note: run with `SNFORGE_BACKTRACE=1` environment variable to display a backtrace +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -addresses stored -swap counters initialized -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +deploying redemption router with calldata +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 deploying redemption router with calldata +RELAYER_ROLE granted +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +[PASS] vault::test::units::redemption_router::test_unsubscribe_third_user_after_second_withdrawn (l1_gas: ~0, l1_data_gas: ~10368, l2_gas: ~54841280) +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +RELAYER_ROLE granted deploying redemption router with calldata -[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_to_asset (l1_gas: ~0, l1_data_gas: ~96, l2_gas: ~480000) -constructor called -initializing components -constructor called -initializing components -access_control initialized -access_control initialized -role hierarchy set -NFT counter initialized -role hierarchy set -NFT counter initialized -owner role granted -addresses stored -swap counters initialized -owner role granted -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -addresses stored -swap counters initialized +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set RELAYER_ROLE granted -minting old NFTs -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 +deploying redemption router deploying redemption router with calldata -constructor called -initializing components -access_control initialized -role hierarchy set deploying redemption router with calldata -NFT counter initialized -owner role granted -addresses stored -swap counters initialized +[PASS] vault::test::units::redemption_router::test_swap_executes_successfully (l1_gas: ~0, l1_data_gas: ~8064, l2_gas: ~21170880) +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +deploying redemption router with calldata +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +deploying redemption router with calldata +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +deploying redemption router with calldata RELAYER_ROLE granted -approving NFTs -approving NFT 1 -constructor called -initializing components -access_control initialized RELAYER_ROLE granted -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -role hierarchy set -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -NFT counter initialized -owner role granted -addresses stored -swap counters initialized -approving NFT 2 +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +RELAYER_ROLE granted +[PASS] vault::test::units::redemption_router::test_swap_reverts_when_not_relayer (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~16588800) +RELAYER_ROLE granted +deploying redemption router with calldata +RELAYER_ROLE granted deploying redemption router with calldata -constructor called -initializing components -access_control initialized -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -role hierarchy set -NFT counter initialized +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +[PASS] vault::test::units::redemption_router::test_subscribe_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~8640, l2_gas: ~23504960) RELAYER_ROLE granted RELAYER_ROLE granted -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router +[PASS] vault::test::units::redemption_router::test_swap_uses_actual_received_amount (l1_gas: ~0, l1_data_gas: ~8064, l2_gas: ~21130880) +[FAIL] vault::test::units::redemption_router::test_subscribe_transfers_old_nft_and_mints_new + +Failure data: + 0x44756520616d6f756e742073746f72656420696e636f72726563746c79 ('Due amount stored incorrectly') + +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 +avnu_exchange deployed +integrator_fee_recipient and integrator_fee_amount_bps set +deploying redemption router +deploying redemption router with calldata +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 RELAYER_ROLE granted -owner role granted -addresses stored -swap counters initialized deploying redemption router with calldata -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -constructor called -initializing components -access_control initialized -subscribed NFT 2 +[PASS] vault::test::units::redemption_router::test_subscribe_reverts_on_too_small_amount (l1_gas: ~0, l1_data_gas: ~8448, l2_gas: ~23368640) +[FAIL] vault::test::units::redemption_router::test_claim_single_subscribe_single_swap_single_claim + +Failure data: + "Claim not allowed" + + + "Claim not allowed" + +note: run with `SNFORGE_BACKTRACE=1` environment variable to display a backtrace +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +[FAIL] vault::test::units::redemption_router::test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts + +Failure data: + Expected to panic, but no panic occurred + Expected panic data: [0x46a6158a16a947e5916b2a2ca68501a45e93d7110e81aa2d6438b1c57c879a3, 0x1, 0x43616e6e6f7420756e7375627363726962653a207377617073206861766520, 0x7061727469616c6c7920636f6e73756d656420617373657473, 0x19] (, , Cannot unsubscribe: swaps have , partially consumed assets, ) + RELAYER_ROLE granted setup done -fulfilled old NFTs -transferred assets -minted to_asset -role hierarchy set -NFT counter initialized -owner role granted -addresses stored -swap counters initialized -claiming NFT 0 +[PASS] vault::test::units::redemption_router::test_swap_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~16443200) addresses stored correctly roles set swap_id and unsettled_swap_id start at 1 NFT counter starts at 0 -swapped -attempting to claim NFT 2 before NFT 1 -claiming NFT 1 -from_rem: 200000000000000000000 -deploying redemption router with calldata -to_rem: 400000000000000000000 -constructor called -initializing components -access_control initialized -role hierarchy set -NFT counter initialized -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -owner role granted -addresses stored -swap counters initialized -claiming NFT 1 -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -RELAYER_ROLE granted -RELAYER_ROLE granted -[PASS] vault::test::units::redemption_router::test_swap_reverts_when_not_relayer (l1_gas: ~0, l1_data_gas: ~5184, l2_gas: ~12897920) -[PASS] vault::test::units::redemption_router::test_claim_two_subscribes_one_swap_two_claims (l1_gas: ~0, l1_data_gas: ~6816, l2_gas: ~36420160) -[PASS] vault::test::units::redemption_router::test_swap_reverts_on_insufficient_balance (l1_gas: ~0, l1_data_gas: ~5088, l2_gas: ~12136960) -[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_redeem_request (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) -[PASS] vault::test::units::redemption_router::test_constructor_initializes_correctly (l1_gas: ~0, l1_data_gas: ~5088, l2_gas: ~13536960) -[PASS] vault::test::units::redemption_router::test_subscribe_transfers_old_nft_and_mints_new (l1_gas: ~0, l1_data_gas: ~6336, l2_gas: ~17337600) -[PASS] vault::test::units::redemption_router::test_swap_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~5184, l2_gas: ~12712320) -[PASS] vault::test::units::redemption_router::test_swap_executes_successfully (l1_gas: ~0, l1_data_gas: ~5664, l2_gas: ~17069760) -[PASS] vault::test::units::redemption_router::test_subscribe_increments_nft_counter (l1_gas: ~0, l1_data_gas: ~7104, l2_gas: ~21458240) -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -[PASS] vault::test::units::redemption_router::test_claim_sequential_enforcement (l1_gas: ~0, l1_data_gas: ~7200, l2_gas: ~29253440) deploying redemption router with calldata -constructor called -initializing components -access_control initialized -role hierarchy set -NFT counter initialized -owner role granted -addresses stored -swap counters initialized -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -RELAYER_ROLE granted -deploying redemption router -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set +[PASS] vault::test::units::redemption_router::test_subscribe_increments_nft_counter (l1_gas: ~0, l1_data_gas: ~9792, l2_gas: ~37530240) deploying redemption router -deploying redemption router with calldata -constructor called -avnu_address: 2974402149641590712284684492250344113565912102804890602759248512068283105355 +RELAYER_ROLE granted +avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 avnu_exchange deployed integrator_fee_recipient and integrator_fee_amount_bps set deploying redemption router -[PASS] vault::test::units::redemption_router::test_swap_uses_actual_received_amount (l1_gas: ~0, l1_data_gas: ~5664, l2_gas: ~17069760) +[FAIL] vault::test::units::redemption_router::test_unsubscribe_second_user_after_fulfillment_but_before_swaps + +Failure data: + 0x55736572203220726563656976656420323030 ('User 2 received 200') + +[PASS] vault::test::units::redemption_router::test_constructor_initializes_correctly (l1_gas: ~0, l1_data_gas: ~7488, l2_gas: ~17427840) deploying redemption router with calldata -constructor called -initializing components -access_control initialized -role hierarchy set -NFT counter initialized -owner role granted -addresses stored -swap counters initialized deploying redemption router with calldata -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -constructor called -initializing components -access_control initialized -role hierarchy set -NFT counter initialized -owner role granted -addresses stored -swap counters initialized +[PASS] vault::test::units::redemption_router::test_set_min_subscribe_amount_reverts_when_not_owner (l1_gas: ~0, l1_data_gas: ~7488, l2_gas: ~15787840) +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 RELAYER_ROLE granted -router: 1341007589477684026508707867424700544582605365157133884134555647548294500017 -[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_vault (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) +router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 RELAYER_ROLE granted -claiming NFT 0 -[PASS] vault::test::units::redemption_router::test_subscribe_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~5760, l2_gas: ~14574720) -[PASS] vault::test::units::redemption_router::test_claim_single_subscribe_single_swap_single_claim (l1_gas: ~0, l1_data_gas: ~6144, l2_gas: ~25864960) -Tests: 15 passed, 0 failed, 0 ignored, 87 filtered out +[PASS] vault::test::units::redemption_router::test_swap_reverts_on_insufficient_balance (l1_gas: ~0, l1_data_gas: ~7488, l2_gas: ~15867840) +[FAIL] vault::test::units::redemption_router::test_sync_settled_epochs + +Failure data: + 0x6c6173745f736574746c65645f65706f63682073686f756c642062652031 ('last_settled_epoch should be 1') + +Tests: 20 passed, 7 failed, 0 ignored, 87 filtered out [ERROR] data did not match any variant of untagged enum JsonRpcResponse diff --git a/package.json b/package.json new file mode 100644 index 00000000..a16cb056 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@strkfarm/sdk": "link:../sdk-ts" + } +} diff --git a/packages/vault/src/redemption_router/INTEGRATION.md b/packages/vault/src/redemption_router/INTEGRATION.md index c90cfbdd..2e79c7a8 100644 --- a/packages/vault/src/redemption_router/INTEGRATION.md +++ b/packages/vault/src/redemption_router/INTEGRATION.md @@ -1,18 +1,137 @@ -# Redemption Router Integration Guidelines +# Redemption Router Integration Guide -## Subscribe Flow +This guide explains how to integrate the Redemption Router contract for both backend services (relayers/indexers) and frontend applications. -When a user wants to redeem vault shares and receive assets in a different token via the Redemption Router: +## Table of Contents -1. **Request Redemption**: Call `vault.request_redeem(shares, receiver, owner)` to receive a RedeemRequest NFT -2. **Approve & Subscribe**: In the same multicall transaction: - - Approve the RedeemRequest NFT to the RedemptionRouter contract - - Call `redemptionRouter.subscribe(nft_id, receiver)` to transfer the redemption NFT to the router -3. **Receive New NFT**: The router transfers the user's RedeemRequest NFT to itself and mints a new RedemptionRouter NFT to the user -4. **Claim Assets**: Once the relayer has swapped assets and settled claims, the user calls `redemptionRouter.claim(new_nft_id)` which burns the NFT and transfers the swapped `to_asset` tokens to the user +- [Overview](#overview) +- [Backend Integration](#backend-integration) + - [Indexing Pending Subscriptions](#indexing-pending-subscriptions) + - [Checking Epoch Settlement Status](#checking-epoch-settlement-status) + - [Executing Swaps](#executing-swaps) + - [Processing Claims](#processing-claims) +- [Frontend Integration](#frontend-integration) + - [Request Redeem and Subscribe (Multicall)](#request-redeem-and-subscribe-multicall) + - [Checking Subscription Status](#checking-subscription-status) + - [Unsubscribing](#unsubscribing) + - [Claiming Redeemed Assets](#claiming-redeemed-assets) -**Note**: The subscribe operation should be done atomically with the approval in a multicall to ensure the NFT is immediately subscribed after redemption request, preventing any intermediate state where the NFT could be lost or mishandled. +## Overview +The Redemption Router allows users to: +1. **Subscribe** their redemption NFTs to receive assets in a different token (`to_asset`) instead of the vault's native asset +2. **Claim** their swapped assets once epochs are settled and swaps are executed +3. **Unsubscribe** if they want to opt out before settlement +Backend services (relayers) are responsible for: +- Monitoring pending subscriptions +- Executing swaps when epochs are settled +- Managing the swap pool +## Reporting +For simplicity, its recommended to call report function on vault contract via this contract. Else, its important to set the epoch offset factor for each epoch before calling swap. +Note: Until audit of RR, our (Troves team) backend shall follow manually setting epoch offset factor for each epoch before calling swap. This is to avoid transfering oracle permission to RR for vaults already in production. + +## Backend Integration + +### Indexing Pending Subscriptions + +To track pending subscriptions, you need to index the `Subscribed` event emitted by the Redemption Router contract. + +#### You could use apibara to index events. +``` + +#### Decoding Subscribed Event + +The `Subscribed` event has the following structure: +```cairo +Subscribed { + new_nft_id: u256, + old_nft_id: u256, + receiver: ContractAddress, +} +``` + +Store this information in your database to track pending subscriptions. + +Additionally, more events to track: +1. Claimed +```cairo +Claimed { + new_nft_id: u256, + old_nft_id: u256, + receivable: u256, + swap_id: u256, +} +``` +2. Unsubscribed +```cairo +Unsubscribed { + new_nft_id: u256, + old_nft_id: u256, + owner: ContractAddress, + is_old_nft_returned: bool, + is_original_assets_returned: bool, + original_assets_returned: u256, +} +``` + +### Checking Epoch Settlement Status + +Before executing swaps, you need to check where required epochs are already handled by the vault. An epoch is considered "handled" when the vault has handled it (i.e., `epoch < vault.handled_epoch_len()`). + +Get above info by calling vault contract. + +#### Key Functions to Monitor + +- `vault.handled_epoch_len()` - Returns the number of handled epochs +- `router.last_settled_epoch()` - Returns the highest fully settled epoch in router (i.e. assets swapped and ready for claims) +- `router.epoch_settled_amounts(epoch)` - Returns how much of an epoch has been settled (i.e. swapped from asset) +- `router.epoch_offset_factor(epoch)` - Returns the offset factor for an epoch (defaults to WAD) (i.e. nft shares to asset conversion factor. precisely, final assets / shares settled for which can vary in a epoch due to epoch level vault losses) + + +### Swapping +Read the balance of the redemption router contract of the from asset (i.e. vault asset). You can have some min check, but if enough balance is available, you can proceed to swap. Ensure you add sufficient min amoutn out checks. + +Call `router.swap(routes: Array, from_amount: u256, min_amount_out: u256)` to swap the assets. +Can get routes from Avnu. + +#### Monitoring Swap Pool + +The router maintains a swap pool that users claim from. Monitor: +- `router.swap_id()` - Current swap ID (next swap will use this) +- `router.unsettled_swap_id()` - First swap ID that still has remaining assets +- `router.swap_info(swap_id)` - Returns `(from_remaining, to_remaining)` for a swap + +### Processing Claims + +Users can claim their assets once their epoch is settled. To settle NFTs, you use the Claimed event to check events which are still pending. Query them, order them by epoch (asc). If epoch is settled, you can claim the assets. + +Call `router.claim(nft_id)` to claim the assets. + +## Frontend Integration + +### Request Redeem and Subscribe (Multicall) + +To provide a seamless UX, combine `vault.request_redeem()` and `router.subscribe()` in a single multicall transaction. + +1. Read next NFT ID to mint (i.e. `redeem_request.id_len()`) +2. Create a redeem request on vault (i.e. `vault.request_redeem(shares, receiver, user_address)`) +3. Approve the NFT to RR (i.e. `redeem_request.approve(router_address, nft_id)`) +4. Subscribe to the router (i.e. `router.subscribe(nft_id, receiver)`) + +Put all above 3 steps into a single starknet multicall transaction. + +In rare cases, the step 1 might cause race condition, in that case, simply retrying the transaction should work. + +## Unsubscribing +In case the router couldnt settle the funds in to_asset or its taking time, users might want to unsubscribe. + +Users can unsubscribe in two ways: +1. **`unsubscribe_for_nft`** - Get back the original redemption NFT (if epoch not settled) +2. **`unsubscribe_for_underlying`** - Get back the underlying assets proportional to their share (only possible if router assets are not fully settled for the epoch. Even partial swap shall prevent this, in such a case, users must wait for their funds to be fully swapped). + +### How to decide which function to call? +- Call `vault.due_assets_from_id(old_nft_id)` to get the due assets for the NFT. If returns non-zero, call `unsubscribe_for_nft`. If the call fails or returns 0, call `unsubscribe_for_underlying`. +- The `unsubscribe_for_underlying` shall only work if epoch is not settled. Call `router.epoch_settled_amounts(epoch)`, if returns 0, you can proceed. Else better to inform users to wait as swapping has already started and is in progress. \ No newline at end of file diff --git a/packages/vault/src/redemption_router/interface.cairo b/packages/vault/src/redemption_router/interface.cairo index 7f755df3..11582ea3 100644 --- a/packages/vault/src/redemption_router/interface.cairo +++ b/packages/vault/src/redemption_router/interface.cairo @@ -38,6 +38,7 @@ pub trait IRedemptionRouter { fn set_integrator_fee_recipient(ref self: TContractState, recipient: ContractAddress); fn set_integrator_fee_amount_bps(ref self: TContractState, fee_bps: u128); fn set_epoch_offset(ref self: TContractState, epoch: u256, offset_factor: u256); + fn get_epoch_offset(self: @TContractState, epoch: u256) -> u256; fn report(ref self: TContractState, new_aum: u256); fn set_min_subscribe_amount(ref self: TContractState, min_subscribe_amount: u256); diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index 5494b8cc..64d675ed 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -90,9 +90,8 @@ pub mod RedemptionRouter { epoch_redeem_assets: Map, // epoch -> snapshot of redeem_assets at subscribe time epoch_redeem_nominal: Map, // epoch -> snapshot of redeem_nominal at subscribe time epoch_wise_nominals: Map, // epoch -> from_amount (nominal amount subscribed for this epoch) - epoch_settled_amounts: Map, // epoch -> actual settled from_amount received + epoch_settled_amounts: Map, // epoch -> actual settled (i.e. swapped) from_amount last_settled_epoch: u256, // Highest epoch number that has been fully settled (may not be sequential if some epochs have no subscriptions) - old_nft_fulfilled: Map, // old_nft_id -> whether the old NFT has been fulfilled (burned) min_subscribe_amount: u256, } @@ -119,15 +118,21 @@ pub mod RedemptionRouter { #[derive(Drop, starknet::Event)] pub struct Subscribed { + #[key] pub new_nft_id: u256, + #[key] pub old_nft_id: u256, + #[key] pub receiver: ContractAddress, } #[derive(Drop, starknet::Event)] pub struct Unsubscribed { + #[key] pub new_nft_id: u256, + #[key] pub old_nft_id: u256, + #[key] pub owner: ContractAddress, pub is_old_nft_returned: bool, // true if old NFT returned as is pub is_original_assets_returned: bool, // true if original assets returned proportional to user's share @@ -136,6 +141,7 @@ pub mod RedemptionRouter { #[derive(Drop, starknet::Event)] pub struct Swapped { + #[key] pub swap_id: u256, pub from_amount: u256, pub to_amount: u256, @@ -144,10 +150,13 @@ pub mod RedemptionRouter { #[derive(Drop, starknet::Event)] pub struct Claimed { + #[key] pub new_nft_id: u256, + #[key] pub old_nft_id: u256, - pub receivable: u256, + #[key] pub swap_id: u256, + pub receivable: u256, } #[constructor] @@ -384,11 +393,18 @@ pub mod RedemptionRouter { ) -> u256 { // Get the vault's current epoch to know the upper bound let vault_dispatcher = IVaultDispatcher { contract_address: self.vault.read() }; - let max_epoch = vault_dispatcher.handled_epoch_len() - 1; // -1 because we are using 0-indexed epochs + let handled_epoch_len = vault_dispatcher.handled_epoch_len(); + + // If no epochs have been handled yet, return early + if (handled_epoch_len == 0) { + return self.last_settled_epoch.read(); + } + + let max_epoch = handled_epoch_len - 1; // -1 because we are using 0-indexed epochs // Start from last_settled_epoch + 1 to avoid re-checking already settled epochs let last_settled_epoch = self.last_settled_epoch.read(); - let mut current_epoch = last_settled_epoch + 1; + let mut current_epoch = if last_settled_epoch == 0 { 0 } else { last_settled_epoch + 1 }; let mut highest_settled_epoch: u256 = last_settled_epoch; let mut epochs_checked: u256 = 0; @@ -425,16 +441,15 @@ pub mod RedemptionRouter { ); let already_settled = self.epoch_settled_amounts.read(current_epoch); - let remaining_to_settle = expected_settled - already_settled; - - // Skip if epoch is already fully settled - if (remaining_to_settle == 0) { + // Check if epoch is already fully settled (handle case where already_settled >= expected_settled) + if (already_settled >= expected_settled) { if (current_epoch > highest_settled_epoch) { highest_settled_epoch = current_epoch; } current_epoch = current_epoch + 1; continue; } + let remaining_to_settle = expected_settled - already_settled; let settle_amount = if (remaining_from >= remaining_to_settle) { remaining_to_settle @@ -458,6 +473,46 @@ pub mod RedemptionRouter { } } + // Phase 2: Sync epochs (check which epochs are already fully settled) + // This is needed when sync_settled_epochs is called separately after swaps + while (current_epoch <= max_epoch) { + epochs_checked = epochs_checked + 1; + // designed to prevent excessive gas usage + if (!check_all && epochs_checked > max_epochs_to_check) { + break; + } + + let epoch_nominal = self.epoch_redeem_nominal.read(current_epoch); + + // Skip epochs without subscriptions + if (epoch_nominal == 0) { + current_epoch = current_epoch + 1; + continue; + } + + // Check if epoch is already fully settled + let effective_offset = self._get_effective_offset_factor(current_epoch); + let expected_settled = math::u256_mul_div( + epoch_nominal, + effective_offset, + WAD, + math::Rounding::Floor + ); + + let already_settled = self.epoch_settled_amounts.read(current_epoch); + + // If epoch is fully settled, update highest_settled_epoch + if (already_settled >= expected_settled && expected_settled > 0) { + if (current_epoch > highest_settled_epoch) { + highest_settled_epoch = current_epoch; + } + current_epoch = current_epoch + 1; + } else { + // Epoch not fully settled, stop here + break; + } + } + // ideally, only sync until the last settled epoch let mut last_settled_epoch = highest_settled_epoch; @@ -683,8 +738,6 @@ pub mod RedemptionRouter { // Transfer original NFT from caller to this contract self._transfer_original_nft(nft_id: nft_id, from_address: caller, to_address: get_contract_address()); - // Mark old NFT as not fulfilled yet (router owns it now) - self.old_nft_fulfilled.write(nft_id, false); // Mint new NFT to receiver let new_nft_id = self.nft_id_counter.read(); @@ -781,7 +834,7 @@ pub mod RedemptionRouter { } to_asset_dispatcher.transfer(owner, total_to); - self.emit(Claimed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id, receivable: total_to, swap_id: self.unsettled_swap_id.read() }); + self.emit(Claimed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id, swap_id: self.unsettled_swap_id.read(), receivable: total_to }); total_to } @@ -858,6 +911,10 @@ pub mod RedemptionRouter { self.epoch_offset_factor.write(epoch, offset_factor); } + fn get_epoch_offset(self: @ContractState, epoch: u256) -> u256 { + self.epoch_offset_factor.read(epoch) + } + fn report(ref self: ContractState, new_aum: u256) { self.access_control.assert_only_role(RELAYER_ROLE); diff --git a/packages/vault/src/test/units/redemption_router.cairo b/packages/vault/src/test/units/redemption_router.cairo index fdfbf28b..fa47e118 100644 --- a/packages/vault/src/test/units/redemption_router.cairo +++ b/packages/vault/src/test/units/redemption_router.cairo @@ -6,21 +6,24 @@ use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; use snforge_std::{ CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, - start_mock_call, store, map_entry_address, }; -use starknet::ContractAddress; +use starknet::{ContractAddress, get_block_timestamp}; use core::array::ArrayTrait; use vault::redeem_request::interface::{ - IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait, RedeemRequestInfo, + IRedeemRequestDispatcher, }; use vault::redemption_router::interface::{ IRedemptionRouterDispatcher, IRedemptionRouterDispatcherTrait, }; use vault::redemption_router::redemption_router::RedemptionRouter; -use vault::test::utils::{OWNER, USER1, USER2, WAD, deploy_erc20_mock, deploy_redeem_request}; +use vault::test::utils::{OWNER, USER1, USER2, WAD, deploy_erc20_mock, deploy_redeem_request, deploy_vault, VAULT_ALLOCATOR, ORACLE}; +use snforge_std::start_cheat_block_timestamp_global; +use vault::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; +use vault::vault::vault::Vault; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; use vault_allocator::mocks::mock_avnu_exchange::IAvnuExchangeDispatcher; @@ -70,17 +73,42 @@ fn deploy_mock_avnu_exchange() -> IAvnuExchangeDispatcher { } fn set_up() -> ( - ContractAddress, // vault (dummy address) + IVaultDispatcher, // vault ContractAddress, // from_asset ContractAddress, // to_asset IRedeemRequestDispatcher, // redeem_request IAvnuExchangeDispatcher, // avnu_exchange IRedemptionRouterDispatcher, // router ) { - let dummy_vault = 'DUMMY_VAULT'.try_into().unwrap(); + let from_asset = deploy_erc20_mock(); let to_asset = deploy_erc20_mock(); - let redeem_request = deploy_redeem_request(dummy_vault); + let vault = deploy_vault( + from_asset + ); + let vault_address = vault.contract_address; + + let redeem_request = deploy_redeem_request(vault_address); + // Register the deployed redeem_request with vault + cheat_caller_address(vault_address, OWNER(), span: CheatSpan::TargetCalls(1)); + vault.register_redeem_request(redeem_request.contract_address); + + // Register vault_allocator if needed + let vault_allocator = VAULT_ALLOCATOR(); + cheat_caller_address(vault_address, OWNER(), span: CheatSpan::TargetCalls(1)); + vault.register_vault_allocator(vault_allocator); + + // overwrite set fees + cheat_caller_address(vault_address, OWNER(), span: CheatSpan::TargetCalls(1)); + vault.set_fees_config(OWNER(), 0, 0, Vault::WAD / 10); + + // Grant ORACLE_ROLE to ORACLE for report calls + let access_control_vault = IAccessControlDispatcher { + contract_address: vault_address, + }; + cheat_caller_address(vault_address, OWNER(), span: CheatSpan::TargetCalls(1)); + access_control_vault.grant_role(Vault::ORACLE_ROLE, ORACLE()); + let avnu_exchange = deploy_mock_avnu_exchange(); println!("avnu_exchange deployed"); let integrator_fee_recipient = 'FEE_RECIPIENT'.try_into().unwrap(); @@ -88,11 +116,8 @@ fn set_up() -> ( let min_subscribe_amount: u256 = 0; // No minimum by default for tests println!("integrator_fee_recipient and integrator_fee_amount_bps set"); - // Mock vault's handled_epoch_len for constructor initialization - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 0_u256); - let router = deploy_redemption_router( - dummy_vault, + vault_address, redeem_request.contract_address, to_asset, avnu_exchange.contract_address, @@ -112,37 +137,120 @@ fn set_up() -> ( cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); access_control.grant_role(selector!("PAUSER_ROLE"), OWNER()); println!("RELAYER_ROLE granted"); - (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) + + // to avoid zero liquidity error, seed some initial liquidity + let due_amount_1: u256 = WAD * 100; + mint_old_nft_to_user(vault, OWNER(), due_amount_1); + + (vault, from_asset, to_asset, redeem_request, avnu_exchange, router) } fn mint_old_nft_to_user( - redeem_request: IRedeemRequestDispatcher, vault_address: ContractAddress, user: ContractAddress, - epoch: u256, nominal: u256, + vault: IVaultDispatcher, user: ContractAddress, nominal: u256, ) -> u256 { - let redeem_request_info = RedeemRequestInfo { epoch, nominal }; - cheat_caller_address( - redeem_request.contract_address, vault_address, span: CheatSpan::TargetCalls(1), - ); - redeem_request.mint(user, redeem_request_info) + // First, user needs to deposit assets to get vault shares + // Get asset address from vault + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + let asset_address = erc4626_dispatcher.asset(); + + // Transfer underlying assets to user + let asset_dispatcher = ERC20ABIDispatcher { contract_address: asset_address }; + cheat_caller_address(asset_address, OWNER(), span: CheatSpan::TargetCalls(1)); + asset_dispatcher.transfer(user, nominal); + + // User approves vault to spend assets + cheat_caller_address(asset_address, user, span: CheatSpan::TargetCalls(1)); + asset_dispatcher.approve(vault.contract_address, nominal); + + // User deposits assets to get shares + cheat_caller_address(vault.contract_address, user, span: CheatSpan::TargetCalls(1)); + let shares = erc4626_dispatcher.deposit(nominal, user); + + shares +} + +fn mint_and_redeem_old_nft_to_user( + vault: IVaultDispatcher, user: ContractAddress, nominal: u256, +) -> u256 { + let shares = mint_old_nft_to_user(vault, user, nominal); + + // Call request_redeem on vault as if user is trying to withdraw + // Now call request_redeem as the user + cheat_caller_address(vault.contract_address, user, span: CheatSpan::TargetCalls(1)); + vault.request_redeem(shares, user, user) } +fn report(vault: IVaultDispatcher, from_asset: ContractAddress) { + // increase timestamp, else report fails + let now = get_block_timestamp(); + start_cheat_block_timestamp_global(now + 3600); // 1 hour + + // First, call report on vault to handle epochs + // Report needs ORACLE_ROLE + let oracle = ORACLE(); + + // After report, if all epochs are handled, excess funds are sent to vault_allocator + // We need to mock bring_liquidity to get them back + let vault_allocator_addr = vault.vault_allocator(); + // bring_liquidity transfers FROM caller TO vault + let asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + let allocator_balance = asset_dispatcher.balance_of(vault_allocator_addr); + if allocator_balance > 0 { + // Transfer funds from allocator back to vault (simulating bring_liquidity) + cheat_caller_address(from_asset, vault_allocator_addr, span: CheatSpan::TargetCalls(1)); + asset_dispatcher.approve(vault.contract_address, allocator_balance); + + // Mock the bring_liquidity call to update vault state + cheat_caller_address(vault.contract_address, vault_allocator_addr, span: CheatSpan::TargetCalls(1)); + vault.bring_liquidity(allocator_balance); + } + + let handled_epoch_len = vault.handled_epoch_len(); + println!("pre::handled_epoch_len: {}", handled_epoch_len); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + println!("pre::total_assets: {}", erc4626_dispatcher.total_assets()); + println!("pre::total_supply: {}", erc4626_dispatcher.total_assets()); + + // Call report as oracle + cheat_caller_address(vault.contract_address, oracle, span: CheatSpan::TargetCalls(1)); + vault.report(0); // no assets in vault allocator + let handled_epoch_len = vault.handled_epoch_len(); + println!("post::handled_epoch_len: {}", handled_epoch_len); + println!("post::total_assets: {}", erc4626_dispatcher.total_assets()); + println!("post::total_supply: {}", erc4626_dispatcher.total_assets()); + +} fn fulfill_old_nft( - redeem_request: IRedeemRequestDispatcher, vault_address: ContractAddress, nft_id: u256, + vault: IVaultDispatcher, from_asset: ContractAddress, nft_id: u256, ) { - cheat_caller_address( - redeem_request.contract_address, vault_address, span: CheatSpan::TargetCalls(1), - ); - redeem_request.burn(nft_id); + + report(vault, from_asset); + let asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + + // Now claim_redeem on vault + // Get NFT owner first + let redeem_request_addr = vault.redeem_request(); + let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request_addr }; + let nft_owner = erc721_dispatcher.owner_of(nft_id); + + let owner_balance = asset_dispatcher.balance_of(nft_owner); + println!("owner_balance: {}", owner_balance); + + // Call claim_redeem as the NFT owner + cheat_caller_address(vault.contract_address, nft_owner, span: CheatSpan::TargetCalls(1)); + vault.claim_redeem(nft_id); + let owner_balance_after = asset_dispatcher.balance_of(nft_owner); + println!("owner_balance_after: {}", owner_balance_after); } fn mark_old_nft_fulfilled(router_address: ContractAddress, old_nft_id: u256) { - // Mark old NFT as fulfilled in router's storage - let mut cheat_calldata_key = ArrayTrait::new(); - old_nft_id.serialize(ref cheat_calldata_key); - let mut cheat_calldata_value = ArrayTrait::new(); - true.serialize(ref cheat_calldata_value); - let map_entry = map_entry_address(selector!("old_nft_fulfilled"), cheat_calldata_key.span()); - store(router_address, map_entry, cheat_calldata_value.span()); + // // Mark old NFT as fulfilled in router's storage + // let mut cheat_calldata_key = ArrayTrait::new(); + // old_nft_id.serialize(ref cheat_calldata_key); + // let mut cheat_calldata_value = ArrayTrait::new(); + // true.serialize(ref cheat_calldata_value); + // let map_entry = map_entry_address(selector!("old_nft_fulfilled"), cheat_calldata_key.span()); + // store(router_address, map_entry, cheat_calldata_value.span()); } // ============================================================================ @@ -151,11 +259,11 @@ fn mark_old_nft_fulfilled(router_address: ContractAddress, old_nft_id: u256) { #[test] fn test_constructor_initializes_correctly() { - let (dummy_vault, _, to_asset, redeem_request, avnu_exchange, router) = set_up(); + let (vault, _, to_asset, redeem_request, avnu_exchange, router) = set_up(); println!("setup done"); // Verify addresses are stored correctly - assert(router.vault() == dummy_vault, 'Vault address incorrect'); + assert(router.vault() == vault.contract_address, 'Vault address incorrect'); assert(router.redeem_request() == redeem_request.contract_address, 'Redeem request incorrect'); assert(router.to_asset() == to_asset, 'To asset incorrect'); assert(router.avnu_exchange() == avnu_exchange.contract_address, 'Avnu exchange incorrect'); @@ -241,18 +349,12 @@ fn test_constructor_reverts_zero_to_asset() { #[test] fn test_subscribe_transfers_old_nft_and_mints_new() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); - // Mint old NFT to user - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); - let epoch: u256 = 1; - let nominal: u256 = 100; - let due_amount: u256 = WAD * nominal; // due_amount equals nominal in WAD - - // Mock vault functions needed by subscribe - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * nominal); // Total redeem assets for epoch - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * nominal); // Total redeem nominal for epoch + // Mint old NFT to user by calling request_redeem on vault + let due_amount: u256 = WAD * 100; // due_amount equals nominal in WAD + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); + let epoch: u256 = vault.epoch(); // Get current epoch from vault // User approves router let erc721_dispatcher = ERC721ABIDispatcher { @@ -288,14 +390,11 @@ fn test_subscribe_transfers_old_nft_and_mints_new() { #[test] fn test_subscribe_increments_nft_counter() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); // Subscribe first NFT - let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount_1: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id_1 = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount_1); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -307,12 +406,8 @@ fn test_subscribe_increments_nft_counter() { assert(new_nft_id_1 == 0, 'First NFT ID should be 0'); // Subscribe second NFT - let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); let due_amount_2: u256 = WAD * 200; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); - // Note: redeem_assets and redeem_nominal should now include both users (300 total) - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); + let old_nft_id_2 = mint_and_redeem_old_nft_to_user(vault, USER2(), due_amount_2); cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_2); @@ -324,18 +419,15 @@ fn test_subscribe_increments_nft_counter() { #[test] #[should_panic(expected: ('Pausable: paused',))] fn test_subscribe_reverts_when_paused() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); // Pause contract cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); router.pause(); // Attempt subscribe - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -349,16 +441,15 @@ fn test_subscribe_reverts_when_paused() { #[test] #[should_panic(expected: "Too small subscribe amount")] fn test_subscribe_reverts_on_too_small_amount() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); // Set min_subscribe_amount cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); router.set_min_subscribe_amount(WAD * 100); // Attempt subscribe with amount below minimum - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 50); // 50 < 100 let due_amount: u256 = WAD * 50; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); // 50 < 100 let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -375,13 +466,7 @@ fn test_subscribe_reverts_on_too_small_amount() { #[test] fn test_swap_executes_successfully() { - let (dummy_vault, from_asset, to_asset, _, avnu_exchange, router) = set_up(); - - // Mock vault asset and epoch functions - // handled_epoch_len must be at least 1 to avoid underflow (even if no epochs are handled) - start_mock_call(dummy_vault, selector!("asset"), from_asset); - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 1_u256); // At least 1 to avoid underflow - start_mock_call(dummy_vault, selector!("epoch"), 0_u256); // Current epoch is 0 + let (vault, from_asset, to_asset, _, avnu_exchange, router) = set_up(); // Mint from_asset tokens to router let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; @@ -417,10 +502,7 @@ fn test_swap_executes_successfully() { #[test] #[should_panic(expected: "Insufficient from amount")] fn test_swap_reverts_on_insufficient_balance() { - let (dummy_vault, from_asset, _, _, _, router) = set_up(); - - // Mock vault asset - start_mock_call(dummy_vault, selector!("asset"), from_asset); + let (vault, from_asset, _, _, _, router) = set_up(); // Don't mint any tokens to router (has 0 balance) @@ -436,10 +518,7 @@ fn test_swap_reverts_on_insufficient_balance() { #[test] #[should_panic(expected: ('Caller is missing role',))] fn test_swap_reverts_when_not_relayer() { - let (dummy_vault, from_asset, _, _, _, router) = set_up(); - - // Mock vault asset - start_mock_call(dummy_vault, selector!("asset"), from_asset); + let (vault, from_asset, _, _, _, router) = set_up(); // Mint tokens to router let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; @@ -458,15 +537,12 @@ fn test_swap_reverts_when_not_relayer() { #[test] #[should_panic(expected: ('Pausable: paused',))] fn test_swap_reverts_when_paused() { - let (dummy_vault, from_asset, _, _, _, router) = set_up(); + let (vault, from_asset, _, _, _, router) = set_up(); // Pause contract cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); router.pause(); - // Mock vault asset - start_mock_call(dummy_vault, selector!("asset"), from_asset); - // Attempt swap let routes: Array = array![]; let from_amount: u256 = WAD * 5; @@ -484,12 +560,7 @@ fn test_swap_reverts_when_paused() { #[test] fn test_swap_uses_actual_received_amount() { - let (dummy_vault, from_asset, to_asset, _, avnu_exchange, router) = set_up(); - - // Mock vault asset and epoch functions - // handled_epoch_len must be at least 1 to avoid underflow (even if no epochs are handled) - start_mock_call(dummy_vault, selector!("asset"), from_asset); - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 1_u256); // At least 1 to avoid underflow + let (vault, from_asset, to_asset, _, avnu_exchange, router) = set_up(); // Mint from_asset tokens to router let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; @@ -525,14 +596,11 @@ fn test_swap_uses_actual_received_amount() { #[test] fn test_claim_single_subscribe_single_swap_single_claim() { - let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + let (vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); // 1. Subscribe - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -543,7 +611,7 @@ fn test_claim_single_subscribe_single_swap_single_claim() { let new_nft_id = router.subscribe(old_nft_id, USER1()); // 2. Fulfill old NFT (burn it) - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + fulfill_old_nft(vault, from_asset, old_nft_id); // 3. Transfer assets to router (simulate vault fulfilling redemption) let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; @@ -556,10 +624,6 @@ fn test_claim_single_subscribe_single_swap_single_claim() { to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 300); // 5. Swap - // handled_epoch_len should be 2 if epoch 1 is handled (epochs are 0-indexed, len is count) - start_mock_call(dummy_vault, selector!("asset"), from_asset); - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); // Epochs 0 and 1 handled - start_mock_call(dummy_vault, selector!("epoch"), 1_u256); let routes: Array = array![]; let from_amount: u256 = WAD * 100; let min_amount_out: u256 = WAD * 200; // 2:1 ratio @@ -588,66 +652,54 @@ fn test_claim_single_subscribe_single_swap_single_claim() { #[test] fn test_claim_two_subscribes_one_swap_two_claims() { - let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + let (vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); // 1. Two subscribes - let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); - let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + let due_amount_1: u256 = WAD * 100; + let old_nft_id_1 = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount_1); + let due_amount_2: u256 = WAD * 200; + let old_nft_id_2 = mint_and_redeem_old_nft_to_user(vault, USER2(), due_amount_2); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, }; - // User 1 subscribes - let due_amount_1: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); - cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_1); cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); let new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); - // User 2 subscribes - let due_amount_2: u256 = WAD * 200; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); // Total for both users - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); - cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_2); cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); // 2. Fulfill both old NFTs - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_1); - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_2); - - // 3. Transfer assets to router - let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; - cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); - from_asset_dispatcher.transfer(router.contract_address, WAD * 300); // 300 tokens total + fulfill_old_nft(vault, from_asset, old_nft_id_1); + fulfill_old_nft(vault, from_asset, old_nft_id_2); + println!("fulfilled 1"); - // 4. Mint to_asset to mock exchange + // 3. Mint to_asset to mock exchange let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 1000); + println!("minted to_asset"); - // 5. One swap: 300 from → 600 to (2:1 ratio) - // handled_epoch_len should be 2 if epoch 1 is handled (epochs are 0-indexed, len is count) - start_mock_call(dummy_vault, selector!("asset"), from_asset); - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); // Epochs 0 and 1 handled - start_mock_call(dummy_vault, selector!("epoch"), 1_u256); + // 4. One swap: 300 from → 600 to (2:1 ratio) let routes: Array = array![]; + let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; + let balance_from = from_asset_dispatcher.balance_of(router.contract_address); + println!("balance_from: {}", balance_from); cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); - router.swap(routes, WAD * 300, WAD * 600); + router.swap(routes, balance_from, WAD * 600); + println!("swapped"); // 6. Claim User 1: due = 100, should get 100 * 600 / 300 = 200 // (no need to mock due_assets_from_id - it's stored in RequestInfo) cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); let receivable_1 = router.claim(new_nft_id_1); assert(receivable_1 == WAD * 200, 'User 1 receivable incorrect'); + println!("claimed 1"); // Verify swap info updated correctly let (from_rem, to_rem) = router.swap_info(1); @@ -655,7 +707,7 @@ fn test_claim_two_subscribes_one_swap_two_claims() { println!("to_rem: {}", to_rem); assert(from_rem == WAD * 200, 'Remaining from_amount incorrect'); assert(to_rem == WAD * 400, 'Remaining to_amount incorrect'); - + println!("claimed 2"); // 7. Claim User 2: due = 200, should get 200 * 600 / 300 = 400 // But since pool has remaining: 200 from, 400 to, user gets 400 // (no need to mock due_assets_from_id - it's stored in RequestInfo) @@ -671,14 +723,11 @@ fn test_claim_two_subscribes_one_swap_two_claims() { #[test] #[should_panic(expected: "Claim not allowed")] fn test_claim_requires_epoch_settled() { - let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + let (vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); // Subscribe to epoch 5 - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 5, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -689,7 +738,7 @@ fn test_claim_requires_epoch_settled() { let new_nft_id = router.subscribe(old_nft_id, USER1()); // Fulfill old NFT - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + fulfill_old_nft(vault, from_asset, old_nft_id); // Transfer assets and swap (but not enough to settle epoch 5) let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; @@ -700,11 +749,6 @@ fn test_claim_requires_epoch_settled() { cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 1000); - // handled_epoch_len should be 6 if epoch 5 is handled (epochs 0-5 are handled, len = 6) - start_mock_call(dummy_vault, selector!("asset"), from_asset); - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 6_u256); // Epochs 0-5 handled - start_mock_call(dummy_vault, selector!("epoch"), 5_u256); - let routes: Array = array![]; cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); router.swap(routes, WAD * 50, WAD * 100); // Swap 50, epoch 5 needs 100, so not fully settled @@ -720,14 +764,11 @@ fn test_claim_requires_epoch_settled() { #[test] fn test_unsubscribe_original_nft_not_fulfilled_returns_nft() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); // 1. Subscribe - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -759,14 +800,11 @@ fn test_unsubscribe_original_nft_not_fulfilled_returns_nft() { #[test] fn test_unsubscribe_original_nft_fulfilled_but_not_swapped_returns_assets() { - let (dummy_vault, from_asset, _, redeem_request, _, router) = set_up(); + let (vault, from_asset, _, redeem_request, _, router) = set_up(); // 1. Subscribe - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -777,29 +815,28 @@ fn test_unsubscribe_original_nft_fulfilled_but_not_swapped_returns_assets() { let new_nft_id = router.subscribe(old_nft_id, USER1()); // 2. Fulfill old NFT (burn it) - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + fulfill_old_nft(vault, from_asset, old_nft_id); // Mark old NFT as fulfilled in router's storage mark_old_nft_fulfilled(router.contract_address, old_nft_id); - - // 3. Transfer assets to router (simulate vault fulfilling redemption) + println!("old_nft_id: {}", old_nft_id); let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; - cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); - from_asset_dispatcher.transfer(router.contract_address, WAD * 100); // Get initial user balance let user_balance_before = from_asset_dispatcher.balance_of(USER1()); // 4. Unsubscribe (original NFT fulfilled but not swapped) // Use unsubscribe_for_underlying since old NFT is fulfilled - // Mock vault functions needed by unsubscribe_for_underlying - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); - start_mock_call(dummy_vault, selector!("asset"), from_asset); // Caller must own the NFT (USER1 already owns it) cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); router.unsubscribe_for_underlying(new_nft_id, USER1()); + println!("new_nft_id: {}", new_nft_id); // Verify user received from_assets let user_balance_after = from_asset_dispatcher.balance_of(USER1()); + println!("user_balance_before: {}", user_balance_before); + println!("user_balance_after: {}", user_balance_after); + let bal_remaining = ERC20ABIDispatcher { contract_address: from_asset }.balance_of(router.contract_address); + println!("bal_remaining: {}", bal_remaining); assert(user_balance_after == user_balance_before + WAD * 100, 'User should receive from_assets'); // Verify new NFT is burned and marked as unsubscribed @@ -810,14 +847,11 @@ fn test_unsubscribe_original_nft_fulfilled_but_not_swapped_returns_assets() { #[test] #[should_panic(expected: "Cannot unsubscribe: swaps have partially consumed assets")] fn test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts() { - let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + let (vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); // 1. Subscribe - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), 100); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -828,7 +862,7 @@ fn test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts() { let new_nft_id = router.subscribe(old_nft_id, USER1()); // 2. Fulfill old NFT - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); + fulfill_old_nft(vault, from_asset, old_nft_id); // Mark old NFT as fulfilled in router's storage mark_old_nft_fulfilled(router.contract_address, old_nft_id); @@ -838,10 +872,6 @@ fn test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts() { from_asset_dispatcher.transfer(router.contract_address, WAD * 100); // 4. Partial swap (swap 50 out of 100) - // Mock handled_epoch_len as 2 so epoch 1 can be processed (epochs are 0-indexed) - start_mock_call(dummy_vault, selector!("asset"), from_asset); - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); - start_mock_call(dummy_vault, selector!("epoch"), 1_u256); let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 200); @@ -852,9 +882,6 @@ fn test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts() { // 5. Attempt unsubscribe - should revert because swaps have partially consumed // Use unsubscribe_for_underlying since old NFT is fulfilled - // Mock vault functions needed by unsubscribe_for_underlying - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); - start_mock_call(dummy_vault, selector!("asset"), from_asset); // Caller must own the NFT (USER1 already owns it) cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); router.unsubscribe_for_underlying(new_nft_id, USER1()); @@ -862,33 +889,23 @@ fn test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts() { #[test] fn test_unsubscribe_second_user_before_swaps() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); // 1. Two users subscribe - let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); - let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + let due_amount_1: u256 = WAD * 100; + let old_nft_id_1 = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount_1); + let due_amount_2: u256 = WAD * 200; + let old_nft_id_2 = mint_and_redeem_old_nft_to_user(vault, USER2(), due_amount_2); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, }; - // User 1 subscribes - let due_amount_1: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); - cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_1); cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); let new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); - // User 2 subscribes - let due_amount_2: u256 = WAD * 200; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); - cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_2); cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); @@ -916,46 +933,31 @@ fn test_unsubscribe_second_user_before_swaps() { #[test] fn test_unsubscribe_third_user_after_second_withdrawn() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); // 1. Three users subscribe - let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); - let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); - let old_nft_id_3 = mint_old_nft_to_user(redeem_request, dummy_vault, 'USER3'.try_into().unwrap(), 1, 300); + let due_amount_1: u256 = WAD * 100; + let old_nft_id_1 = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount_1); + let due_amount_2: u256 = WAD * 200; + let old_nft_id_2 = mint_and_redeem_old_nft_to_user(vault, USER2(), due_amount_2); + let due_amount_3: u256 = WAD * 300; + let old_nft_id_3 = mint_and_redeem_old_nft_to_user(vault, 'USER3'.try_into().unwrap(), due_amount_3); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, }; let user3: ContractAddress = 'USER3'.try_into().unwrap(); - // User 1 subscribes - let due_amount_1: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); - cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_1); cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); let _new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); - // User 2 subscribes - let due_amount_2: u256 = WAD * 200; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); - cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_2); cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); - // User 3 subscribes - let due_amount_3: u256 = WAD * 300; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_3); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 600); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 600); - cheat_caller_address(redeem_request.contract_address, user3, span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_3); cheat_caller_address(router.contract_address, user3, span: CheatSpan::TargetCalls(1)); @@ -985,41 +987,33 @@ fn test_unsubscribe_third_user_after_second_withdrawn() { #[test] fn test_unsubscribe_second_user_after_fulfillment_but_before_swaps() { - let (dummy_vault, from_asset, _, redeem_request, _, router) = set_up(); + let (vault, from_asset, _, redeem_request, _, router) = set_up(); // 1. Two users subscribe - let old_nft_id_1 = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); - let old_nft_id_2 = mint_old_nft_to_user(redeem_request, dummy_vault, USER2(), 1, 200); + let due_amount_1: u256 = WAD * 100; + let old_nft_id_1 = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount_1); + + let due_amount_2: u256 = WAD * 200; + let old_nft_id_2 = mint_and_redeem_old_nft_to_user(vault, USER2(), due_amount_2); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, }; // User 1 subscribes - let due_amount_1: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_1); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); - cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_1); cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); let new_nft_id_1 = router.subscribe(old_nft_id_1, USER1()); - // User 2 subscribes - let due_amount_2: u256 = WAD * 200; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount_2); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 300); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 300); - cheat_caller_address(redeem_request.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); erc721_dispatcher.approve(router.contract_address, old_nft_id_2); cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); let new_nft_id_2 = router.subscribe(old_nft_id_2, USER2()); // 2. Fulfill both old NFTs - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_1); - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id_2); + fulfill_old_nft(vault, from_asset, old_nft_id_1); + fulfill_old_nft(vault, from_asset, old_nft_id_2); // Mark old NFTs as fulfilled in router's storage mark_old_nft_fulfilled(router.contract_address, old_nft_id_1); mark_old_nft_fulfilled(router.contract_address, old_nft_id_2); @@ -1034,9 +1028,6 @@ fn test_unsubscribe_second_user_after_fulfillment_but_before_swaps() { // 4. User 2 unsubscribes (original NFT fulfilled but not swapped) // Use unsubscribe_for_underlying since old NFT is fulfilled - // Mock vault functions needed by unsubscribe_for_underlying - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); - start_mock_call(dummy_vault, selector!("asset"), from_asset); // Caller must own the NFT (USER2 already owns it) cheat_caller_address(router.contract_address, USER2(), span: CheatSpan::TargetCalls(1)); router.unsubscribe_for_underlying(new_nft_id_2, USER2()); @@ -1053,14 +1044,11 @@ fn test_unsubscribe_second_user_after_fulfillment_but_before_swaps() { #[test] #[should_panic(expected: "NFT already withdrawn")] fn test_unsubscribe_twice_reverts() { - let (dummy_vault, _, _, redeem_request, _, router) = set_up(); + let (vault, _, _, redeem_request, _, router) = set_up(); // Subscribe - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -1116,14 +1104,11 @@ fn test_set_min_subscribe_amount_reverts_when_not_owner() { #[test] fn test_sync_settled_epochs() { - let (dummy_vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); + let (vault, from_asset, to_asset, redeem_request, avnu_exchange, router) = set_up(); // Subscribe to epoch 1 - let old_nft_id = mint_old_nft_to_user(redeem_request, dummy_vault, USER1(), 1, 100); let due_amount: u256 = WAD * 100; - start_mock_call(dummy_vault, selector!("due_assets_from_id"), due_amount); - start_mock_call(dummy_vault, selector!("redeem_assets"), WAD * 100); - start_mock_call(dummy_vault, selector!("redeem_nominal"), WAD * 100); + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); let erc721_dispatcher = ERC721ABIDispatcher { contract_address: redeem_request.contract_address, @@ -1133,23 +1118,16 @@ fn test_sync_settled_epochs() { cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); let _new_nft_id = router.subscribe(old_nft_id, USER1()); + // report once to skip epoch 0 + report(vault, from_asset); + // Fulfill old NFT - fulfill_old_nft(redeem_request, dummy_vault, old_nft_id); - - // Transfer assets and swap enough to settle epoch 1 - let from_asset_dispatcher = ERC20ABIDispatcher { contract_address: from_asset }; - cheat_caller_address(from_asset, OWNER(), span: CheatSpan::TargetCalls(1)); - from_asset_dispatcher.transfer(router.contract_address, WAD * 100); + fulfill_old_nft(vault, from_asset, old_nft_id); let to_asset_dispatcher = ERC20ABIDispatcher { contract_address: to_asset }; cheat_caller_address(to_asset, OWNER(), span: CheatSpan::TargetCalls(1)); to_asset_dispatcher.transfer(avnu_exchange.contract_address, WAD * 1000); - // handled_epoch_len should be 2 if epoch 1 is handled (epochs are 0-indexed, len is count) - start_mock_call(dummy_vault, selector!("asset"), from_asset); - start_mock_call(dummy_vault, selector!("handled_epoch_len"), 2_u256); // Epochs 0 and 1 handled - start_mock_call(dummy_vault, selector!("epoch"), 1_u256); - let routes: Array = array![]; cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); router.swap(routes, WAD * 100, WAD * 200); @@ -1159,6 +1137,7 @@ fn test_sync_settled_epochs() { router.sync_settled_epochs(10); // Check up to 10 epochs // Verify last_settled_epoch updated + println!("last_settled_epoch: {}", router.last_settled_epoch()); assert(router.last_settled_epoch() == 1, 'last_settled_epoch should be 1'); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..cfc1cc3a --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,13 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@strkfarm/sdk': + specifier: link:../sdk-ts + version: link:../sdk-ts From 623b21d78aa015029750ce518fa12118f9ae8d74 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Mon, 5 Jan 2026 13:54:11 +0530 Subject: [PATCH 03/14] rm nohup --- nohup.log | 341 --------- .../vault/src/redemption_router/TEST_PLAN.md | 671 ------------------ 2 files changed, 1012 deletions(-) delete mode 100644 nohup.log delete mode 100644 packages/vault/src/redemption_router/TEST_PLAN.md diff --git a/nohup.log b/nohup.log deleted file mode 100644 index ec8f2296..00000000 --- a/nohup.log +++ /dev/null @@ -1,341 +0,0 @@ - Compiling snforge_scarb_plugin v0.48.0 -warning: hiding a lifetime that's elided elsewhere is confusing - --> src/args.rs:79:43 - | -79 | pub fn unnamed_only(&self) -> Result { - | ^^^^^ ----------- the same lifetime is hidden here - | | - | the lifetime is elided here - | - = help: the same lifetime is referred to in inconsistent ways, making the signature confusing - = note: `#[warn(mismatched_lifetime_syntaxes)]` on by default -help: use `'_` for type paths - | -79 | pub fn unnamed_only(&self) -> Result, Diagnostic> { - | ++++ -warning: hiding a lifetime that's elided elsewhere is confusing - --> src/args.rs:88:20 - | -88 | pub fn unnamed(&self) -> UnnamedArgs { - | ^^^^^ ----------- the same lifetime is hidden here - | | - | the lifetime is elided here - | - = help: the same lifetime is referred to in inconsistent ways, making the signature confusing -help: use `'_` for type paths - | -88 | pub fn unnamed(&self) -> UnnamedArgs<'_> { - | ++++ -warning: `snforge_scarb_plugin` (lib) generated 2 warnings - Finished `release` profile [optimized] target(s) in 1.41s - Compiling test(starknet_vault_kit_unittest) starknet_vault_kit v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/Scarb.toml) - Compiling test(vault_unittest) vault v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/packages/vault/Scarb.toml) -warn: Unused import: `vault::test::units::redemption_router::Zero` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault/src/test/units/redemption_router.cairo:11:24 -use core::num::traits::Zero; - ^^^^ - -warn[E0001]: Unused variable. Consider ignoring by prefixing with `_`. - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault/src/test/units/redemption_router.cairo:854:9 - let due_amount: u256 = WAD * 100; - ^^^^^^^^^^ -note: this error originates in the attribute macro: `test` -note: this error originates in the attribute macro: `should_panic` -note: this error originates in the attribute macro: `__internal_config_statement` - - Compiling test(vault_allocator_unittest) vault_allocator v0.1.0 (/Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/Scarb.toml) -warn: Unused import: `vault_allocator::middlewares::avnu_middleware::avnu_middleware::AvnuMiddleware::Map` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo:15:9 - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - ^^^ - -warn: Unused import: `vault_allocator::middlewares::avnu_middleware::avnu_middleware::AvnuMiddleware::StorageMapReadAccess` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo:15:14 - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - ^^^^^^^^^^^^^^^^^^^^ - -warn: Unused import: `vault_allocator::middlewares::avnu_middleware::avnu_middleware::AvnuMiddleware::StorageMapWriteAccess` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo:15:36 - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - ^^^^^^^^^^^^^^^^^^^^^ - -warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::get_caller_address` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:5:33 -use starknet::{ContractAddress, get_caller_address, get_contract_address}; - ^^^^^^^^^^^^^^^^^^ - -warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::get_contract_address` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:5:53 -use starknet::{ContractAddress, get_caller_address, get_contract_address}; - ^^^^^^^^^^^^^^^^^^^^ - -warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::ERC20ABIDispatcher` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:6:39 -use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - ^^^^^^^^^^^^^^^^^^ - -warn: Unused import: `vault_allocator::mocks::mock_avnu_exchange::ERC20ABIDispatcherTrait` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/mocks/mock_avnu_exchange.cairo:6:59 -use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - ^^^^^^^^^^^^^^^^^^^^^^^ - -warn: Unused import: `vault_allocator::test::integrations::vault_bring_liquidity::IERC4626Dispatcher` - --> /Users/vt/STRKFarm/starknet_vault_kit/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo:6:40 -use openzeppelin::interfaces::erc4626::IERC4626Dispatcher; - ^^^^^^^^^^^^^^^^^^ - - Finished `dev` profile target(s) in 19 seconds - - -Collected 0 test(s) from starknet_vault_kit package -Running 0 test(s) from src/ -Tests: 0 passed, 0 failed, 0 ignored, 0 filtered out - - -Collected 27 test(s) from vault package -Running 27 test(s) from src/ -deploying redemption router -deploying redemption router -deploying redemption router with calldata -deploying redemption router with calldata -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -deploying redemption router with calldata -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -deploying redemption router with calldata -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -deploying redemption router with calldata -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_vault (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) -[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_redeem_request (l1_gas: ~0, l1_data_gas: ~864, l2_gas: ~1351680) -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -deploying redemption router with calldata -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -RELAYER_ROLE granted -RELAYER_ROLE granted -deploying redemption router with calldata -deploying redemption router with calldata -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -RELAYER_ROLE granted -deploying redemption router with calldata -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -RELAYER_ROLE granted -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -deploying redemption router with calldata -deploying redemption router with calldata -deploying redemption router -RELAYER_ROLE granted -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -[PASS] vault::test::units::redemption_router::test_sync_settled_epochs_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~16323200) -deploying redemption router with calldata -[PASS] vault::test::units::redemption_router::test_unsubscribe_twice_reverts (l1_gas: ~0, l1_data_gas: ~9120, l2_gas: ~29808960) -[PASS] vault::test::units::redemption_router::test_set_min_subscribe_amount_only_owner (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~15947840) -[PASS] vault::test::units::redemption_router::test_constructor_reverts_zero_to_asset (l1_gas: ~0, l1_data_gas: ~96, l2_gas: ~520000) -[FAIL] vault::test::units::redemption_router::test_unsubscribe_original_nft_fulfilled_but_not_swapped_returns_assets - -Failure data: - 0x557365722073686f756c6420726563656976652066726f6d5f617373657473 ('User should receive from_assets') - -[PASS] vault::test::units::redemption_router::test_unsubscribe_second_user_before_swaps (l1_gas: ~0, l1_data_gas: ~9792, l2_gas: ~41260160) -[PASS] vault::test::units::redemption_router::test_claim_requires_epoch_settled (l1_gas: ~0, l1_data_gas: ~9600, l2_gas: ~40604160) -[PASS] vault::test::units::redemption_router::test_unsubscribe_original_nft_not_fulfilled_returns_nft (l1_gas: ~0, l1_data_gas: ~9120, l2_gas: ~30208960) -[FAIL] vault::test::units::redemption_router::test_claim_two_subscribes_one_swap_two_claims - -Failure data: - "Claim not allowed" - - - "Claim not allowed" - -note: run with `SNFORGE_BACKTRACE=1` environment variable to display a backtrace -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -deploying redemption router with calldata -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -deploying redemption router with calldata -RELAYER_ROLE granted -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -[PASS] vault::test::units::redemption_router::test_unsubscribe_third_user_after_second_withdrawn (l1_gas: ~0, l1_data_gas: ~10368, l2_gas: ~54841280) -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -RELAYER_ROLE granted -deploying redemption router with calldata -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -RELAYER_ROLE granted -deploying redemption router -deploying redemption router with calldata -deploying redemption router with calldata -[PASS] vault::test::units::redemption_router::test_swap_executes_successfully (l1_gas: ~0, l1_data_gas: ~8064, l2_gas: ~21170880) -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -deploying redemption router with calldata -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -deploying redemption router with calldata -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -deploying redemption router with calldata -RELAYER_ROLE granted -RELAYER_ROLE granted -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -[PASS] vault::test::units::redemption_router::test_swap_reverts_when_not_relayer (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~16588800) -RELAYER_ROLE granted -deploying redemption router with calldata -RELAYER_ROLE granted -deploying redemption router with calldata -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -[PASS] vault::test::units::redemption_router::test_subscribe_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~8640, l2_gas: ~23504960) -RELAYER_ROLE granted -RELAYER_ROLE granted -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -[PASS] vault::test::units::redemption_router::test_swap_uses_actual_received_amount (l1_gas: ~0, l1_data_gas: ~8064, l2_gas: ~21130880) -[FAIL] vault::test::units::redemption_router::test_subscribe_transfers_old_nft_and_mints_new - -Failure data: - 0x44756520616d6f756e742073746f72656420696e636f72726563746c79 ('Due amount stored incorrectly') - -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -deploying redemption router with calldata -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -deploying redemption router with calldata -[PASS] vault::test::units::redemption_router::test_subscribe_reverts_on_too_small_amount (l1_gas: ~0, l1_data_gas: ~8448, l2_gas: ~23368640) -[FAIL] vault::test::units::redemption_router::test_claim_single_subscribe_single_swap_single_claim - -Failure data: - "Claim not allowed" - - - "Claim not allowed" - -note: run with `SNFORGE_BACKTRACE=1` environment variable to display a backtrace -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -[FAIL] vault::test::units::redemption_router::test_unsubscribe_original_nft_fulfilled_partially_swapped_reverts - -Failure data: - Expected to panic, but no panic occurred - Expected panic data: [0x46a6158a16a947e5916b2a2ca68501a45e93d7110e81aa2d6438b1c57c879a3, 0x1, 0x43616e6e6f7420756e7375627363726962653a207377617073206861766520, 0x7061727469616c6c7920636f6e73756d656420617373657473, 0x19] (, , Cannot unsubscribe: swaps have , partially consumed assets, ) - -RELAYER_ROLE granted -setup done -[PASS] vault::test::units::redemption_router::test_swap_reverts_when_paused (l1_gas: ~0, l1_data_gas: ~7584, l2_gas: ~16443200) -addresses stored correctly -roles set -swap_id and unsettled_swap_id start at 1 -NFT counter starts at 0 -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -deploying redemption router with calldata -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -[PASS] vault::test::units::redemption_router::test_subscribe_increments_nft_counter (l1_gas: ~0, l1_data_gas: ~9792, l2_gas: ~37530240) -deploying redemption router -RELAYER_ROLE granted -avnu_address: 1628116951761910920870155251384136531213406409703273313579474904754020109217 -avnu_exchange deployed -integrator_fee_recipient and integrator_fee_amount_bps set -deploying redemption router -[FAIL] vault::test::units::redemption_router::test_unsubscribe_second_user_after_fulfillment_but_before_swaps - -Failure data: - 0x55736572203220726563656976656420323030 ('User 2 received 200') - -[PASS] vault::test::units::redemption_router::test_constructor_initializes_correctly (l1_gas: ~0, l1_data_gas: ~7488, l2_gas: ~17427840) -deploying redemption router with calldata -deploying redemption router with calldata -[PASS] vault::test::units::redemption_router::test_set_min_subscribe_amount_reverts_when_not_owner (l1_gas: ~0, l1_data_gas: ~7488, l2_gas: ~15787840) -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -router: 1621714980566236991272171700418711100308322164303330559391498409129115170949 -RELAYER_ROLE granted -[PASS] vault::test::units::redemption_router::test_swap_reverts_on_insufficient_balance (l1_gas: ~0, l1_data_gas: ~7488, l2_gas: ~15867840) -[FAIL] vault::test::units::redemption_router::test_sync_settled_epochs - -Failure data: - 0x6c6173745f736574746c65645f65706f63682073686f756c642062652031 ('last_settled_epoch should be 1') - -Tests: 20 passed, 7 failed, 0 ignored, 87 filtered out -[ERROR] data did not match any variant of untagged enum JsonRpcResponse diff --git a/packages/vault/src/redemption_router/TEST_PLAN.md b/packages/vault/src/redemption_router/TEST_PLAN.md deleted file mode 100644 index 80efce61..00000000 --- a/packages/vault/src/redemption_router/TEST_PLAN.md +++ /dev/null @@ -1,671 +0,0 @@ -# Redemption Router Test Plan - -## Overview -This document outlines comprehensive unit tests for the `RedemptionRouter` contract. Tests will use: -- **Mock Call Cheatcodes**: Use `mock_call` and `start_mock_call` from snforge to mock vault functions -- **RedeemRequest Contract**: Deploy actual `RedeemRequest` contract with dummy vault address -- **Avnu Exchange**: Deploy mock contract from `vault_allocator/src/mocks/mock_avnu_exchange.cairo` -- **ERC20 Tokens**: Use `Erc20Mock` from `vault_allocator/src/mocks/erc20.cairo` for from_asset and to_asset - -## Test Setup & Mocking Strategy - -### 1. Vault Contract Mocking (using `mock_call`) -- **No mock contract needed** - use `mock_call` cheatcode to mock vault functions -- Mock `due_assets_from_id(id: u256) -> u256`: - ```cairo - let due_amount: u256 = 100; // example - mock_call(VAULT_ADDRESS, selector!("due_assets_from_id"), due_amount, 1); - ``` -- Mock `asset() -> ContractAddress`: - ```cairo - mock_call(VAULT_ADDRESS, selector!("asset"), from_asset_address, 1); - ``` -- **Important**: If old NFT is not burnt/claimed, `due_assets_from_id` returns non-zero. If burnt, returns 0. - -### 2. RedeemRequest Contract -- **Deploy actual contract** with dummy vault address -- To mint NFT to user (simulate subscription): - - Cheat caller as vault: `cheat_caller_address(redeem_request_address, VAULT_ADDRESS)` - - Call `mint(user_address, redeem_request_info)` on redeem_request - - User approves router: `approve(router_address, nft_id)` - - User calls `subscribe(nft_id, receiver)` on router -- To burn NFT (simulate claim_redeem completion): - - Cheat caller as vault: `cheat_caller_address(redeem_request_address, VAULT_ADDRESS)` - - Call `burn(nft_id)` on redeem_request - - After burn, `due_assets_from_id` should return 0 (mocked) - -### 3. Avnu Exchange Mock -- **Deploy mock contract** from `vault_allocator/src/mocks/mock_avnu_exchange.cairo` -- The mock automatically transfers `sell_token_amount` from caller and `buy_token_min_amount` to beneficiary -- Returns `true` on success - -### 4. ERC20 Tokens -- Deploy `Erc20Mock` from `vault_allocator/src/mocks/erc20.cairo` for: - - `from_asset`: Asset token that vault uses - - `to_asset`: Target token for swaps -- Mint tokens to router contract for swap operations - -### 5. Test Flow Summary - -**Setup:** -1. Deploy from_asset and to_asset ERC20 mocks -2. Deploy Avnu exchange mock -3. Deploy redeem_request with dummy vault address -4. Deploy redemption_router with all addresses -5. Set up roles (grant RELAYER_ROLE) - -**Simulating User Subscription:** -1. Cheat caller as vault → mint NFT to user via redeem_request -2. User approves router for NFT -3. User calls `subscribe(nft_id, receiver)` on router -4. Router transfers old NFT and mints new NFT - -**Simulating Swap (after subscription):** -1. First, fulfill the old NFT (simulate claim_redeem): - - Transfer vault assets to router (from_asset tokens) - - Cheat caller as vault → burn old NFT on redeem_request - - Mock `due_assets_from_id` to return 0 for this NFT (since it's burnt) -2. Router has from_asset balance -3. Relayer calls `swap()` with routes and amounts -4. Mock Avnu transfers tokens appropriately - -**Simulating Claim:** -1. Ensure old NFT is fulfilled (burnt on redeem_request) -2. Mock `due_assets_from_id` for the old NFT with the amount user was owed (even though NFT is burnt, this represents historical due amount) -3. User calls `claim(new_nft_id)` on router -4. Router calls `vault.due_assets_from_id(old_nft_id)` - this should return the mocked amount -5. Router iterates pools and transfers proportional to_asset to user - -**Important**: The router's `claim` function calls `vault.due_assets_from_id(old_nft_id)` to determine what the user was owed. Even if the old NFT is burnt, we need to mock this to return the historical due amount so the router can calculate proportional swap amounts correctly. - ---- - -## Test Categories - -### 1. Constructor & Initialization Tests - -#### `test_constructor_initializes_correctly` -- Verify all addresses are stored correctly -- Verify roles are set (OWNER_ROLE, RELAYER_ROLE) -- Verify `swap_id` and `unsettled_swap_id` start at 1 -- Verify `nft_id_counter` starts at 0 -- Verify `min_subscribe_amount` is set correctly -- Verify `last_settled_epoch` is initialized from vault's `handled_epoch_len` -- Verify NFT contract initialized with name "RedemptionRouter", symbol "RR" - -#### `test_constructor_reverts_on_zero_addresses` -- Test each address parameter (vault, redeem_request, to_asset, avnu_exchange, integrator_fee_recipient) -- Verify appropriate error is raised - ---- - -### 2. Subscribe Function Tests - -#### `test_subscribe_transfers_old_nft_and_mints_new` -- User subscribes with old NFT ID -- Verify old NFT transferred from user to router -- Verify new NFT minted to receiver -- Verify `new_nft_request_map` stores correct `old_nft_id` and `is_claimed = false` -- Verify `Subscribed` event emitted - -#### `test_subscribe_increments_nft_counter` -- Multiple subscriptions -- Verify NFT IDs are sequential (0, 1, 2, ...) - -#### `test_subscribe_reverts_when_paused` -- Pause contract -- Attempt subscribe -- Verify revert - -#### `test_subscribe_reverts_on_too_small_amount` -- Set `min_subscribe_amount` to 100 -- Attempt subscribe with NFT that has `due_amount` < 100 -- Verify `too_small_subscribe_amount` error - -#### `test_subscribe_snapshots_epoch_data` -- Subscribe with NFT from epoch 5 -- Verify `epoch_redeem_assets` and `epoch_redeem_nominal` are stored for epoch 5 -- Verify `epoch_offset_factor` defaults to WAD if not set -- Verify `epoch_wise_nominals` accumulates nominal amount - ---- - -### 3. Swap Function Tests - -#### `test_swap_executes_successfully` -- Router has balance of from_asset -- Execute swap -- Verify Avnu `multi_route_swap` called with correct parameters -- Verify `swap_info` stores `(from_amount, to_amount)` correctly -- Verify `swap_id` increments -- Verify `Swapped` event emitted with `swap_id`, `from_amount`, `to_amount`, and `last_settled_epoch` -- Verify epochs are settled correctly based on `from_amount` - -#### `test_swap_reverts_on_insufficient_balance` -- Router has insufficient from_asset balance -- Attempt swap -- Verify `insufficient_from_amount` error - -#### `test_swap_reverts_when_not_relayer` -- Non-relayer attempts swap -- Verify access control revert - -#### `test_swap_reverts_when_paused` -- Pause contract -- Attempt swap -- Verify revert - -#### `test_swap_reverts_on_avnu_failure` -- Mock Avnu returns `false` -- Verify `swap_failed` error - -#### `test_swap_uses_actual_received_amount` -- Mock Avnu returns different amount than `min_amount_out` -- Verify `swap_info` stores actual received amount (balance_delta) - ---- - -### 4. Basic Claim Scenarios - -#### `test_claim_single_subscribe_single_swap_single_claim` -- 1 subscribe → 1 swap → 1 claim -- Verify user receives correct proportional amount: `due_assets * to_amount / from_amount` -- Verify NFT burned -- Verify `is_claimed = true` -- Verify `Claimed` event emitted with `new_nft_id`, `old_nft_id`, `receivable`, and `swap_id` -- Verify pool fully consumed, `unsettled_swap_id` advances - -#### `test_claim_two_subscribes_one_swap_two_claims` -- 2 subscribes → 1 swap → 2 claims -- User 1 due: 100, User 2 due: 200, Swap: 300 from → 600 to -- Verify User 1 gets: 100 * 600 / 300 = 200 -- Assert remaining swap info is updated correctly, correct all swap IDs -- Verify User 2 gets: 200 * 600 / 300 = 400 -- Verify both claims succeed (both epochs must be settled) -- Verify pool fully consumed after both claims - -#### `test_claim_requires_epoch_settled` -- User subscribes to epoch 5 -- Execute swap but epoch 5 not fully settled yet -- Attempt to claim -- Verify `claim_not_allowed` error -- Complete epoch settlement -- Now claim should succeed - ---- - -### 5. Multi-Pool Claim Scenarios - -#### `test_claim_spans_multiple_pools_single_user` -- User subscribes with due: 500 -- Swap 1: 200 from → 400 to -- Swap 2: 300 from → 600 to -- Claim should drain: - - From pool 1: 200 from → 400 to - - From pool 2: 300 from → 600 to -- Verify total received: 1000 -- Verify both pools consumed -- Verify `unsettled_swap_id` advances to 3 - -#### `test_claim_partial_pool_consumption` -- User 1 subscribes with due: 100 -- User 2 subscribes with due: 200 -- Swap 1: 300 from → 600 to -- Claim User 1: Takes 100 from → 200 to, pool remains (200 from, 400 to) -- Claim User 2: Takes remaining 200 from → 400 to, pool emptied -- Verify User 1 gets 200, User 2 gets 400 -- Verify pool state updated correctly after each claim - -#### `test_claim_multiple_swaps_before_claims` -- 4 subscribes (User 0-3) -- Swap 1: 100 from → 200 to -- Swap 2: 200 from → 400 to -- Swap 3: 300 from → 600 to -- All users have due: 150 each -- Claims in order: - - User 0: 100 from pool 1 (100 from → 200 to), 50 from pool 2 (50 from → 100 to) = 300 total - - User 1: 50 from pool 2 (50 from → 100 to), 100 from pool 3 (100 from → 200 to) = 300 total - - User 2: 200 from pool 3 (200 from → 400 to) = 400 total - - User 3: Claims from remaining pools -- Verify each user gets correct proportional amounts -- Verify pools consumed in order - ---- - -### 6. Fairness Scenarios (Different Due Amounts) - -#### `test_fairness_different_vault_due_amounts` -- User A subscribes: vault due = 100 -- User B subscribes: vault due = 99 (reporting loss) -- Swap: 199 from → 398 to (2:1 ratio) -- Claim User A: 100 * 398 / 199 = 200 -- Claim User B: 99 * 398 / 199 = 198 (rounded down) -- Verify User B gets exactly 99/100 of User A's amount -- Verify fairness: User B gets less because they received less from vault - -#### `test_fairness_across_multiple_pools` -- User A: due = 100 -- User B: due = 50 -- Swap 1: 50 from → 100 to (for User B) -- Swap 2: 100 from → 200 to (for User A) -- Claim User A: 100 from pool 2 → 200 -- Claim User B: 50 from pool 1 → 100 -- Verify User B gets exactly half of User A (proportional) - -#### `test_fairness_with_rounding_dust` -- User A: due = 100 -- User B: due = 100 -- Swap: 199 from → 399 to (rounding will leave dust) -- Claim User A: 100 * 399 / 199 = 200 (rounded) -- Claim User B: 99 * 399 / 199 = 198 (rounded) -- Verify remaining dust stays in pool or goes to last claimer -- Verify no value leakage - ---- - -### 7. Complex Ordering Scenarios - -#### `test_complex_ordering_4_subscribes_2_swaps_4_claims` -- Subscribe 4 NFTs (IDs 0-3) -- Swap 1: 200 from → 400 to (intended for NFTs 2-3, but available globally) -- Swap 2: 200 from → 400 to (intended for NFTs 0-1, but available globally) -- Claims in original sequence (0, 1, 2, 3): - - NFT 0: Claims from pool 1 (global head), drains 200 → 400 - - NFT 1: Claims from pool 2 (next), drains 200 → 400 - - NFT 2: Claims from pool 1 (if still available) or pool 2 - - NFT 3: Claims from remaining pools -- Verify all claims succeed sequentially -- Verify users get proportional amounts based on their vault due - -#### `test_swaps_between_subscribes` -- Subscribe NFT 0 -- Swap 1: 100 from → 200 to -- Subscribe NFT 1 -- Swap 2: 100 from → 200 to -- Subscribe NFT 2 -- Claim NFT 0: Gets from pool 1 -- Claim NFT 1: Gets from pool 2 (if pool 1 drained) or pool 1 (if available) -- Claim NFT 2: Gets from remaining pools -- Verify sequential claiming still works - -#### `test_claims_after_multiple_swaps` -- Subscribe 3 NFTs -- Swap 1: 100 from → 200 to -- Swap 2: 100 from → 200 to -- Swap 3: 100 from → 200 to -- Claim NFT 0: Drains pool 1 entirely or partially -- Claim NFT 1: Continues from where NFT 0 left off -- Claim NFT 2: Continues from remaining pools -- Verify global head advances correctly - ---- - -### 8. Edge Cases - -#### `test_claim_with_zero_due_assets` -- User subscribes but vault returns 0 due (mock `due_assets_from_id` to return 0) -- Attempt claim -- Verify `invalid_nft_id` error - -**Note**: This can happen if old NFT is already burnt but router hasn't processed it yet - -#### `test_claim_when_no_swaps_exist` -- Subscribe NFT -- Fulfill old NFT (burn it, mock due_assets = 0) -- Mock `due_assets_from_id` to return desired amount -- Attempt claim before any swaps -- Verify claim succeeds but returns 0 (no pools available, but old NFT is valid) - -#### `test_claim_insufficient_pools` -- User subscribes with due: 1000 -- Fulfill old NFT (burn it) -- Mock `due_assets_from_id` to return 1000 -- Swap: 100 from → 200 to -- Attempt claim -- Verify claim succeeds but only gets 200 (from available pool) -- Verify user can claim remaining 900 in future when more swaps occur - -#### `test_claim_empty_pools_skipped` -- Swap 1: 100 from → 200 to (fully consumed) -- Swap 2: 100 from → 200 to (fully consumed) -- Swap 3: 100 from → 200 to (available) -- User subscribes and claims -- Verify skips empty pools 1 and 2, uses pool 3 -- Verify `unsettled_swap_id` advances correctly - -#### `test_claim_invalid_nft_id` -- Attempt claim with non-existent NFT -- Verify `invalid_old_nft_id` error - -#### `test_claim_already_claimed_nft` -- Subscribe and claim NFT -- Attempt claim again -- Verify `nft_already_claimed` error (from `_validate_request_info`) - -#### `test_claim_reverts_on_insufficient_to_asset_balance` -- Subscribe and swap successfully -- Router has insufficient to_asset balance -- Attempt claim -- Verify `insufficient_balance_for_withdrawal` error - ---- - -### 9. Unsubscribe Function Tests - -#### `test_unsubscribe_original_nft_not_fulfilled` -- User subscribes -- Original NFT not fulfilled (router still owns it) -- Epoch not settled -- Call `unsubscribe(nft_id, receiver)` -- Verify original NFT transferred to receiver -- Verify new NFT burned -- Verify `Unsubscribed` event with `is_old_nft_returned = true` -- Verify `is_original_assets_returned = false` - -#### `test_unsubscribe_original_nft_fulfilled` -- User subscribes -- Original NFT fulfilled (burnt on redeem_request) -- Epoch not settled -- Router has from_asset balance -- Call `unsubscribe(nft_id, receiver)` -- Verify from_asset transferred to receiver (proportional to user's share) -- Verify new NFT burned -- Verify `Unsubscribed` event with `is_old_nft_returned = false` -- Verify `is_original_assets_returned = true` and `original_assets_returned` amount - -#### `test_unsubscribe_reverts_when_epoch_settled` -- User subscribes -- Epoch fully settled -- Attempt unsubscribe -- Verify `cannot_unsubscribe_partial_swap` error - -#### `test_unsubscribe_reverts_when_partial_settlement` -- User subscribes -- Original NFT fulfilled -- Epoch partially settled (some swaps occurred) -- Attempt unsubscribe -- Verify `cannot_unsubscribe_partial_swap` error - -#### `test_unsubscribe_reverts_when_not_owner` -- User subscribes -- Another user attempts to unsubscribe -- Verify `not_owner` error (router must own the NFT) - -#### `test_unsubscribe_reverts_on_insufficient_balance` -- User subscribes -- Original NFT fulfilled -- Router has insufficient from_asset balance -- Attempt unsubscribe -- Verify `insufficient_balance_for_withdrawal` error - -#### `test_unsubscribe_reverts_when_paused` -- Pause contract -- Attempt unsubscribe -- Verify revert - ---- - -### 10. Access Control & Authorization Tests - -#### `test_set_integrator_fee_recipient_only_owner` -- Non-owner attempts to set fee recipient -- Verify revert - -#### `test_set_integrator_fee_amount_bps_only_owner` -- Non-owner attempts to set fee BPS -- Verify revert - -#### `test_set_integrator_fee_recipient_zero_address` -- Owner attempts to set zero address -- Verify `zero_address` error - -#### `test_set_min_subscribe_amount_only_owner` -- Non-owner attempts to set min subscribe amount -- Verify revert - -#### `test_set_epoch_offset_only_relayer` -- Non-relayer attempts to set epoch offset -- Verify revert - -#### `test_swap_only_relayer` -- Non-relayer attempts swap -- Verify access control revert - -#### `test_report_only_relayer` -- Non-relayer attempts report -- Verify access control revert - ---- - -#### `test_subscribe_reverts_when_paused` -- Pause contract -- Attempt subscribe -- Verify revert - -#### `test_swap_reverts_when_paused` -- Pause contract -- Attempt swap -- Verify revert - -#### `test_claim_reverts_when_paused` -- Pause contract -- Attempt claim -- Verify revert - ---- - -### 11. Pausable Tests - -#### `test_subscribe_reverts_when_paused` -- Pause contract -- Attempt subscribe -- Verify revert - -#### `test_swap_reverts_when_paused` -- Pause contract -- Attempt swap -- Verify revert - -#### `test_claim_reverts_when_paused` -- Pause contract -- Attempt claim -- Verify revert - -#### `test_unsubscribe_reverts_when_paused` -- Pause contract -- Attempt unsubscribe -- Verify revert - ---- - -### 14. Integration Flow Tests - -#### `test_full_redemption_flow` -- User requests redemption from vault (mock) -- User subscribes to router with redemption NFT -- Relayer swaps assets -- User claims swapped assets -- Verify entire flow works end-to-end - -#### `test_batch_redemption_flow` -- Multiple users subscribe -- Multiple swaps executed -- All users claim in sequence -- Verify all receive correct proportional amounts - -#### `test_unsubscribe_flow` -- User subscribes -- User changes mind and unsubscribes before epoch settled -- Verify original NFT or assets returned correctly - -### 12. View Function Tests - -#### `test_view_functions_return_correct_values` -- Test all view functions: - - `vault()`, `redeem_request()`, `to_asset()`, `avnu_exchange()` - - `integrator_fee_recipient()`, `integrator_fee_amount_bps()` - - `swap_id()`, `unsettled_swap_id()` - - `new_nft_request_info()`, `swap_info()` - - `last_settled_epoch()`, `epoch_settled_amounts(epoch)` - - `expected_receivable(nft_id)`, `last_nft_id()` -- Verify all return expected values - ---- - -### 13. Epoch Settlement & Sync Tests - -#### `test_settle_epochs_from_amount` -- Multiple users subscribe to different epochs -- Execute swap with from_amount -- Verify epochs are settled in order (skipping epochs without subscriptions) -- Verify `epoch_settled_amounts` updated correctly -- Verify `last_settled_epoch` updated - -#### `test_settle_epochs_skips_empty_epochs` -- Users subscribe to epochs 1, 3, 5 (epochs 2, 4 have no subscriptions) -- Execute swap -- Verify epochs 1, 3, 5 are settled -- Verify epochs 2, 4 are skipped - -#### `test_sync_settled_epochs` -- Multiple epochs with subscriptions -- Some epochs fully settled, some partially settled -- Call `sync_settled_epochs(max_epochs_to_check)` -- Verify `last_settled_epoch` updated to highest fully settled epoch -- Verify respects `max_epochs_to_check` limit - -#### `test_sync_settled_epochs_with_limit` -- Many epochs to check -- Call `sync_settled_epochs(10)` to limit to 10 epochs -- Verify only checks up to 10 epochs -- Verify doesn't run out of gas - -#### `test_sync_settled_epochs_reverts_when_paused` -- Pause contract -- Attempt sync_settled_epochs -- Verify revert - ---- - -### 14. Integration Flow Tests - -#### `test_full_redemption_flow` -- User requests redemption from vault (mock) -- User subscribes to router with redemption NFT -- Relayer swaps assets -- User claims swapped assets -- Verify entire flow works end-to-end - -#### `test_batch_redemption_flow` -- Multiple users subscribe -- Multiple swaps executed -- All users claim in sequence -- Verify all receive correct proportional amounts - -#### `test_unsubscribe_flow` -- User subscribes -- User changes mind and unsubscribes before epoch settled -- Verify original NFT or assets returned correctly - ---- - -## Test Utilities Needed - -### Helper Functions -- `deploy_redemption_router()`: Deploy router with all dependencies (vault, redeem_request, to_asset, avnu_exchange addresses, integrator_fee_recipient, integrator_fee_amount_bps, min_subscribe_amount) -- `deploy_redeem_request(dummy_vault_address)`: Deploy actual RedeemRequest contract -- `deploy_mock_avnu_exchange()`: Deploy mock Avnu exchange from vault_allocator mocks -- `deploy_erc20_mock(name, symbol, initial_supply)`: Deploy ERC20 mock for from_asset/to_asset -- `setup_test_environment()`: Complete test setup with all contracts -- `execute_unsubscribe(router, nft_id, receiver)`: Helper to execute unsubscribe (requires router to own NFT) -- `mint_old_nft_to_user(user, redeem_request_info)`: - - Cheat caller as vault - - Mint NFT to user via redeem_request.mint() - - Return minted NFT ID -- `fulfill_old_nft(old_nft_id, asset_amount)`: - - Transfer asset_amount from vault to router (from_asset tokens) - - Cheat caller as vault → burn old NFT on redeem_request - - Mock `due_assets_from_id` to return 0 (since NFT is burnt) -- `mock_vault_due_assets(old_nft_id, amount)`: - - Use `mock_call` to set `due_assets_from_id` return value - - Use `mock_call(VAULT_ADDRESS, selector!("due_assets_from_id"), amount, 1)` -- `mock_vault_asset(asset_address)`: - - Use `mock_call` to set `asset()` return value -- `execute_swap(router, from_amount, to_amount, routes)`: Helper to execute swap (requires relayer role) - ---- - -## Test File Structure - -``` -packages/vault/src/redemption_router/ -└── test/ - └── redemption_router_test.cairo - -packages/vault_allocator/src/mocks/ -├── mock_avnu_exchange.cairo (for Avnu exchange mocking) -├── erc20.cairo (for ERC20 token mocking) -└── (other existing mocks) -``` - -## Mock Call Cheatcode Reference - -From [Starknet Foundry Documentation](https://foundry-rs.github.io/starknet-foundry/appendix/cheatcodes/mock_call.html): - -- `mock_call(contract_address, function_selector, ret_data, n_times)`: Mock a function call for `n_times` calls -- `start_mock_call(contract_address, function_selector, ret_data)`: Mock a function call indefinitely -- `stop_mock_call(contract_address, function_selector)`: Stop mocking a function - -Example usage: -```cairo -use snforge_std::{mock_call, start_mock_call, stop_mock_call}; - -// Mock due_assets_from_id to return 100 for 1 call -let due_amount: u256 = 100; -mock_call(VAULT_ADDRESS, selector!("due_assets_from_id"), due_amount, 1); - -// Mock asset() indefinitely -start_mock_call(VAULT_ADDRESS, selector!("asset"), from_asset_address); -``` - ---- - -## Notes - -1. **Epoch-Based Claiming**: Claims require the epoch to be fully settled. Users can only claim after their epoch is settled. -2. **Global Head**: All claims start from `unsettled_swap_id` (global head) -3. **Fairness**: Users with less vault due should get proportionally less swapped assets -4. **Rounding**: Use floor rounding for calculations, verify no value leakage -5. **Events**: Verify all events are emitted with correct parameters: - - `Subscribed`: `new_nft_id`, `old_nft_id`, `receiver` - - `Swapped`: `swap_id`, `from_amount`, `to_amount`, `last_settled_epoch` - - `Claimed`: `new_nft_id`, `old_nft_id`, `receivable`, `swap_id` - - `Unsubscribed`: `new_nft_id`, `old_nft_id`, `owner`, `is_old_nft_returned`, `is_original_assets_returned`, `original_assets_returned` -6. **State Updates**: Verify storage is updated correctly after each operation -7. **Mock Call Limitations**: - - `mock_call` only works for entry point calls, not internal calls - - Use `n_times = 1` for single-call scenarios, or `start_mock_call` for indefinite mocking - - Remember to mock `due_assets_from_id` AFTER burning old NFT (should return 0) or BEFORE (should return desired amount) -8. **Old NFT State & Due Assets Mocking**: - - **During subscription**: Old NFT exists, not burnt yet. `due_assets_from_id` returns non-zero (used for validation) - - **After fulfillment**: Old NFT is burnt on redeem_request (vault called `burn`). - - **During claim**: Router calls `vault.due_assets_from_id(old_nft_id)` to get what user was owed. - - **Mock this** to return the historical due amount (e.g., 100 tokens) even though NFT is burnt - - This represents "what was the user owed before redemption was fulfilled" - - Router uses this to calculate proportional swap amounts - - If old NFT is NOT burnt and we try to claim: Mock `due_assets_from_id` to return non-zero - - If old NFT IS burnt and we try to claim: Mock `due_assets_from_id` to return the historical amount (not 0, unless user was owed 0) -9. **Test Flow Order**: - - Setup contracts → Mint old NFT to user → User subscribes → Fulfill old NFT (burn) → Execute swaps → Mock `due_assets_from_id` with historical amount → Claim -10. **Epoch Settlement**: - - Epochs are settled in order (1, 2, 3...), but only epochs with subscriptions matter - - Epochs without subscriptions are skipped - - `last_settled_epoch` tracks the highest fully settled epoch (may not be sequential) -11. **Unsubscribe**: - - Requires router to own the NFT (not the original subscriber) - - Returns original NFT if not fulfilled, or from_assets if fulfilled - - Cannot unsubscribe if epoch is settled or partially settled -12. **Min Subscribe Amount**: - - Subscriptions below `min_subscribe_amount` are rejected to save gas - - Set by owner via `set_min_subscribe_amount` - From 2604af6b37a016804263d92b1d104659439a4c6a Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Mon, 5 Jan 2026 13:59:48 +0530 Subject: [PATCH 04/14] set min report delay to 0 --- packages/vault/src/test/units/vault.cairo | 17 +++++++++-------- packages/vault/src/vault/vault.cairo | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index c02ad023..cd8ac5e6 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -216,14 +216,15 @@ fn test_set_report_delay_not_owner() { vault.set_report_delay(Vault::MIN_REPORT_DELAY); } -#[test] -#[should_panic(expected: "Invalid report delay")] -fn test_set_report_delay_invalid_delay() { - let (_, vault, _) = set_up(); - let invalid_delay = Vault::MIN_REPORT_DELAY - 1; - cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_report_delay(invalid_delay); -} +// Not needed because MIN_REPORT_DELAY is 0 +// #[test] +// #[should_panic(expected: "Invalid report delay")] +// fn test_set_report_delay_invalid_delay() { +// let (_, vault, _) = set_up(); +// let invalid_delay = Vault::MIN_REPORT_DELAY - 1; +// cheat_caller_address_once(vault.contract_address, OWNER()); +// vault.set_report_delay(invalid_delay); +// } #[test] fn test_set_report_delay_success() { diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index 1bb806df..bcf77ce0 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -64,7 +64,7 @@ pub mod Vault { pub const MAX_REDEEM_FEE: u256 = WAD / 1000; // 0.1% - maximum redemption fee pub const MAX_MANAGEMENT_FEE: u256 = WAD / 50; // 2% - maximum annual management fee pub const MAX_PERFORMANCE_FEE: u256 = WAD / 5; // 20% - maximum performance fee - pub const MIN_REPORT_DELAY: u64 = HOUR; // 1 hour - minimum report delay + pub const MIN_REPORT_DELAY: u64 = 0; // 0 seconds - minimum report delay // --- Time Constants --- pub const MIN: u64 = 60; // Seconds in a minute From 0d7c9d5f5bea3ef5e0f9c37512325bfc5023df86 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Mon, 5 Jan 2026 14:58:49 +0530 Subject: [PATCH 05/14] add Redeem and subscribe in Router --- .../src/redemption_router/interface.cairo | 1 + .../redemption_router/redemption_router.cairo | 120 +++++++++++------- .../src/test/units/redemption_router.cairo | 48 +++++++ 3 files changed, 125 insertions(+), 44 deletions(-) diff --git a/packages/vault/src/redemption_router/interface.cairo b/packages/vault/src/redemption_router/interface.cairo index 11582ea3..9f16b5ae 100644 --- a/packages/vault/src/redemption_router/interface.cairo +++ b/packages/vault/src/redemption_router/interface.cairo @@ -17,6 +17,7 @@ pub struct RequestInfo { #[starknet::interface] pub trait IRedemptionRouter { fn subscribe(ref self: TContractState, nft_id: u256, receiver: ContractAddress) -> u256; + fn redeem_and_subscribe(ref self: TContractState, shares: u256, receiver: ContractAddress) -> u256; fn swap( ref self: TContractState, routes: Array, diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index 64d675ed..ce92d74e 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -311,6 +311,63 @@ pub mod RedemptionRouter { } } + // Internal subscription logic when NFT is already owned by this contract + fn _subscribe_internal( + ref self: ContractState, + nft_id: u256, + receiver: ContractAddress, + vault_dispatcher: IVaultDispatcher, + ) -> u256 { + // Read epoch and nominal from NFT + let redeem_request_interface = IRedeemRequestDispatcher { + contract_address: self.redeem_request.read(), + }; + let redeem_request_info = redeem_request_interface.id_to_info(nft_id); + let epoch = redeem_request_info.epoch; + + // Get due_amount from vault + let due_amount = vault_dispatcher.due_assets_from_id(nft_id); + if (due_amount == 0) { + // nothing to settle for any NFT with due_amount == 0 + Errors::invalid_nft_id(); + } + + // avoid processing very small subscriptions to save gas + if (due_amount < self.min_subscribe_amount.read()) { + Errors::too_small_subscribe_amount(); + } + + // Snapshot epoch data (required for fairly distributing redeemed assets to subscribers) + self._snapshot_epoch_data(epoch, vault_dispatcher); + + // Note: NFT is already owned by this contract, so no transfer needed + + // Mint new NFT to receiver + let new_nft_id = self.nft_id_counter.read(); + self.erc721.mint(receiver, new_nft_id); + self.nft_id_counter.write(new_nft_id + 1); + + // Store mapping with epoch and due_amount_approximate + let request_info = RequestInfo { + old_nft_id: nft_id, + is_claimed: false, + epoch, + due_amount_approximate: due_amount, + unsubscribed: false, + }; + self._update_request_info(new_nft_id, request_info); + + // Update epoch_wise_nominals (accumulate nominal for this epoch) + // Use to compute how much of the epoch is settled + let current_epoch_amount = self.epoch_wise_nominals.read(epoch); + self.epoch_wise_nominals.write(epoch, current_epoch_amount + redeem_request_info.nominal); + + // Emit event + self.emit(Subscribed { new_nft_id, old_nft_id: nft_id, receiver }); + + new_nft_id + } + // Process swap pool iteration to calculate receivable (read-only) fn _calculate_receivable_from_pools( self: @ContractState, @@ -712,57 +769,32 @@ pub mod RedemptionRouter { self.pausable.assert_not_paused(); let caller = get_caller_address(); - - // Read epoch and due_amount from old NFT before transferring - let redeem_request_interface = IRedeemRequestDispatcher { - contract_address: self.redeem_request.read(), - }; - let redeem_request_info = redeem_request_interface.id_to_info(nft_id); - let epoch = redeem_request_info.epoch; - - // Get due_amount from vault (before NFT is transferred/burned) let vault_dispatcher = IVaultDispatcher { contract_address: self.vault.read() }; - let due_amount = vault_dispatcher.due_assets_from_id(nft_id); - if (due_amount == 0) { - // nothing to settle for any NFT with due_amount == 0 - Errors::invalid_nft_id(); - } - - // avoid processing very small subscriptions to save gas - if (due_amount < self.min_subscribe_amount.read()) { - Errors::too_small_subscribe_amount(); - } - - // Snapshot epoch data (required for fairly distributing redeemed assets to subscribers) - self._snapshot_epoch_data(epoch, vault_dispatcher); // Transfer original NFT from caller to this contract self._transfer_original_nft(nft_id: nft_id, from_address: caller, to_address: get_contract_address()); - // Mint new NFT to receiver - let new_nft_id = self.nft_id_counter.read(); - self.erc721.mint(receiver, new_nft_id); - self.nft_id_counter.write(new_nft_id + 1); + // Use internal helper to handle subscription logic + self._subscribe_internal(nft_id, receiver, vault_dispatcher) + } - // Store mapping with epoch and due_amount_approximate - let request_info = RequestInfo { - old_nft_id: nft_id, - is_claimed: false, - epoch, - due_amount_approximate: due_amount, - unsubscribed: false, - }; - self._update_request_info(new_nft_id, request_info); + fn redeem_and_subscribe(ref self: ContractState, shares: u256, receiver: ContractAddress) -> u256 { + self.pausable.assert_not_paused(); - // Update epoch_wise_nominals (accumulate nominal for this epoch) - // Use to compute how much of the epoch is settled - let current_epoch_amount = self.epoch_wise_nominals.read(epoch); - self.epoch_wise_nominals.write(epoch, current_epoch_amount + redeem_request_info.nominal); - - // Emit event - self.emit(Subscribed { new_nft_id, old_nft_id: nft_id, receiver }); - - new_nft_id + let caller = get_caller_address(); + let this = get_contract_address(); + let vault_address = self.vault.read(); + + // Transfer shares from user to this contract + let vault_erc20_dispatcher = ERC20ABIDispatcher { contract_address: vault_address }; + assert(vault_erc20_dispatcher.transfer_from(caller, this, shares), 'Transfer failed'); + + // Call request_redeem on vault with receiver = this contract + let vault_dispatcher = IVaultDispatcher { contract_address: vault_address }; + let nft_id = vault_dispatcher.request_redeem(shares, this, this); + + // Subscribe the NFT (already owned by this contract) + self._subscribe_internal(nft_id, receiver, vault_dispatcher) } fn swap( diff --git a/packages/vault/src/test/units/redemption_router.cairo b/packages/vault/src/test/units/redemption_router.cairo index fa47e118..978b6803 100644 --- a/packages/vault/src/test/units/redemption_router.cairo +++ b/packages/vault/src/test/units/redemption_router.cairo @@ -460,6 +460,54 @@ fn test_subscribe_reverts_on_too_small_amount() { router.subscribe(old_nft_id, USER1()); } +#[test] +fn test_redeem_and_subscribe_transfers_shares_and_subscribes() { + let (vault, _, _, redeem_request, _, router) = set_up(); + + // User deposits assets to get vault shares + let nominal: u256 = WAD * 100; + let shares = mint_old_nft_to_user(vault, USER1(), nominal); + let epoch: u256 = vault.epoch(); // Get current epoch from vault + + // User approves router to transfer shares + let vault_erc20_dispatcher = ERC20ABIDispatcher { contract_address: vault.contract_address }; + cheat_caller_address(vault.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + vault_erc20_dispatcher.approve(router.contract_address, shares); + + // Get user's share balance before + let user_shares_before = vault_erc20_dispatcher.balance_of(USER1()); + + // Call redeem_and_subscribe + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let new_nft_id = router.redeem_and_subscribe(shares, USER1()); + + // Verify new NFT was minted to receiver + let router_erc721 = ERC721ABIDispatcher { contract_address: router.contract_address }; + assert(router_erc721.owner_of(new_nft_id) == USER1(), 'New NFT owner incorrect'); + + // Verify user's shares were transferred (burned by vault during request_redeem) + let user_shares_after = vault_erc20_dispatcher.balance_of(USER1()); + assert(user_shares_after == user_shares_before - shares, 'User shares not transferred'); + + // Verify old NFT (from request_redeem) is owned by router + // The NFT ID returned from request_redeem is stored in old_nft_id + let request_info = router.new_nft_request_info(new_nft_id); + let old_nft_id = request_info.old_nft_id; + let redeem_request_erc721 = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + assert(redeem_request_erc721.owner_of(old_nft_id) == router.contract_address, 'Old NFT not owned by router'); + + // Verify mapping stored correctly + assert(request_info.is_claimed == false, 'is_claimed should be false'); + assert(request_info.epoch == epoch, 'Epoch stored incorrectly'); + assert(request_info.due_amount_approximate > 0, 'Due amount should be set'); + assert(request_info.unsubscribed == false, 'unsubscribed should be false'); + + // Verify new_nft_id is correct (should be 0 if this is the first subscription) + assert(new_nft_id == 0, 'First NFT ID should be 0'); +} + // ============================================================================ // 3. Swap Function Tests // ============================================================================ From ee0f5039ca5a87f2fc7f2169b7a30ca30b2ad023 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 11:23:48 +0530 Subject: [PATCH 06/14] script updates --- my_scripts/contracts.json | 2 +- my_scripts/deploy.ts | 26 +- my_scripts/package.json | 2 +- my_scripts/pnpm-lock.yaml | 2450 +------------------------------------ 4 files changed, 66 insertions(+), 2414 deletions(-) diff --git a/my_scripts/contracts.json b/my_scripts/contracts.json index 299e7f20..46426e0c 100644 --- a/my_scripts/contracts.json +++ b/my_scripts/contracts.json @@ -1 +1 @@ -{"class_hashes":{"Vault":"0x5f3e4d3a6f6ea274d0288b7320428e9960298b9db1f6848b7805d14ace6413e","VaultAllocator":"0x7608b7b98f28a18841285367907f2c8bb924949e9f610f370b190e106cd3c3f","RedeemRequest":"0x7421d403cf73830acc41ebc9646c74f4a931f747ac275e32d82fc3a7c7a9aef","Manager":"0x7dbf4bc6ebf73952e77d60a7da8b1ec1523cfd3477ed99253403a3e0cab40d0","SimpleDecoderAndSanitizer":"0x19c38fa79f607d0935596802bcee103b88bc81dfa4b9f2d8bf0e398b19ec3c8","UsdtFixer":"0x19b362ba0e70d94185ebb986ea80039ec19ffca410f200f971fe5731ffd5a21","RedemptionRouter":"0x3f8f13e8146a875ba5cdffb510c51b3691d4b5401baf23b6bd5746881c4095f"},"contracts":{"Vault":"0x6a346bda4e723d3f4763513007d4b8ef0029f491ed0a1e0626db6d7f3af3c01","RedeemRequest":"0x66c5f84e5fc6c20545737cc187289adccacfeb9829da6fd4ddf6121b32573dc","VaultAllocator":"0x292503a0bff97ad8818481cca50f5f9298537d0e0e9dc6a7ac9bbb8a93f71a3","Manager":"0x2d7822d1616f5e0b556c0f7467eb968bd2acd0f2b2b8623adff96a51e5516db","SimpleDecoderAndSanitizer":"0x7b6f98311af8aa425278570e62abf523e6462eaa01a38c1feab9b2f416492e2","aum_oracle":"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345","UsdtFixer":"0x7954afaca4c706f9f30658777e55b7f8e264c8974b8a2638d5ebb359280816","RedemptionRouter":"0x6ea649f402898f69baf775c1afdd08522c071c640b9c4460192070ec2b96417"}} \ No newline at end of file +{"class_hashes":{"Vault":"0x5f3e4d3a6f6ea274d0288b7320428e9960298b9db1f6848b7805d14ace6413e","VaultAllocator":"0x7608b7b98f28a18841285367907f2c8bb924949e9f610f370b190e106cd3c3f","RedeemRequest":"0x7421d403cf73830acc41ebc9646c74f4a931f747ac275e32d82fc3a7c7a9aef","Manager":"0x7dbf4bc6ebf73952e77d60a7da8b1ec1523cfd3477ed99253403a3e0cab40d0","SimpleDecoderAndSanitizer":"0x19c38fa79f607d0935596802bcee103b88bc81dfa4b9f2d8bf0e398b19ec3c8","UsdtFixer":"0x19b362ba0e70d94185ebb986ea80039ec19ffca410f200f971fe5731ffd5a21","RedemptionRouter":"0x5a11a86291a4f64583bda5c9706535253d842bd5a391f6481bcce38f24351b2"},"contracts":{"Vault":"0x6a346bda4e723d3f4763513007d4b8ef0029f491ed0a1e0626db6d7f3af3c01","RedeemRequest":"0x66c5f84e5fc6c20545737cc187289adccacfeb9829da6fd4ddf6121b32573dc","VaultAllocator":"0x292503a0bff97ad8818481cca50f5f9298537d0e0e9dc6a7ac9bbb8a93f71a3","Manager":"0x2d7822d1616f5e0b556c0f7467eb968bd2acd0f2b2b8623adff96a51e5516db","SimpleDecoderAndSanitizer":"0x7b6f98311af8aa425278570e62abf523e6462eaa01a38c1feab9b2f416492e2","aum_oracle":"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345","UsdtFixer":"0x7954afaca4c706f9f30658777e55b7f8e264c8974b8a2638d5ebb359280816","RedemptionRouter":"0x3de9c409d1e357e25778fb7a3e2e2393666956846a5c2caa607296fa8e76b5d"}} \ No newline at end of file diff --git a/my_scripts/deploy.ts b/my_scripts/deploy.ts index 03f29108..1252c045 100644 --- a/my_scripts/deploy.ts +++ b/my_scripts/deploy.ts @@ -329,7 +329,7 @@ async function deployUsdtFixer() { async function deployRedemptionRouter() { const provider = config.provider; // ! set strategy - const strategy = HyperLSTStrategies.find(u => u.name.includes('xWBTC'))!; + const strategy = HyperLSTStrategies.find(u => u.name.includes('xtBTC'))!; const calls = await Deployer.prepareMultiDeployContracts([{ contract_name: 'RedemptionRouter', package_name: VAULT_PACKAGE, @@ -338,8 +338,8 @@ async function deployRedemptionRouter() { strategy.additionalInfo.vaultAddress.address, strategy.additionalInfo.redeemRequestNFT.address, // ! set to_asset - Global.getDefaultTokens().find(t => t.symbol === 'WBTC')?.address!, - "0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f", + Global.getDefaultTokens().find(t => t.symbol === 'tBTC')?.address!, + "0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f", // avnu exchange OWNER, "0", uint256.bnToUint256(0) // min subscribe amount @@ -403,7 +403,7 @@ if (require.main === module) { // deployStrategy(); // deployAUMOracle("0x437ef1e7d0f100b2e070b7a65cafec0b2be31b0290776da8b4112f5473d8d9") - const strategy = HyperLSTStrategies.find(u => u.name.includes('xWBTC'))!; + const strategy = HyperLSTStrategies.find(u => u.name.includes('xSTRK'))!; // const vaultStrategy = new UniversalStrategy(config, pricer, strategy); const vaultStrategy = new UniversalLstMultiplierStrategy(config, pricer, strategy); const vaultContracts = { @@ -422,25 +422,25 @@ if (require.main === module) { // await upgrade('Manager', VAULT_ALLOCATOR_PACKAGE, vaultContracts.manager.toString()); // await upgrade('RedeemRequest', VAULT_PACKAGE, vaultContracts.redeemRequest.toString()); // await configureSettings(vaultContracts); - await setManagerRoot(vaultStrategy, ContractAddr.from(RELAYER)); + // await setManagerRoot(vaultStrategy, ContractAddr.from(RELAYER)); // await grantRole(vaultStrategy, hash.getSelectorFromName('ORACLE_ROLE'), strategy.additionalInfo.aumOracle.address); // await setMaxDelta(vaultStrategy, getMaxDelta(200, CommonSettings.vault.default_settings.report_delay * 6)); - // for (let i=0; i < UniversalStrategies.length; i++) { - // const u = UniversalStrategies[i]; - // const strategy = new UniversalStrategy(config, pricer, u); - // await setManagerRoot(strategy, ContractAddr.from(RELAYER)); + for (let i=0; i < HyperLSTStrategies.length; i++) { + const u = HyperLSTStrategies[i]; + const strategy = new UniversalLstMultiplierStrategy(config, pricer, u); + await setManagerRoot(strategy, ContractAddr.from(RELAYER)); // await setMaxDelta(strategy, getMaxDelta(200, CommonSettings.vault.default_settings.report_delay * 24)); // await grantRole(u, hash.getSelectorFromName('ORACLE_ROLE'), strategy.additionalInfo.aumOracle.address); // await setFeesConfig(strategy); // await pause(strategy); - // await unpause(strategy); - // } + // await unpause(strategy); + } // const netAPY = await vaultStrategy.netAPY(); // console.log(netAPY); } - // setConfig(); + setConfig(); // configurePriceRouter(); // deployPriceRouter(); // deployAvnuMiddleware(); @@ -450,7 +450,7 @@ if (require.main === module) { // asset: u.depositTokens[0].address.address // }))) // deploySanitizer(); - upgrade('RedemptionRouter', VAULT_PACKAGE, '0x6ea649f402898f69baf775c1afdd08522c071c640b9c4460192070ec2b96417'); + // upgrade('RedemptionRouter', VAULT_PACKAGE, '0x6ea649f402898f69baf775c1afdd08522c071c640b9c4460192070ec2b96417'); // grantRole(vaultStrategy, hash.getSelectorFromName('ORACLE_ROLE'), '0x2edf4edbed3f839e7f07dcd913e92299898ff4cf0ba532f8c572c66c5b331b2') // setMaxDelta(vaultStrategy, getMaxDelta(15, CommonSettings.vault.default_settings.report_delay * 24)); } \ No newline at end of file diff --git a/my_scripts/package.json b/my_scripts/package.json index 965dff44..7d4dacb0 100644 --- a/my_scripts/package.json +++ b/my_scripts/package.json @@ -16,7 +16,7 @@ "dependencies": { "@ericnordelo/strk-merkle-tree": "^1.0.0", "@noble/curves": "^2.0.0", - "@strkfarm/sdk": "^1.2.0", + "@strkfarm/sdk": "link:../../sdk-ts", "axios": "^1.12.2", "dotenv": "^17.2.1", "react": "^19.1.1", diff --git a/my_scripts/pnpm-lock.yaml b/my_scripts/pnpm-lock.yaml index 762e0309..6881b24e 100644 --- a/my_scripts/pnpm-lock.yaml +++ b/my_scripts/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^2.0.0 version: 2.0.1 '@strkfarm/sdk': - specifier: ^1.2.0 - version: 1.2.0(@types/react@19.2.7)(axios@1.12.2)(moment@2.30.1)(qs@6.14.0)(react@19.2.0)(request@2.88.2)(starknet@9.2.1) + specifier: link:../../sdk-ts + version: link:../../sdk-ts axios: specifier: ^1.12.2 version: 1.12.2 @@ -45,50 +45,6 @@ packages: '@adraffy/ens-normalize@1.10.1': resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} - '@apollo/client@3.11.8': - resolution: {integrity: sha512-CgG1wbtMjsV2pRGe/eYITmV5B8lXUCYljB2gB/6jWTFQcrvirUVvKg7qtFdjYkQSFbIffU1IDyxgeaN81eTjbA==} - peerDependencies: - graphql: ^15.0.0 || ^16.0.0 - graphql-ws: ^5.5.5 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 - subscriptions-transport-ws: ^0.9.0 || ^0.11.0 - peerDependenciesMeta: - graphql-ws: - optional: true - react: - optional: true - react-dom: - optional: true - subscriptions-transport-ws: - optional: true - - '@avnu/avnu-sdk@3.0.2': - resolution: {integrity: sha512-N8McoXYEcp1uhSZ4XlEi5BcTpvJDDhUw4kYnlYmPrd7fWezfGy4UHyoFT/A4gqEM4nL3vtENczDNFS1AVfdTnA==} - engines: {node: '>=18'} - peerDependencies: - ethers: ^6.13.0 - moment: ^2.30.1 - qs: ^6.13.0 - starknet: ^6.11.0 - - '@colors/colors@1.6.0': - resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} - engines: {node: '>=0.1.90'} - - '@cypress/request-promise@5.0.0': - resolution: {integrity: sha512-eKdYVpa9cBEw2kTBlHeu1PP16Blwtum6QHg/u9s/MoHkZfuo1pRGka1VlUHXF5kdew82BvOJVVGk0x8X0nbp+w==} - engines: {node: '>=0.10.0'} - peerDependencies: - '@cypress/request': ^3.0.0 - - '@cypress/request@3.0.9': - resolution: {integrity: sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==} - engines: {node: '>= 6'} - - '@dabh/diagnostics@2.0.8': - resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} - '@ericnordelo/strk-merkle-tree@1.0.0': resolution: {integrity: sha512-iKY8uZ84W6pOzUrq09QHb+jRU7LcB8b/h2kpmX1gEVVsGrQI84xqTjVQCyU2Pzl7h8loc3SL835yMUKdoXWjrQ==} @@ -302,74 +258,6 @@ packages: '@ethersproject/web@5.8.0': resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} - '@graphql-typed-document-node/core@3.2.0': - resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - - '@inquirer/checkbox@2.5.0': - resolution: {integrity: sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==} - engines: {node: '>=18'} - - '@inquirer/confirm@3.2.0': - resolution: {integrity: sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==} - engines: {node: '>=18'} - - '@inquirer/core@9.2.1': - resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==} - engines: {node: '>=18'} - - '@inquirer/editor@2.2.0': - resolution: {integrity: sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==} - engines: {node: '>=18'} - - '@inquirer/expand@2.3.0': - resolution: {integrity: sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==} - engines: {node: '>=18'} - - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} - engines: {node: '>=18'} - - '@inquirer/input@2.3.0': - resolution: {integrity: sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==} - engines: {node: '>=18'} - - '@inquirer/number@1.1.0': - resolution: {integrity: sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==} - engines: {node: '>=18'} - - '@inquirer/password@2.2.0': - resolution: {integrity: sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==} - engines: {node: '>=18'} - - '@inquirer/prompts@5.5.0': - resolution: {integrity: sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog==} - engines: {node: '>=18'} - - '@inquirer/rawlist@2.3.0': - resolution: {integrity: sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==} - engines: {node: '>=18'} - - '@inquirer/search@1.1.0': - resolution: {integrity: sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==} - engines: {node: '>=18'} - - '@inquirer/select@2.5.0': - resolution: {integrity: sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==} - engines: {node: '>=18'} - - '@inquirer/type@1.5.5': - resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} - engines: {node: '>=18'} - - '@inquirer/type@2.0.0': - resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==} - engines: {node: '>=18'} - - '@noble/curves@1.2.0': - resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.7.0': resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} engines: {node: ^14.21.3 || >=16} @@ -382,10 +270,6 @@ packages: resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} engines: {node: '>= 20.19.0'} - '@noble/hashes@1.3.2': - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} - engines: {node: '>= 16'} - '@noble/hashes@1.6.0': resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} engines: {node: ^14.21.3 || >=16} @@ -398,35 +282,6 @@ packages: resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} - '@redis/bloom@1.2.0': - resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} - peerDependencies: - '@redis/client': ^1.0.0 - - '@redis/client@1.6.1': - resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==} - engines: {node: '>=14'} - - '@redis/graph@1.1.1': - resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} - peerDependencies: - '@redis/client': ^1.0.0 - - '@redis/json@1.0.7': - resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} - peerDependencies: - '@redis/client': ^1.0.0 - - '@redis/search@1.2.0': - resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} - peerDependencies: - '@redis/client': ^1.0.0 - - '@redis/time-series@1.1.0': - resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} - peerDependencies: - '@redis/client': ^1.0.0 - '@scure/base@1.2.1': resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} @@ -442,13 +297,6 @@ packages: '@scure/starknet@1.1.0': resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} - '@scure/starknet@2.0.0': - resolution: {integrity: sha512-uDGDGleYeqsYQTpPU2IZdO7WeCeYO2sORIJdLnjX/vuENxnu8WRhabFKHjoQGQf0sPuMRD5nKyQBxKWy45W5cg==} - engines: {node: '>= 20.19.0'} - - '@so-ric/colorspace@1.1.6': - resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} - '@starknet-io/get-starknet-wallet-standard@5.0.0': resolution: {integrity: sha512-isDNGDlp16W24HE4IuweYXLDRZN0JbsDnazAieeKXE87Mn+jqhsjgTsMxcwWTjX7v906Bjz39FiDjGUddnr36g==} @@ -461,36 +309,9 @@ packages: '@starknet-io/types-js@0.9.2': resolution: {integrity: sha512-vWOc0FVSn+RmabozIEWcEny1I73nDGTvOrLYJsR1x7LGA3AZmqt4i/aW69o/3i2NN5CVP8Ok6G1ayRQJKye3Wg==} - '@strkfarm/sdk@1.2.0': - resolution: {integrity: sha512-rHYyHR9rZtngZY2YTtO6Ez673e2XnA1Vk2M9Jdl3LkCGeqhgidn//dyYgme85tRWo9hIMhjIaaseKurq8q3bNQ==} - hasBin: true - peerDependencies: - '@types/react': ^19.1.2 - axios: ^1.7.2 - react: 19.1.2 - starknet: 9.2.1 - - '@types/mute-stream@0.0.4': - resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} - - '@types/node@22.19.3': - resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} - - '@types/node@22.7.5': - resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} - '@types/node@24.9.1': resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==} - '@types/react@19.2.7': - resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} - - '@types/triple-beam@1.3.5': - resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - - '@types/wrap-ansi@3.0.0': - resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} - '@wallet-standard/base@1.1.0': resolution: {integrity: sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ==} engines: {node: '>=16'} @@ -499,22 +320,6 @@ packages: resolution: {integrity: sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg==} engines: {node: '>=16'} - '@wry/caches@1.0.1': - resolution: {integrity: sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==} - engines: {node: '>=8'} - - '@wry/context@0.7.4': - resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==} - engines: {node: '>=8'} - - '@wry/equality@0.5.7': - resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==} - engines: {node: '>=8'} - - '@wry/trie@0.5.0': - resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==} - engines: {node: '>=8'} - abi-wan-kanabi@2.2.4: resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} hasBin: true @@ -530,16 +335,6 @@ packages: zod: optional: true - aes-js@4.0.0-beta.5: - resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -551,60 +346,12 @@ packages: ansicolors@0.3.2: resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - array.prototype.findindex@2.2.4: - resolution: {integrity: sha512-LLm4mhxa9v8j0A/RPnpQAP4svXToJFh+Hp1pNYl5ZD5qpB4zdx/D4YjpVcETkhFbUKWO3iGMVLvrOnnmkAJT6A==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - aws-sign2@0.7.0: - resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - - aws4@1.13.2: - resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - axios@1.12.2: resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - - bignumber.js@4.0.4: - resolution: {integrity: sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg==} - - bl@1.2.3: - resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} - - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - bn.js@4.12.2: resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} @@ -614,119 +361,29 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browser-assert@1.2.1: - resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true - caseless@0.12.0: - resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-convert@3.1.3: - resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} - engines: {node: '>=14.6'} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-name@2.1.0: - resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} - engines: {node: '>=12.20'} - - color-string@2.1.4: - resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} - engines: {node: '>=18'} - - color@5.0.3: - resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} - engines: {node: '>=18'} - combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - dashdash@1.14.1: - resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} - engines: {node: '>=0.10'} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -739,28 +396,12 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - ecc-jsbn@0.1.2: - resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - enabled@2.0.0: - resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - error-stack-parser@2.1.4: - resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} - engines: {node: '>= 0.4'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -777,14 +418,6 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - esbuild@0.25.11: resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} engines: {node: '>=18'} @@ -799,46 +432,12 @@ packages: engines: {node: '>=4'} hasBin: true - ethers@6.16.0: - resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} - engines: {node: '>=14.0.0'} - - eventemitter3@3.1.2: - resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} - eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - extsprintf@1.3.0: - resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} - engines: {'0': node >=0.6.0} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fecha@4.2.3: - resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - fetch-cookie@3.0.1: resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} - file-type@3.9.0: - resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} - engines: {node: '>=0.10.0'} - - fn.name@1.1.0: - resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -848,17 +447,6 @@ packages: debug: optional: true - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - forever-agent@0.6.1: - resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - - form-data@2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -875,21 +463,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - - generic-pool@3.9.0: - resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} - engines: {node: '>= 4'} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -902,20 +475,9 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} - getpass@0.1.7: - resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -923,40 +485,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql-tag@2.12.6: - resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} - engines: {node: '>=10'} - peerDependencies: - graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - - graphql@16.9.0: - resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - - har-schema@2.0.0: - resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} - engines: {node: '>=4'} - - har-validator@5.1.5: - resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} - engines: {node: '>=6'} - deprecated: this library is no longer supported - - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -975,217 +503,42 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - - http-signature@1.2.0: - resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} - engines: {node: '>=0.8', npm: '>=1.3.7'} - - http-signature@1.4.0: - resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} - engines: {node: '>=0.10'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inquirer@10.2.2: - resolution: {integrity: sha512-tyao/4Vo36XnUItZ7DnUXX4f1jVao2mSrleV/5IPtW/XAEA26hRVsbc68nuTEKWcr5vMP/1mVoT2O7u8H4v1Vg==} - engines: {node: '>=18'} - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} + isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + lossless-json@4.3.0: + resolution: {integrity: sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==} - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isomorphic-fetch@3.0.0: - resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} - - isstream@0.1.2: - resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - jsprim@1.4.2: - resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} - engines: {node: '>=0.6.0'} - - jsprim@2.0.2: - resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} - engines: {'0': node >=0.6.0} - - kuler@2.0.0: - resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - logform@2.7.0: - resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} - engines: {node: '>= 12.0.0'} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lossless-json@4.3.0: - resolution: {integrity: sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -1196,46 +549,6 @@ packages: encoding: optional: true - node-telegram-bot-api@0.66.0: - resolution: {integrity: sha512-s4Hrg5q+VPl4/tJVG++pImxF6eb8tNJNj4KnDqAOKL6zGU34lo9RXmyAN158njwGN+v8hdNf8s9fWIYW9hPb5A==} - engines: {node: '>=0.12'} - - oauth-sign@0.9.0: - resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - one-time@1.0.0: - resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} - - optimism@0.18.1: - resolution: {integrity: sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - ox@0.4.4: resolution: {integrity: sha512-oJPEeCDs9iNiPs6J0rTx+Y0KGeCGyCAA3zo94yZhm8G5WpOxrwUtn2Ie/Y8IyARSqqY/j9JTKA3Fc1xs1DvFnw==} peerDependencies: @@ -1247,93 +560,26 @@ packages: pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - pump@2.0.1: - resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - - qs@6.5.3: - resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} - engines: {node: '>=0.6'} - querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react@19.2.0: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} - redis@4.7.1: - resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - rehackt@0.1.0: - resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==} - peerDependencies: - '@types/react': '*' - react: '*' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - - request-promise-core@1.1.3: - resolution: {integrity: sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==} - engines: {node: '>=0.10.0'} - peerDependencies: - request: ^2.34 - - request@2.88.2: - resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} - engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1344,101 +590,9 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - response-iterator@0.2.25: - resolution: {integrity: sha512-15K4tT8X35W0zJ5bv3fAf4eEKqOwS7yzd+Bg6YEE9NLltVbPbuTcYo3J2AP6AMQGMJmJkFCG421+kP2/iCBfDA==} - engines: {node: '>=0.8'} - - run-async@3.0.0: - resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} - engines: {node: '>=0.12.0'} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - source-map@0.5.6: - resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} - engines: {node: '>=0.10.0'} - - sshpk@1.18.0: - resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} - engines: {node: '>=0.10.0'} - hasBin: true - - stack-generator@2.0.10: - resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} - - stack-trace@0.0.10: - resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} - - stackframe@1.3.4: - resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - - stacktrace-gps@3.1.2: - resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} - - stacktrace-js@2.0.2: - resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} - starknet@6.24.1: resolution: {integrity: sha512-g7tiCt73berhcNi41otlN3T3kxZnIvZhMi8WdC21Y6GC6zoQgbI2z1t7JAZF9c4xZiomlanwVnurcpyfEdyMpg==} @@ -1446,135 +600,29 @@ packages: resolution: {integrity: sha512-bFJY2sMZ9tsLBhPCm719MWjoz+doabXIwPX/xtW56EHwAJMRAS6mICF6H2dCwOQHJmCMKpOSFBwW0SaiHzcioQ==} engines: {node: '>=22'} - stealthy-require@1.1.1: - resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} - engines: {node: '>=0.10.0'} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - symbol-observable@4.0.0: - resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} - engines: {node: '>=0.10'} - - text-hex@1.0.0: - resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} - - tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - - tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} - hasBin: true - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - tough-cookie@2.5.0: - resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} - engines: {node: '>=0.8'} - tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} - tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} - tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - triple-beam@1.4.1: - resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} - engines: {node: '>= 14.0.0'} - - ts-invariant@0.10.3: - resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} - engines: {node: '>=8'} - ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.20.6: resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} engines: {node: '>=18.0.0'} hasBin: true - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -1586,28 +634,9 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@3.4.0: - resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. - hasBin: true - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -1617,60 +646,14 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} - - winston-transport@4.9.0: - resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} - engines: {node: '>= 12.0.0'} - - winston@3.19.0: - resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} - engines: {node: '>= 12.0.0'} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -1679,88 +662,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} - - zen-observable-ts@1.2.5: - resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==} - - zen-observable@0.8.15: - resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} - snapshots: '@adraffy/ens-normalize@1.10.1': {} - '@apollo/client@3.11.8(@types/react@19.2.7)(graphql@16.9.0)(react@19.2.0)': - dependencies: - '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) - '@wry/caches': 1.0.1 - '@wry/equality': 0.5.7 - '@wry/trie': 0.5.0 - graphql: 16.9.0 - graphql-tag: 2.12.6(graphql@16.9.0) - hoist-non-react-statics: 3.3.2 - optimism: 0.18.1 - prop-types: 15.8.1 - rehackt: 0.1.0(@types/react@19.2.7)(react@19.2.0) - response-iterator: 0.2.25 - symbol-observable: 4.0.0 - ts-invariant: 0.10.3 - tslib: 2.8.1 - zen-observable-ts: 1.2.5 - optionalDependencies: - react: 19.2.0 - transitivePeerDependencies: - - '@types/react' - - '@avnu/avnu-sdk@3.0.2(ethers@6.16.0)(moment@2.30.1)(qs@6.14.0)(starknet@9.2.1)': - dependencies: - ethers: 6.16.0 - moment: 2.30.1 - qs: 6.14.0 - starknet: 9.2.1 - - '@colors/colors@1.6.0': {} - - '@cypress/request-promise@5.0.0(@cypress/request@3.0.9)(request@2.88.2)': - dependencies: - '@cypress/request': 3.0.9 - bluebird: 3.7.2 - request-promise-core: 1.1.3(request@2.88.2) - stealthy-require: 1.1.1 - tough-cookie: 4.1.4 - transitivePeerDependencies: - - request - - '@cypress/request@3.0.9': - dependencies: - aws-sign2: 0.7.0 - aws4: 1.13.2 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 4.0.4 - http-signature: 1.4.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - performance-now: 2.1.0 - qs: 6.14.0 - safe-buffer: 5.2.1 - tough-cookie: 5.1.2 - tunnel-agent: 0.6.0 - uuid: 8.3.2 - - '@dabh/diagnostics@2.0.8': - dependencies: - '@so-ric/colorspace': 1.1.6 - enabled: 2.0.0 - kuler: 2.0.0 - '@ericnordelo/strk-merkle-tree@1.0.0': dependencies: '@ethersproject/abi': 5.8.0 @@ -1972,191 +877,45 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - '@graphql-typed-document-node/core@3.2.0(graphql@16.9.0)': + '@noble/curves@1.7.0': dependencies: - graphql: 16.9.0 + '@noble/hashes': 1.6.0 - '@inquirer/checkbox@2.5.0': + '@noble/curves@1.9.7': dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 1.5.5 - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.3 + '@noble/hashes': 1.8.0 - '@inquirer/confirm@3.2.0': + '@noble/curves@2.0.1': dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/type': 1.5.5 + '@noble/hashes': 2.0.1 - '@inquirer/core@9.2.1': - dependencies: - '@inquirer/figures': 1.0.15 - '@inquirer/type': 2.0.0 - '@types/mute-stream': 0.0.4 - '@types/node': 22.19.3 - '@types/wrap-ansi': 3.0.0 - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 1.0.0 - signal-exit: 4.1.0 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 + '@noble/hashes@1.6.0': {} - '@inquirer/editor@2.2.0': - dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/type': 1.5.5 - external-editor: 3.1.0 + '@noble/hashes@1.8.0': {} - '@inquirer/expand@2.3.0': - dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/type': 1.5.5 - yoctocolors-cjs: 2.1.3 + '@noble/hashes@2.0.1': {} - '@inquirer/figures@1.0.15': {} + '@scure/base@1.2.1': {} - '@inquirer/input@2.3.0': - dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/type': 1.5.5 + '@scure/base@1.2.6': {} - '@inquirer/number@1.1.0': + '@scure/bip32@1.7.0': dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/type': 1.5.5 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 - '@inquirer/password@2.2.0': + '@scure/bip39@1.6.0': dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/type': 1.5.5 - ansi-escapes: 4.3.2 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 - '@inquirer/prompts@5.5.0': + '@scure/starknet@1.1.0': dependencies: - '@inquirer/checkbox': 2.5.0 - '@inquirer/confirm': 3.2.0 - '@inquirer/editor': 2.2.0 - '@inquirer/expand': 2.3.0 - '@inquirer/input': 2.3.0 - '@inquirer/number': 1.1.0 - '@inquirer/password': 2.2.0 - '@inquirer/rawlist': 2.3.0 - '@inquirer/search': 1.1.0 - '@inquirer/select': 2.5.0 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 - '@inquirer/rawlist@2.3.0': - dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/type': 1.5.5 - yoctocolors-cjs: 2.1.3 - - '@inquirer/search@1.1.0': - dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 1.5.5 - yoctocolors-cjs: 2.1.3 - - '@inquirer/select@2.5.0': - dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 1.5.5 - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.3 - - '@inquirer/type@1.5.5': - dependencies: - mute-stream: 1.0.0 - - '@inquirer/type@2.0.0': - dependencies: - mute-stream: 1.0.0 - - '@noble/curves@1.2.0': - dependencies: - '@noble/hashes': 1.3.2 - - '@noble/curves@1.7.0': - dependencies: - '@noble/hashes': 1.6.0 - - '@noble/curves@1.9.7': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/curves@2.0.1': - dependencies: - '@noble/hashes': 2.0.1 - - '@noble/hashes@1.3.2': {} - - '@noble/hashes@1.6.0': {} - - '@noble/hashes@1.8.0': {} - - '@noble/hashes@2.0.1': {} - - '@redis/bloom@1.2.0(@redis/client@1.6.1)': - dependencies: - '@redis/client': 1.6.1 - - '@redis/client@1.6.1': - dependencies: - cluster-key-slot: 1.1.2 - generic-pool: 3.9.0 - yallist: 4.0.0 - - '@redis/graph@1.1.1(@redis/client@1.6.1)': - dependencies: - '@redis/client': 1.6.1 - - '@redis/json@1.0.7(@redis/client@1.6.1)': - dependencies: - '@redis/client': 1.6.1 - - '@redis/search@1.2.0(@redis/client@1.6.1)': - dependencies: - '@redis/client': 1.6.1 - - '@redis/time-series@1.1.0(@redis/client@1.6.1)': - dependencies: - '@redis/client': 1.6.1 - - '@scure/base@1.2.1': {} - - '@scure/base@1.2.6': {} - - '@scure/bip32@1.7.0': - dependencies: - '@noble/curves': 1.9.7 - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@scure/bip39@1.6.0': - dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@scure/starknet@1.1.0': - dependencies: - '@noble/curves': 1.7.0 - '@noble/hashes': 1.6.0 - - '@scure/starknet@2.0.0': - dependencies: - '@noble/curves': 2.0.1 - '@noble/hashes': 2.0.1 - - '@so-ric/colorspace@1.1.6': - dependencies: - color: 5.0.3 - text-hex: 1.0.0 - - '@starknet-io/get-starknet-wallet-standard@5.0.0': + '@starknet-io/get-starknet-wallet-standard@5.0.0': dependencies: '@starknet-io/types-js': 0.7.10 '@wallet-standard/base': 1.1.0 @@ -2172,88 +931,16 @@ snapshots: '@starknet-io/types-js@0.9.2': {} - '@strkfarm/sdk@1.2.0(@types/react@19.2.7)(axios@1.12.2)(moment@2.30.1)(qs@6.14.0)(react@19.2.0)(request@2.88.2)(starknet@9.2.1)': - dependencies: - '@apollo/client': 3.11.8(@types/react@19.2.7)(graphql@16.9.0)(react@19.2.0) - '@avnu/avnu-sdk': 3.0.2(ethers@6.16.0)(moment@2.30.1)(qs@6.14.0)(starknet@9.2.1) - '@ericnordelo/strk-merkle-tree': 1.0.0 - '@noble/curves': 1.9.7 - '@noble/hashes': 2.0.1 - '@scure/starknet': 2.0.0 - '@types/react': 19.2.7 - axios: 1.12.2 - bignumber.js: 4.0.4 - browser-assert: 1.2.1 - chalk: 4.1.2 - commander: 12.1.0 - ethers: 6.16.0 - graphql: 16.9.0 - inquirer: 10.2.2 - node-telegram-bot-api: 0.66.0(request@2.88.2) - proxy-from-env: 1.1.0 - react: 19.2.0 - redis: 4.7.1 - stacktrace-js: 2.0.2 - starknet: 9.2.1 - winston: 3.19.0 - transitivePeerDependencies: - - bufferutil - - encoding - - graphql-ws - - moment - - qs - - react-dom - - request - - subscriptions-transport-ws - - supports-color - - utf-8-validate - - '@types/mute-stream@0.0.4': - dependencies: - '@types/node': 24.9.1 - - '@types/node@22.19.3': - dependencies: - undici-types: 6.21.0 - - '@types/node@22.7.5': - dependencies: - undici-types: 6.19.8 - '@types/node@24.9.1': dependencies: undici-types: 7.16.0 - '@types/react@19.2.7': - dependencies: - csstype: 3.2.3 - - '@types/triple-beam@1.3.5': {} - - '@types/wrap-ansi@3.0.0': {} - '@wallet-standard/base@1.1.0': {} '@wallet-standard/features@1.1.0': dependencies: '@wallet-standard/base': 1.1.0 - '@wry/caches@1.0.1': - dependencies: - tslib: 2.8.1 - - '@wry/context@0.7.4': - dependencies: - tslib: 2.8.1 - - '@wry/equality@0.5.7': - dependencies: - tslib: 2.8.1 - - '@wry/trie@0.5.0': - dependencies: - tslib: 2.8.1 - abi-wan-kanabi@2.2.4: dependencies: ansicolors: 0.3.2 @@ -2263,19 +950,6 @@ snapshots: abitype@1.2.3: {} - aes-js@4.0.0-beta.5: {} - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -2284,50 +958,8 @@ snapshots: ansicolors@0.3.2: {} - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - array.prototype.findindex@2.2.4: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assert-plus@1.0.0: {} - - async-function@1.0.0: {} - - async@3.2.6: {} - asynckit@0.4.0: {} - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - aws-sign2@0.7.0: {} - - aws4@1.13.2: {} - axios@1.12.2: dependencies: follow-redirects: 1.15.11 @@ -2336,139 +968,38 @@ snapshots: transitivePeerDependencies: - debug - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - - bignumber.js@4.0.4: {} - - bl@1.2.3: - dependencies: - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - - bluebird@3.7.2: {} - bn.js@4.12.2: {} bn.js@5.2.2: {} brorand@1.1.0: {} - browser-assert@1.2.1: {} - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - cardinal@2.1.1: dependencies: ansicolors: 0.3.2 redeyed: 2.1.1 - caseless@0.12.0: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chardet@0.7.0: {} - - cli-width@4.1.0: {} - cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - cluster-key-slot@1.1.2: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-convert@3.1.3: - dependencies: - color-name: 2.1.0 - color-name@1.1.4: {} - color-name@2.1.0: {} - - color-string@2.1.4: - dependencies: - color-name: 2.1.0 - - color@5.0.3: - dependencies: - color-convert: 3.1.3 - color-string: 2.1.4 - combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 - commander@12.1.0: {} - - core-util-is@1.0.2: {} - - core-util-is@1.0.3: {} - - csstype@3.2.3: {} - - dashdash@1.14.1: - dependencies: - assert-plus: 1.0.0 - - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - debug@3.2.7: - dependencies: - ms: 2.1.3 - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - delayed-stream@1.0.0: {} dotenv@17.2.3: {} @@ -2479,11 +1010,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - ecc-jsbn@0.1.2: - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - elliptic@6.6.1: dependencies: bn.js: 4.12.2 @@ -2496,73 +1022,6 @@ snapshots: emoji-regex@8.0.0: {} - enabled@2.0.0: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - error-stack-parser@2.1.4: - dependencies: - stackframe: 1.3.4 - - es-abstract@1.24.1: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.19 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -2578,16 +1037,6 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.2 - - es-to-primitive@1.3.0: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - esbuild@0.25.11: optionalDependencies: '@esbuild/aix-ppc64': 0.25.11 @@ -2621,62 +1070,15 @@ snapshots: esprima@4.0.1: {} - ethers@6.16.0: - dependencies: - '@adraffy/ens-normalize': 1.10.1 - '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 - '@types/node': 22.7.5 - aes-js: 4.0.0-beta.5 - tslib: 2.7.0 - ws: 8.17.1 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - eventemitter3@3.1.2: {} - eventemitter3@5.0.1: {} - extend@3.0.2: {} - - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - - extsprintf@1.3.0: {} - - fast-deep-equal@3.1.3: {} - - fast-json-stable-stringify@2.1.0: {} - - fecha@4.2.3: {} - fetch-cookie@3.0.1: dependencies: set-cookie-parser: 2.7.1 tough-cookie: 4.1.4 - file-type@3.9.0: {} - - fn.name@1.1.0: {} - follow-redirects@1.15.11: {} - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - forever-agent@0.6.1: {} - - form-data@2.3.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -2696,21 +1098,6 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.8: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.2 - is-callable: 1.2.7 - - functions-have-names@1.2.3: {} - - generator-function@2.0.1: {} - - generic-pool@3.9.0: {} - get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -2731,55 +1118,14 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 - getpass@0.1.7: - dependencies: - assert-plus: 1.0.0 - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - gopd@1.2.0: {} graceful-fs@4.2.11: {} - graphql-tag@2.12.6(graphql@16.9.0): - dependencies: - graphql: 16.9.0 - tslib: 2.8.1 - - graphql@16.9.0: {} - - har-schema@2.0.0: {} - - har-validator@5.1.5: - dependencies: - ajv: 6.12.6 - har-schema: 2.0.0 - - has-bigints@1.1.0: {} - - has-flag@4.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -2801,209 +1147,25 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 + inherits@2.0.4: {} - http-signature@1.2.0: - dependencies: - assert-plus: 1.0.0 - jsprim: 1.4.2 - sshpk: 1.18.0 + is-fullwidth-code-point@3.0.0: {} - http-signature@1.4.0: + isomorphic-fetch@3.0.0: dependencies: - assert-plus: 1.0.0 - jsprim: 2.0.2 - sshpk: 1.18.0 - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - inherits@2.0.4: {} - - inquirer@10.2.2: - dependencies: - '@inquirer/core': 9.2.1 - '@inquirer/prompts': 5.5.0 - '@inquirer/type': 1.5.5 - '@types/mute-stream': 0.0.4 - ansi-escapes: 4.3.2 - mute-stream: 1.0.0 - run-async: 3.0.0 - rxjs: 7.8.2 - - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 - - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-callable@1.2.7: {} - - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-fullwidth-code-point@3.0.0: {} - - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-map@2.0.3: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - - is-stream@2.0.1: {} - - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.19 - - is-typedarray@1.0.0: {} - - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isomorphic-fetch@3.0.0: - dependencies: - node-fetch: 2.7.0 - whatwg-fetch: 3.6.20 - transitivePeerDependencies: - - encoding - - isstream@0.1.2: {} + node-fetch: 2.7.0 + whatwg-fetch: 3.6.20 + transitivePeerDependencies: + - encoding js-sha3@0.8.0: {} - js-tokens@4.0.0: {} - - jsbn@0.1.1: {} - - json-schema-traverse@0.4.1: {} - - json-schema@0.4.0: {} - - json-stringify-safe@5.0.1: {} - jsonfile@6.2.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - jsprim@1.4.2: - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - - jsprim@2.0.2: - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - - kuler@2.0.0: {} - - lodash@4.17.21: {} - - logform@2.7.0: - dependencies: - '@colors/colors': 1.6.0 - '@types/triple-beam': 1.3.5 - fecha: 4.2.3 - ms: 2.1.3 - safe-stable-stringify: 2.5.0 - triple-beam: 1.4.1 - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - lossless-json@4.3.0: {} math-intrinsics@1.1.0: {} @@ -3014,77 +1176,14 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@1.6.0: {} - minimalistic-assert@1.0.1: {} minimalistic-crypto-utils@1.0.1: {} - moment@2.30.1: {} - - ms@2.1.3: {} - - mute-stream@1.0.0: {} - node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - node-telegram-bot-api@0.66.0(request@2.88.2): - dependencies: - '@cypress/request': 3.0.9 - '@cypress/request-promise': 5.0.0(@cypress/request@3.0.9)(request@2.88.2) - array.prototype.findindex: 2.2.4 - bl: 1.2.3 - debug: 3.2.7 - eventemitter3: 3.1.2 - file-type: 3.9.0 - mime: 1.6.0 - pump: 2.0.1 - transitivePeerDependencies: - - request - - supports-color - - oauth-sign@0.9.0: {} - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - one-time@1.0.0: - dependencies: - fn.name: 1.1.0 - - optimism@0.18.1: - dependencies: - '@wry/caches': 1.0.1 - '@wry/context': 0.7.4 - '@wry/trie': 0.5.0 - tslib: 2.8.1 - - os-tmpdir@1.0.2: {} - - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - ox@0.4.4: dependencies: '@adraffy/ens-normalize': 1.10.1 @@ -3099,253 +1198,30 @@ snapshots: pako@2.1.0: {} - performance-now@2.1.0: {} - - possible-typed-array-names@1.1.0: {} - - process-nextick-args@2.0.1: {} - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - proxy-from-env@1.1.0: {} psl@1.15.0: dependencies: punycode: 2.3.1 - pump@2.0.1: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - punycode@2.3.1: {} - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - - qs@6.5.3: {} - querystringify@2.2.0: {} - react-is@16.13.1: {} - react@19.2.0: {} - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - redeyed@2.1.1: dependencies: esprima: 4.0.1 - redis@4.7.1: - dependencies: - '@redis/bloom': 1.2.0(@redis/client@1.6.1) - '@redis/client': 1.6.1 - '@redis/graph': 1.1.1(@redis/client@1.6.1) - '@redis/json': 1.0.7(@redis/client@1.6.1) - '@redis/search': 1.2.0(@redis/client@1.6.1) - '@redis/time-series': 1.1.0(@redis/client@1.6.1) - - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 - - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 - - rehackt@0.1.0(@types/react@19.2.7)(react@19.2.0): - optionalDependencies: - '@types/react': 19.2.7 - react: 19.2.0 - - request-promise-core@1.1.3(request@2.88.2): - dependencies: - lodash: 4.17.21 - request: 2.88.2 - - request@2.88.2: - dependencies: - aws-sign2: 0.7.0 - aws4: 1.13.2 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - har-validator: 5.1.5 - http-signature: 1.2.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - oauth-sign: 0.9.0 - performance-now: 2.1.0 - qs: 6.5.3 - safe-buffer: 5.2.1 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 3.4.0 - require-directory@2.1.1: {} requires-port@1.0.0: {} resolve-pkg-maps@1.0.0: {} - response-iterator@0.2.25: {} - - run-async@3.0.0: {} - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - - safe-array-concat@1.1.3: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 - - safe-buffer@5.1.2: {} - - safe-buffer@5.2.1: {} - - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safe-stable-stringify@2.5.0: {} - - safer-buffer@2.1.2: {} - set-cookie-parser@2.7.1: {} - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - signal-exit@4.1.0: {} - - source-map@0.5.6: {} - - sshpk@1.18.0: - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - - stack-generator@2.0.10: - dependencies: - stackframe: 1.3.4 - - stack-trace@0.0.10: {} - - stackframe@1.3.4: {} - - stacktrace-gps@3.1.2: - dependencies: - source-map: 0.5.6 - stackframe: 1.3.4 - - stacktrace-js@2.0.2: - dependencies: - error-stack-parser: 2.1.4 - stack-generator: 2.0.10 - stacktrace-gps: 3.1.2 - starknet@6.24.1: dependencies: '@noble/curves': 1.7.0 @@ -3379,77 +1255,16 @@ snapshots: - typescript - zod - stealthy-require@1.1.1: {} - - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string.prototype.trim@1.2.10: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - has-property-descriptors: 1.0.2 - - string.prototype.trimend@1.0.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - symbol-observable@4.0.0: {} - - text-hex@1.0.0: {} - - tldts-core@6.1.86: {} - - tldts@6.1.86: - dependencies: - tldts-core: 6.1.86 - - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - - tough-cookie@2.5.0: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -3457,24 +1272,10 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 - tough-cookie@5.1.2: - dependencies: - tldts: 6.1.86 - tr46@0.0.3: {} - triple-beam@1.4.1: {} - - ts-invariant@0.10.3: - dependencies: - tslib: 2.8.1 - ts-mixer@6.0.4: {} - tslib@2.7.0: {} - - tslib@2.8.1: {} - tsx@4.20.6: dependencies: esbuild: 0.25.11 @@ -3482,85 +1283,17 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - - tweetnacl@0.14.5: {} - - type-fest@0.21.3: {} - - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typed-array-byte-length@1.0.3: - dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - - typed-array-byte-offset@1.0.4: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 - - typed-array-length@1.0.7: - dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 - - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 - - undici-types@6.19.8: {} - - undici-types@6.21.0: {} - undici-types@7.16.0: {} universalify@0.2.0: {} universalify@2.0.1: {} - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - url-parse@1.5.10: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 - util-deprecate@1.0.2: {} - - uuid@3.4.0: {} - - uuid@8.3.2: {} - - verror@1.10.0: - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - webidl-conversions@3.0.1: {} whatwg-fetch@3.6.20: {} @@ -3570,87 +1303,14 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 - - which-builtin-type@1.2.1: - dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.1.8 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.19 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 - - which-typed-array@1.1.19: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - winston-transport@4.9.0: - dependencies: - logform: 2.7.0 - readable-stream: 3.6.2 - triple-beam: 1.4.1 - - winston@3.19.0: - dependencies: - '@colors/colors': 1.6.0 - '@dabh/diagnostics': 2.0.8 - async: 3.2.6 - is-stream: 2.0.1 - logform: 2.7.0 - one-time: 1.0.0 - readable-stream: 3.6.2 - safe-stable-stringify: 2.5.0 - stack-trace: 0.0.10 - triple-beam: 1.4.1 - winston-transport: 4.9.0 - - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrappy@1.0.2: {} - - ws@8.17.1: {} - y18n@5.0.8: {} - yallist@4.0.0: {} - yargs-parser@21.1.1: {} yargs@17.7.2: @@ -3662,11 +1322,3 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - - yoctocolors-cjs@2.1.3: {} - - zen-observable-ts@1.2.5: - dependencies: - zen-observable: 0.8.15 - - zen-observable@0.8.15: {} From 2b96c3856c89af59126eee5321cad2cab433fb3c Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 12:31:26 +0530 Subject: [PATCH 07/14] improve comments on redemption router --- .../src/redemption_router/redemption_router.cairo | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index ce92d74e..7854c097 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -79,11 +79,14 @@ pub mod RedemptionRouter { integrator_fee_amount_bps: u128, // state variables - swap_id: u256, + swap_id: u256, // sequently updated id for each swap unsettled_swap_id: u256, nft_id_counter: u256, // Counter for new NFT IDs new_nft_request_map: Map, - swap_info: Map, // swap_id -> (from_remaining, to_remaining) + + // swap_id -> (from_remaining, to_remaining) + // - created during a swap and reduced when claims are made + swap_info: Map, // Epoch offset tracking epoch_offset_factor: Map, // epoch -> offset_factor (defaults to WAD) @@ -409,6 +412,7 @@ pub mod RedemptionRouter { let (from_remaining, to_remaining) = self.swap_info.read(pool_id); if (from_remaining == 0) { + // if pool is settled, advance unsettled_swap_id if (self.unsettled_swap_id.read() == pool_id) { self.unsettled_swap_id.write(pool_id + 1); } @@ -423,6 +427,7 @@ pub mod RedemptionRouter { let new_to = to_remaining - take_to; self.swap_info.write(pool_id, (new_from, new_to)); + // if pool is settled, advance unsettled_swap_id if (new_from == 0 && self.unsettled_swap_id.read() == pool_id) { self.unsettled_swap_id.write(pool_id + 1); } @@ -846,6 +851,7 @@ pub mod RedemptionRouter { } // Calculate adjusted due amount with epoch offset factor + // - vault shares to vault asset conversion let remaining_due = self._calculate_adjusted_due_amount(request_info.due_amount_approximate, epoch); // Process swap pools and calculate receivable @@ -935,6 +941,7 @@ pub mod RedemptionRouter { fn set_integrator_fee_amount_bps(ref self: ContractState, fee_bps: u128) { self.access_control.assert_only_role(OWNER_ROLE); + self.integrator_fee_amount_bps.write(fee_bps); } From cf0f9307dd4b864b39302b15ce2ad33ae3c5f472 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 12:32:01 +0530 Subject: [PATCH 08/14] audit-fix: Missing fee upper bound for integrator fee --- .../vault/src/redemption_router/errors.cairo | 4 ++ .../redemption_router/redemption_router.cairo | 4 ++ .../src/test/units/redemption_router.cairo | 55 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/packages/vault/src/redemption_router/errors.cairo b/packages/vault/src/redemption_router/errors.cairo index 601e116e..2f8d613c 100644 --- a/packages/vault/src/redemption_router/errors.cairo +++ b/packages/vault/src/redemption_router/errors.cairo @@ -62,6 +62,10 @@ pub mod Errors { pub fn not_owner() { panic!("Not owner"); } + + pub fn invalid_fee_amount() { + panic!("Invalid integrator fee amount"); + } } diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index 7854c097..93a9e20f 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -942,6 +942,10 @@ pub mod RedemptionRouter { fn set_integrator_fee_amount_bps(ref self: ContractState, fee_bps: u128) { self.access_control.assert_only_role(OWNER_ROLE); + // Ensure fee doesn't exceed 5% (500 bps) + if (fee_bps > 500) { // MAX_INTEGRATOR_FEES_BPS from Avnu + Errors::invalid_fee_amount(); + } self.integrator_fee_amount_bps.write(fee_bps); } diff --git a/packages/vault/src/test/units/redemption_router.cairo b/packages/vault/src/test/units/redemption_router.cairo index 978b6803..6e66842c 100644 --- a/packages/vault/src/test/units/redemption_router.cairo +++ b/packages/vault/src/test/units/redemption_router.cairo @@ -1146,6 +1146,61 @@ fn test_set_min_subscribe_amount_reverts_when_not_owner() { router.set_min_subscribe_amount(WAD * 100); } +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_integrator_fee_amount_bps_reverts_when_not_owner() { + let (_, _, _, _, _, router) = set_up(); + + // Non-owner attempts to set integrator_fee_amount_bps + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.set_integrator_fee_amount_bps(100); +} + +#[test] +#[should_panic(expected: "Invalid integrator fee amount")] +fn test_set_integrator_fee_amount_bps_reverts_on_invalid_fee() { + let (_, _, _, _, _, router) = set_up(); + + // Owner attempts to set fee > 500 bps (max allowed) + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.set_integrator_fee_amount_bps(501); // Exceeds max of 500 bps +} + +#[test] +fn test_set_integrator_fee_amount_bps_success() { + let (_, _, _, _, _, router) = set_up(); + + // Verify initial fee (set in set_up) + let initial_fee = router.integrator_fee_amount_bps(); + assert(initial_fee == 100, 'Initial fee should be 100 bps'); + + // Owner sets valid fee + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.set_integrator_fee_amount_bps(250); + + // Verify fee was updated + let new_fee = router.integrator_fee_amount_bps(); + assert(new_fee == 250, 'Fee should be 250 bps'); + + // Test edge case: set to max allowed (500 bps) + cheat_caller_address(router.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + router.set_integrator_fee_amount_bps(500); + + let max_fee = router.integrator_fee_amount_bps(); + assert(max_fee == 500, 'Fee should be 500 bps'); +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_integrator_fee_recipient_reverts_when_not_owner() { + let (_, _, _, _, _, router) = set_up(); + + // Non-owner attempts to set integrator_fee_recipient + let new_recipient: ContractAddress = 'NEW_RECIPIENT'.try_into().unwrap(); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.set_integrator_fee_recipient(new_recipient); +} + // ============================================================================ // 7. Epoch Settlement & Sync Tests // ============================================================================ From 13db213f7d8c18792c5ee587cf005596aca6146b Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 13:07:44 +0530 Subject: [PATCH 09/14] audit-fix: Unused internal functions --- .../redemption_router/redemption_router.cairo | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index 93a9e20f..14153b38 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -642,70 +642,6 @@ pub mod RedemptionRouter { vault_dispatcher.asset() } - // Handle unsubscribe when original NFT not fulfilled - fn _handle_unsubscribe_original_nft( - ref self: ContractState, - nft_id: u256, - request_info: RequestInfo, - receiver: ContractAddress, - ) { - let epoch = request_info.epoch; - if (self._is_epoch_settled(epoch)) { - Errors::cannot_unsubscribe_partial_swap(); - } - - self._transfer_original_nft(nft_id: request_info.old_nft_id, from_address: get_contract_address(), to_address: receiver); - - let mut updated = request_info; - updated.unsubscribed = true; - self._update_request_info(nft_id, updated); - self.erc721.burn(nft_id); - - self.emit(Unsubscribed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id, owner: receiver, is_old_nft_returned: true, is_original_assets_returned: false, original_assets_returned: 0 }); - } - - // Handle unsubscribe when original NFT fulfilled - fn _handle_unsubscribe_fulfilled_nft( - ref self: ContractState, - nft_id: u256, - request_info: RequestInfo, - receiver: ContractAddress, - ) { - let epoch = request_info.epoch; - if (self._is_epoch_settled(epoch)) { - Errors::cannot_unsubscribe_partial_swap(); - } - - let settled_amount = self.epoch_settled_amounts.read(epoch); - let expected_settled = self._calculate_expected_settled(epoch); - - if (settled_amount > 0 && settled_amount < expected_settled) { - Errors::cannot_unsubscribe_partial_swap(); - } - - // Compute assets based on current offset factor - // due_amount_approximate is the asset amount, adjust it by offset factor - let user_expected_amount = self._calculate_adjusted_due_amount(request_info.due_amount_approximate, epoch); - - let from_asset_address = self._get_from_asset_address(); - let erc20_dispatcher = ERC20ABIDispatcher { contract_address: from_asset_address }; - let this = get_contract_address(); - let contract_balance = erc20_dispatcher.balance_of(this); - - if (user_expected_amount > contract_balance) { - Errors::insufficient_balance_for_withdrawal(); - } - - erc20_dispatcher.transfer(receiver, user_expected_amount); - - let mut updated = request_info; - updated.unsubscribed = true; - self._update_request_info(nft_id, updated); - self.erc721.burn(nft_id); - - self.emit(Unsubscribed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id, owner: receiver, is_old_nft_returned: false, is_original_assets_returned: true, original_assets_returned: user_expected_amount }); - } - // Validate common prerequisites for unsubscribe operations fn _validate_unsubscribe_prerequisites( self: @ContractState, From a75a51bc35a986ae9d537434707edecb628e8fc3 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 13:18:23 +0530 Subject: [PATCH 10/14] audit-fix: Missing event emissions for setter functions --- .../redemption_router/redemption_router.cairo | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index 14153b38..a430140d 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -117,6 +117,9 @@ pub mod RedemptionRouter { Claimed: Claimed, RequestInfo: RequestInfo, Unsubscribed: Unsubscribed, + IntegratorFeeRecipientSet: IntegratorFeeRecipientSet, + IntegratorFeeAmountBpsSet: IntegratorFeeAmountBpsSet, + MinSubscribeAmountSet: MinSubscribeAmountSet, } #[derive(Drop, starknet::Event)] @@ -162,6 +165,22 @@ pub mod RedemptionRouter { pub receivable: u256, } + #[derive(Drop, starknet::Event)] + pub struct IntegratorFeeRecipientSet { + #[key] + pub recipient: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + pub struct IntegratorFeeAmountBpsSet { + pub fee_bps: u128, + } + + #[derive(Drop, starknet::Event)] + pub struct MinSubscribeAmountSet { + pub min_subscribe_amount: u256, + } + #[constructor] fn constructor( ref self: ContractState, @@ -873,6 +892,7 @@ pub mod RedemptionRouter { Errors::zero_address(); } self.integrator_fee_recipient.write(recipient); + self.emit(IntegratorFeeRecipientSet { recipient }); } fn set_integrator_fee_amount_bps(ref self: ContractState, fee_bps: u128) { @@ -883,6 +903,7 @@ pub mod RedemptionRouter { Errors::invalid_fee_amount(); } self.integrator_fee_amount_bps.write(fee_bps); + self.emit(IntegratorFeeAmountBpsSet { fee_bps }); } fn set_epoch_offset(ref self: ContractState, epoch: u256, offset_factor: u256) { @@ -943,6 +964,7 @@ pub mod RedemptionRouter { fn set_min_subscribe_amount(ref self: ContractState, min_subscribe_amount: u256) { self.access_control.assert_only_role(OWNER_ROLE); self.min_subscribe_amount.write(min_subscribe_amount); + self.emit(MinSubscribeAmountSet { min_subscribe_amount }); } fn pause(ref self: ContractState) { From 8417e63e3cc5bce6270fe11e0df7a3270ebd2eb8 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 20:06:05 +0530 Subject: [PATCH 11/14] audit-fix: Incorrect epoch offset calculation due to skipped currently handled epoch --- .../redemption_router/redemption_router.cairo | 6 +- .../src/test/units/redemption_router.cairo | 87 ++++++++++++++++++- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index a430140d..e1a7b128 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -88,7 +88,9 @@ pub mod RedemptionRouter { // - created during a swap and reduced when claims are made swap_info: Map, - // Epoch offset tracking + // Epoch offset tracking (in case, an epoch incurs loss, the output amount is lower than + // expected value computed during subscribe time) + // this factor represents that relative loss in WAD epoch_offset_factor: Map, // epoch -> offset_factor (defaults to WAD) epoch_redeem_assets: Map, // epoch -> snapshot of redeem_assets at subscribe time epoch_redeem_nominal: Map, // epoch -> snapshot of redeem_nominal at subscribe time @@ -930,7 +932,7 @@ pub mod RedemptionRouter { let handled_epochs_after = vault_dispatcher.handled_epoch_len(); // For each newly handled epoch, compute and update offset factor - let mut epoch = handled_epochs_before + 1; + let mut epoch = handled_epochs_before; while (epoch <= handled_epochs_after) { // Read new redeem_assets and redeem_nominal after report let new_redeem_assets = vault_dispatcher.redeem_assets(epoch); diff --git a/packages/vault/src/test/units/redemption_router.cairo b/packages/vault/src/test/units/redemption_router.cairo index 6e66842c..def6d663 100644 --- a/packages/vault/src/test/units/redemption_router.cairo +++ b/packages/vault/src/test/units/redemption_router.cairo @@ -140,12 +140,12 @@ fn set_up() -> ( // to avoid zero liquidity error, seed some initial liquidity let due_amount_1: u256 = WAD * 100; - mint_old_nft_to_user(vault, OWNER(), due_amount_1); + deposit_to_user(vault, OWNER(), due_amount_1); (vault, from_asset, to_asset, redeem_request, avnu_exchange, router) } -fn mint_old_nft_to_user( +fn deposit_to_user( vault: IVaultDispatcher, user: ContractAddress, nominal: u256, ) -> u256 { // First, user needs to deposit assets to get vault shares @@ -172,7 +172,7 @@ fn mint_old_nft_to_user( fn mint_and_redeem_old_nft_to_user( vault: IVaultDispatcher, user: ContractAddress, nominal: u256, ) -> u256 { - let shares = mint_old_nft_to_user(vault, user, nominal); + let shares = deposit_to_user(vault, user, nominal); // Call request_redeem on vault as if user is trying to withdraw // Now call request_redeem as the user @@ -466,7 +466,7 @@ fn test_redeem_and_subscribe_transfers_shares_and_subscribes() { // User deposits assets to get vault shares let nominal: u256 = WAD * 100; - let shares = mint_old_nft_to_user(vault, USER1(), nominal); + let shares = deposit_to_user(vault, USER1(), nominal); let epoch: u256 = vault.epoch(); // Get current epoch from vault // User approves router to transfer shares @@ -1258,3 +1258,82 @@ fn test_sync_settled_epochs_reverts_when_paused() { router.sync_settled_epochs(10); } +#[test] +fn test_report_updates_offset_factor_for_currently_handled_epoch() { + let (vault, _, _, redeem_request, _, router) = set_up(); + + let vault_dispatcher = IVaultDispatcher { contract_address: vault.contract_address }; + + // Initial state: no epochs handled yet + let handled_epochs_before = vault.handled_epoch_len(); + assert(handled_epochs_before == 0, 'Should start with 0'); + + // Grant RELAYER role to router + let access_control = IAccessControlDispatcher { + contract_address: vault.contract_address, + }; + cheat_caller_address(vault.contract_address, OWNER(), span: CheatSpan::TargetCalls(1)); + access_control.grant_role(Vault::ORACLE_ROLE, router.contract_address); + + // Subscribe to epoch 0 (before any report) + let due_amount: u256 = WAD * 100; + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); + + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let _new_nft_id = router.subscribe(old_nft_id, USER1()); + + // Verify epoch 0 offset factor is not set (defaults to 0, which means WAD when used) + let offset_before_report = router.get_epoch_offset(0); + assert(offset_before_report == 1000000000000000000, 'Offset should be 0'); + + // Call report on vault to handle epoch 0 + // increase timestamp, else report fails + let now = get_block_timestamp(); + start_cheat_block_timestamp_global(now + 3600); // 1 hour + + // runs epoch from 1 to 1 (skips epoch 0) + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + router.report(0); + + // After vault report, handled_epoch_len should be 1 (epoch 0 is now handled) + let handled_epochs_after_vault = vault.handled_epoch_len(); + assert(handled_epochs_after_vault == 1, 'Epoch 0 should be handled'); + + // advance epoch + start_cheat_block_timestamp_global(now + (3600 * 2)); // 2 hour + // Now call router.report() which should update offset factor for epoch 0 + // The bug: it starts from handled_epochs_before + 1 = 1 + 1 = 2, runs till 2, skipping epoch 1 + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + let aum = vault.aum(); + router.report(aum * 100001/100000); + + // request withdrawal (i.e. mint nft) && subscribe to epoch 1 + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), WAD * 100); + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + let _new_nft_id = router.subscribe(old_nft_id, USER1()); + + start_cheat_block_timestamp_global(now + (3600 * 3)); // 3 hour + // Now call router.report() which should update offset factor for epoch 0 + // The bug: it starts from handled_epochs_before + 1 = 2 + 1 = 3, runs till 3, skipping epoch 2 + cheat_caller_address(router.contract_address, RELAYER, span: CheatSpan::TargetCalls(1)); + let aum = vault.aum(); + router.report(aum * (100000 - 1)/100000); // loss of 1 basis point + + // Verify epoch 0 offset factor was updated + // If the bug exists, offset will still be 0 (not updated) + // If fixed, offset should be calculated based on the ratio + let offset_after_report = router.get_epoch_offset(2); + + // The bug: offset_after_report will be 0 because epoch 0 was skipped + // After fix: offset_after_report should be calculated if conditions are met + // For now, we test that the bug exists by checking offset is still 0 + // After fixing, this assertion should check that offset was calculated + assert(offset_after_report != WAD && offset_after_report != 0, 'Offset factor not right'); +} \ No newline at end of file From 251f4a1c38f7d576569328925b65dbdc9c366c56 Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 20:15:32 +0530 Subject: [PATCH 12/14] audit-fix: Subscribe function allows subscribing NFTs from already settled epochs --- .../vault/src/redemption_router/errors.cairo | 4 ++ .../redemption_router/redemption_router.cairo | 4 ++ .../src/test/units/redemption_router.cairo | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/packages/vault/src/redemption_router/errors.cairo b/packages/vault/src/redemption_router/errors.cairo index 2f8d613c..37d77a8e 100644 --- a/packages/vault/src/redemption_router/errors.cairo +++ b/packages/vault/src/redemption_router/errors.cairo @@ -66,6 +66,10 @@ pub mod Errors { pub fn invalid_fee_amount() { panic!("Invalid integrator fee amount"); } + + pub fn epoch_already_handled() { + panic!("Epoch already handled"); + } } diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index e1a7b128..8cf66a3e 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -348,6 +348,10 @@ pub mod RedemptionRouter { }; let redeem_request_info = redeem_request_interface.id_to_info(nft_id); let epoch = redeem_request_info.epoch; + let handled_epoch_len = vault_dispatcher.handled_epoch_len(); + if (epoch < handled_epoch_len) { + Errors::epoch_already_handled(); + } // Get due_amount from vault let due_amount = vault_dispatcher.due_assets_from_id(nft_id); diff --git a/packages/vault/src/test/units/redemption_router.cairo b/packages/vault/src/test/units/redemption_router.cairo index def6d663..68d6fe52 100644 --- a/packages/vault/src/test/units/redemption_router.cairo +++ b/packages/vault/src/test/units/redemption_router.cairo @@ -460,6 +460,44 @@ fn test_subscribe_reverts_on_too_small_amount() { router.subscribe(old_nft_id, USER1()); } +#[test] +#[should_panic(expected: "Epoch already handled")] +fn test_subscribe_reverts_when_epoch_already_handled() { + let (vault, _, _, redeem_request, _, router) = set_up(); + + // 1. Mint the NFT (redeem request) but don't subscribe + let due_amount: u256 = WAD * 100; + let old_nft_id = mint_and_redeem_old_nft_to_user(vault, USER1(), due_amount); + + // Verify NFT was minted and epoch is current + let epoch_before_report = vault.epoch(); + let handled_epochs_before = vault.handled_epoch_len(); + assert(handled_epochs_before == 0, 'Should start with 0'); + + // 2. Report - this handles the epoch + // increase timestamp, else report fails + let now = get_block_timestamp(); + start_cheat_block_timestamp_global(now + 3600); // 1 hour + + let oracle = ORACLE(); + cheat_caller_address(vault.contract_address, oracle, span: CheatSpan::TargetCalls(1)); + vault.report(0); + + // Verify epoch was handled + let handled_epochs_after = vault.handled_epoch_len(); + assert(handled_epochs_after == 1, 'Epoch 0 should be handled'); + assert(epoch_before_report < handled_epochs_after, 'Epoch should be handled'); + + // 3. Try to subscribe - should fail with "Epoch already handled" + let erc721_dispatcher = ERC721ABIDispatcher { + contract_address: redeem_request.contract_address, + }; + cheat_caller_address(redeem_request.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(router.contract_address, old_nft_id); + cheat_caller_address(router.contract_address, USER1(), span: CheatSpan::TargetCalls(1)); + router.subscribe(old_nft_id, USER1()); // Should panic with "Epoch already handled" +} + #[test] fn test_redeem_and_subscribe_transfers_shares_and_subscribes() { let (vault, _, _, redeem_request, _, router) = set_up(); From 595896b808b14ac54ffca5f4af4adfc29aa39bcd Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 20:27:16 +0530 Subject: [PATCH 13/14] audit-fix: Wrong mapping used in epoch settlement calculation leading to potential inability to settle epochs --- .../vault/src/redemption_router/redemption_router.cairo | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index 8cf66a3e..6a9cf086 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -261,7 +261,7 @@ pub mod RedemptionRouter { // Calculate expected settled amount for an epoch fn _calculate_expected_settled(self: @ContractState, epoch: u256) -> u256 { - let epoch_nominal = self.epoch_redeem_nominal.read(epoch); + let epoch_nominal = self.epoch_wise_nominals.read(epoch); if (epoch_nominal == 0) { return 0; } @@ -465,8 +465,6 @@ pub mod RedemptionRouter { total_to } - // todo what if a subscription comes in after an epoch was handled but redeem not claimed? - // Settle epochs and/or sync settled epochs state // - If remaining_from > 0: settles epochs by allocating remaining_from to them // - After settling (or if remaining_from == 0): syncs by checking which epochs are fully settled @@ -500,7 +498,7 @@ pub mod RedemptionRouter { // Phase 1: Settle epochs with remaining_from (if any) while (remaining_from > 0 && current_epoch <= max_epoch) { - let epoch_nominal = self.epoch_redeem_nominal.read(current_epoch); + let epoch_nominal = self.epoch_wise_nominals.read(current_epoch); epochs_checked = epochs_checked + 1; // designed to prevent excessive gas usage @@ -569,7 +567,7 @@ pub mod RedemptionRouter { break; } - let epoch_nominal = self.epoch_redeem_nominal.read(current_epoch); + let epoch_nominal = self.epoch_wise_nominals.read(current_epoch); // Skip epochs without subscriptions if (epoch_nominal == 0) { From 74ade7d4ed003fe996dfde9cbe38283ece0fd9fe Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Wed, 4 Feb 2026 20:36:11 +0530 Subject: [PATCH 14/14] audit-fix: Unsubscribing doesnt decrease the accumulated nominal of the epoch --- packages/vault/src/redemption_router/interface.cairo | 1 + packages/vault/src/redemption_router/redemption_router.cairo | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/vault/src/redemption_router/interface.cairo b/packages/vault/src/redemption_router/interface.cairo index 9f16b5ae..e50fa218 100644 --- a/packages/vault/src/redemption_router/interface.cairo +++ b/packages/vault/src/redemption_router/interface.cairo @@ -10,6 +10,7 @@ pub struct RequestInfo { pub old_nft_id: u256, pub is_claimed: bool, pub epoch: u256, + pub nominal: u256, pub due_amount_approximate: u256, pub unsubscribed: bool, // if true, the original NFT has been unsubscribed } diff --git a/packages/vault/src/redemption_router/redemption_router.cairo b/packages/vault/src/redemption_router/redemption_router.cairo index 6a9cf086..260ef783 100644 --- a/packages/vault/src/redemption_router/redemption_router.cairo +++ b/packages/vault/src/redemption_router/redemption_router.cairo @@ -380,6 +380,7 @@ pub mod RedemptionRouter { old_nft_id: nft_id, is_claimed: false, epoch, + nominal: redeem_request_info.nominal, due_amount_approximate: due_amount, unsubscribed: false, }; @@ -716,6 +717,10 @@ pub mod RedemptionRouter { self._update_request_info(nft_id, updated); self.erc721.burn(nft_id); + // reduce the epoch_wise_nominals by the nominal of the NFT + let epoch_wise_nominals = self.epoch_wise_nominals.read(request_info.epoch); + self.epoch_wise_nominals.write(request_info.epoch, epoch_wise_nominals - request_info.nominal); + self.emit(Unsubscribed { new_nft_id: nft_id, old_nft_id: request_info.old_nft_id,