From c0ad7470ef507f22ba6b3165654f7b564456670a Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:45:04 +0100 Subject: [PATCH 01/54] Add leveraged loop staked ether test scenario - Add new test scenario for leveraged loop staked ether - Configure SLLSE fork in Scarb.toml for mainnet block 1794270 - Update lib.cairo to include new scenario module --- packages/vault_allocator/Scarb.toml | 7 + packages/vault_allocator/src/lib.cairo | 1 + .../leveraged_loop_staked_ether.cairo | 280 ++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo diff --git a/packages/vault_allocator/Scarb.toml b/packages/vault_allocator/Scarb.toml index 54f0bf30..de1d3c7f 100644 --- a/packages/vault_allocator/Scarb.toml +++ b/packages/vault_allocator/Scarb.toml @@ -29,6 +29,13 @@ name = "SSCL" url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" block_id.number = "1784627" +[[tool.snforge.fork]] +name = "SLLSE" +url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" +block_id.number = "1794270" + + + diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index f67eaee9..d85f6c81 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -75,6 +75,7 @@ pub mod test { } pub mod scenarios { + pub mod leveraged_loop_staked_ether; pub mod stable_carry_loop; } } diff --git a/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo b/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo new file mode 100644 index 00000000..d927b740 --- /dev/null +++ b/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. +use alexandria_math::i257::{I257Impl, I257Trait}; +use core::num::traits::Zero; +use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use snforge_std::{map_entry_address, store}; +use starknet::ContractAddress; +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ + Amount, AmountDenomination, AmountType, Route, UnsignedAmount, +}; +use vault_allocator::integration_interfaces::vesu::{ + IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, + ISingletonV2DispatcherTrait, +}; +use vault_allocator::manager::interface::IManagerDispatcherTrait; +use vault_allocator::middlewares::avnu_middleware::interface::{ + IAvnuMiddlewareDispatcher, IAvnuMiddlewareDispatcherTrait, +}; +use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, USDC, USDT, VESU_SINGLETON, wstETH}; +use vault_allocator::test::utils::{ + ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _add_vesu_flash_loan_leafs, + _add_vesu_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, + deploy_avnu_middleware, deploy_manager, deploy_price_router, + deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, generate_merkle_tree, + initialize_price_router, +}; +use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; + +#[fork("SLLSE")] +#[test] +fn test_leveraged_loop_staked_ether() { + let vault_allocator = deploy_vault_allocator(); + let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); + let price_router = deploy_price_router(); + initialize_price_router(price_router); + let avnu_middleware = deploy_avnu_middleware(price_router); + + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + let collateral_asset = wstETH(); + + _add_vesu_flash_loan_leafs( + ref leafs, + ref leaf_index, + vault_allocator.contract_address, + simple_decoder_and_sanitizer, + manager.contract_address, + collateral_asset, + false, + ); + + _add_vesu_leafs( + ref leafs, + ref leaf_index, + vault_allocator.contract_address, + simple_decoder_and_sanitizer, + GENESIS_POOL_ID, + array![wstETH()].span(), + array![array![ETH()].span()].span(), + ); + + _add_avnu_leafs( + ref leafs, + ref leaf_index, + vault_allocator.contract_address, + simple_decoder_and_sanitizer, + avnu_middleware, + array![(ETH(), wstETH())], + ); + + _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); + + let tree = generate_merkle_tree(leafs.span()); + let root = *tree.at(tree.len() - 1).at(0); + cheat_caller_address_once(vault_allocator.contract_address, OWNER()); + vault_allocator.set_manager(manager.contract_address); + + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(STRATEGIST(), root); + + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(manager.contract_address, root); + + // config + let collateral_asset = wstETH(); + let initial_collateral_balance: u256 = WAD; + let collateral_to_flash_loan: u256 = WAD; + let debt_asset = ETH(); + let allowed_slippage: u256 = 10; // 0.1% + let required_debt_amount_to_refund_flash_loan = 1208100829164930048 + + 1208100829164930048 * allowed_slippage / 10000; + + let mut cheat_calldata = ArrayTrait::new(); + initial_collateral_balance.serialize(ref cheat_calldata); + store( + collateral_asset, + map_entry_address( + selector!("ERC20_balances"), array![vault_allocator.contract_address.into()].span(), + ), + cheat_calldata.span(), + ); + + let underlying_disp = ERC20ABIDispatcher { contract_address: collateral_asset }; + assert( + underlying_disp.balance_of(vault_allocator.contract_address) == initial_collateral_balance, + 'wsteth balance is not correct', + ); + + let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); + array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); + + let mut array_of_targets = ArrayTrait::new(); + array_of_targets.append(manager.contract_address); + + let mut array_of_selectors = ArrayTrait::new(); + array_of_selectors.append(selector!("flash_loan")); + + let mut array_of_calldatas = ArrayTrait::new(); + let mut array_of_calldatas_flash_loan = ArrayTrait::new(); + manager.contract_address.serialize(ref array_of_calldatas_flash_loan); + collateral_asset.serialize(ref array_of_calldatas_flash_loan); + collateral_to_flash_loan.serialize(ref array_of_calldatas_flash_loan); + false.serialize(ref array_of_calldatas_flash_loan); + + let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); + flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); + flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); + flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); + flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); + + let mut flash_loan_data_target: Array = ArrayTrait::new(); + flash_loan_data_target.append(collateral_asset); + flash_loan_data_target.append(VESU_SINGLETON()); + flash_loan_data_target.append(debt_asset); + flash_loan_data_target.append(avnu_middleware); + + let mut flash_loan_data_selector: Array = ArrayTrait::new(); + flash_loan_data_selector.append(selector!("approve")); + flash_loan_data_selector.append(selector!("modify_position")); + flash_loan_data_selector.append(selector!("approve")); + flash_loan_data_selector.append(selector!("multi_route_swap")); + + let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); + + // approve wsteth initial collateral + flash loan amount + let mut flash_loan_data_calldata_approve = ArrayTrait::new(); + VESU_SINGLETON().serialize(ref flash_loan_data_calldata_approve); + (initial_collateral_balance + collateral_to_flash_loan) + .serialize(ref flash_loan_data_calldata_approve); + flash_loan_data_calldata.append(flash_loan_data_calldata_approve.span()); + + // modify position supplying wsteth initial collateral + flash loan amount and borrowing eth + // equivalent to refund flashloan + let mut flash_loan_data_calldata_modify_position = ArrayTrait::new(); + GENESIS_POOL_ID.serialize(ref flash_loan_data_calldata_modify_position); + wstETH().serialize(ref flash_loan_data_calldata_modify_position); + ETH().serialize(ref flash_loan_data_calldata_modify_position); + vault_allocator.contract_address.serialize(ref flash_loan_data_calldata_modify_position); + + let value_for_collateral_modify_position = I257Trait::new( + initial_collateral_balance + collateral_to_flash_loan, false, + ); + let collateral_modify_position: Amount = Amount { + amount_type: AmountType::Delta, + denomination: AmountDenomination::Assets, + value: value_for_collateral_modify_position, + }; + collateral_modify_position.serialize(ref flash_loan_data_calldata_modify_position); + + let value_for_debt_modify_position = I257Trait::new( + required_debt_amount_to_refund_flash_loan, false, + ); + let debt_modify_position: Amount = Amount { + amount_type: AmountType::Delta, + denomination: AmountDenomination::Assets, + value: value_for_debt_modify_position, + }; + debt_modify_position.serialize(ref flash_loan_data_calldata_modify_position); + + let data: Span = array![].span(); + data.serialize(ref flash_loan_data_calldata_modify_position); + + flash_loan_data_calldata.append(flash_loan_data_calldata_modify_position.span()); + + // approve debt asset to avnu middleware + let mut flash_loan_data_calldata_approve_debt_asset: Array = ArrayTrait::new(); + avnu_middleware.serialize(ref flash_loan_data_calldata_approve_debt_asset); + required_debt_amount_to_refund_flash_loan + .serialize(ref flash_loan_data_calldata_approve_debt_asset); + flash_loan_data_calldata.append(flash_loan_data_calldata_approve_debt_asset.span()); + + // multi route swap: sell debt asset to avnu middleware for collateral to refund flashloan + let mut array_of_calldata_multi_route_swap: Array = ArrayTrait::new(); + debt_asset.serialize(ref array_of_calldata_multi_route_swap); + required_debt_amount_to_refund_flash_loan.serialize(ref array_of_calldata_multi_route_swap); + collateral_asset.serialize(ref array_of_calldata_multi_route_swap); + // buy token amount is 0 because we are selling + let buy_token_amount: u256 = Zero::zero(); + buy_token_amount.serialize(ref array_of_calldata_multi_route_swap); + // buy_token_min_amount is set to 0 because we are protected by price router whatever + let buy_token_min_amount: u256 = Zero::zero(); + buy_token_min_amount.serialize(ref array_of_calldata_multi_route_swap); + vault_allocator.contract_address.serialize(ref array_of_calldata_multi_route_swap); + let integrator_fee_amount_bps: u128 = Zero::zero(); + integrator_fee_amount_bps.serialize(ref array_of_calldata_multi_route_swap); + let integrator_fee_recipient: ContractAddress = Zero::zero(); + integrator_fee_recipient.serialize(ref array_of_calldata_multi_route_swap); + + let mut routes: Array = ArrayTrait::new(); + // hardcode route to ekubo wsteth/eth + let mut additional_swap_params: Array = ArrayTrait::new(); + collateral_asset.serialize(ref additional_swap_params); + debt_asset.serialize(ref additional_swap_params); + + let fee: u128 = 0x68db8bac710cb4000000000000000; + fee.serialize(ref additional_swap_params); + let tick_spacing: u128 = 0xc8; + tick_spacing.serialize(ref additional_swap_params); + let extension: ContractAddress = Zero::zero(); + extension.serialize(ref additional_swap_params); + let sqrt_ratio_distance: felt252 = 0x290d5f61e20000000000000000000; + sqrt_ratio_distance.serialize(ref additional_swap_params); + + routes + .append( + Route { + sell_token: debt_asset, + buy_token: collateral_asset, + exchange_address: 0x5dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b + .try_into() + .unwrap(), // ekubo + percent: 1000000000000, + additional_swap_params, + }, + ); + + routes.serialize(ref array_of_calldata_multi_route_swap); + println!("array_of_calldata_multi_route_swap: {:?}", array_of_calldata_multi_route_swap); + + flash_loan_data_calldata.append(array_of_calldata_multi_route_swap.span()); + + let mut flash_loan_manager_leafs: Array = ArrayTrait::new(); + flash_loan_manager_leafs.append(leafs.at(6).clone()); + flash_loan_manager_leafs.append(leafs.at(9).clone()); + flash_loan_manager_leafs.append(leafs.at(10).clone()); + flash_loan_manager_leafs.append(leafs.at(11).clone()); + + let mut flash_loan_proofs = _get_proofs_using_tree(flash_loan_manager_leafs, tree.clone()); + + let mut serialized_flash_loan_data = ArrayTrait::new(); + ( + flash_loan_proofs.span(), + flash_loan_data_decoder_and_sanitizer.span(), + flash_loan_data_target.span(), + flash_loan_data_selector.span(), + flash_loan_data_calldata.span(), + ) + .serialize(ref serialized_flash_loan_data); + + serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); + array_of_calldatas.append(array_of_calldatas_flash_loan.span()); + + let mut manage_leafs: Array = ArrayTrait::new(); + manage_leafs.append(leafs.at(0).clone()); + + let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); + + cheat_caller_address_once(manager.contract_address, STRATEGIST()); + manager + .manage_vault_with_merkle_verification( + proofs.span(), + array_of_decoders_and_sanitizers.span(), + array_of_targets.span(), + array_of_selectors.span(), + array_of_calldatas.span(), + ); +} From f9f90a03807168a9506d441eb74596bfce222ce1 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 19 Aug 2025 22:27:24 +0100 Subject: [PATCH 02/54] Update OpenZeppelin to v3.0.0-alpha.1 and remove deprecated OZ 4626 implementation - Upgrade OpenZeppelin from v2.0.0 to v3.0.0-alpha.1 using git dependency - Remove custom oz_4626.cairo implementation (deprecated) - Update all package dependencies for consistency - Add pragma integration interface for price feeds - Refactor vault and allocator components to use new OZ interfaces - Update test scenarios and middleware implementations --- Scarb.lock | 86 +- Scarb.toml | 3 +- packages/vault/src/lib.cairo | 2 - packages/vault/src/oz_4626.cairo | 795 ------------------ .../src/redeem_request/redeem_request.cairo | 4 +- .../vault/src/test/units/redeem_request.cairo | 15 +- packages/vault/src/test/units/vault.cairo | 24 +- packages/vault/src/vault/vault.cairo | 48 +- packages/vault_allocator/Scarb.toml | 1 - .../src/integration_interfaces/pragma.cairo | 20 + packages/vault_allocator/src/lib.cairo | 1 + .../vault_allocator/src/manager/manager.cairo | 4 +- .../avnu_middleware/avnu_middleware.cairo | 3 +- .../vault_allocator/src/mocks/erc20.cairo | 2 +- .../vault_allocator/src/mocks/erc4626.cairo | 4 +- .../vault_allocator/src/mocks/flashloan.cairo | 2 +- .../periphery/price_router/interface.cairo | 3 +- .../periphery/price_router/price_router.cairo | 8 +- .../src/test/integrations/avnu.cairo | 2 +- .../src/test/integrations/vesu.cairo | 10 +- .../leveraged_loop_staked_ether.cairo | 14 +- .../test/scenarios/stable_carry_loop.cairo | 4 +- .../src/test/units/manager.cairo | 12 +- .../src/test/units/vault_allocator.cairo | 4 +- packages/vault_allocator/src/test/utils.cairo | 7 +- .../src/vault_allocator/vault_allocator.cairo | 2 +- 26 files changed, 155 insertions(+), 925 deletions(-) delete mode 100644 packages/vault/src/oz_4626.cairo create mode 100644 packages/vault_allocator/src/integration_interfaces/pragma.cairo diff --git a/Scarb.lock b/Scarb.lock index 95633666..b67ee2d0 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -9,14 +9,14 @@ checksum = "sha256:1e08ebba0ed9f7217b8efc283d2ad41730257cf41a47ca88a94fb0fafad22 [[package]] name = "openzeppelin" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:5e4fdecc957cfca7854d95912dc72d9f725517c063b116512900900add29fd77" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", "openzeppelin_finance", "openzeppelin_governance", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_merkle_tree", "openzeppelin_presets", @@ -28,67 +28,73 @@ dependencies = [ [[package]] name = "openzeppelin_access" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:511681dd26d814ee2bc996d44ff8cb4aaa5ae9d14272130def7eb901cf004850" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ + "openzeppelin_interfaces", "openzeppelin_introspection", ] [[package]] name = "openzeppelin_account" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:fb3381c50d68b028d3801feb43df378e2bd62137b6884844f8f60aefe796188b" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_utils", ] [[package]] name = "openzeppelin_finance" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:e9456ef69502a87c4c99bf50145351b50950f8b11244847d92935c466c4ba787" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", + "openzeppelin_interfaces", "openzeppelin_token", ] [[package]] name = "openzeppelin_governance" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:056e6d6f3d48193b53f06283884f8a9675f986fc85425f6a40e8c1aeb3b3ecfa" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_token", "openzeppelin_utils", ] +[[package]] +name = "openzeppelin_interfaces" +version = "2.1.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" + [[package]] name = "openzeppelin_introspection" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:87773ed6cd2318f169283ecbbb161890d1996260a80302d81ec45b70ee5e54c1" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +dependencies = [ + "openzeppelin_interfaces", +] [[package]] name = "openzeppelin_merkle_tree" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:47f80c9ce59557774243214f8e75c5e866f30f3d8daa755855f6ffd01c89ca89" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" [[package]] name = "openzeppelin_presets" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:36c761ee923f1dc0887c0eab8c224b49ac242dbfe9163fbb0b08562042ab3d98" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", "openzeppelin_finance", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_token", "openzeppelin_upgrades", @@ -97,40 +103,35 @@ dependencies = [ [[package]] name = "openzeppelin_security" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:902932ec296c2f400e0ac7c579edeaafd6067b6ce6d9854c1191de28e396ffe3" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +dependencies = [ + "openzeppelin_interfaces", +] [[package]] name = "openzeppelin_token" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:6fe61f63b5a6706018265fb7373b6e5bd3ff829bdc760b2b90296b1e708d180c" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_utils", ] [[package]] name = "openzeppelin_upgrades" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:560d57a9c3f3ec5a476e82fec8963c93c8df63a4ff9ff134f64ab8383bde3c61" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" [[package]] name = "openzeppelin_utils" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:bf799c794139837f397975ffdf6a7ed5032d198bbf70e87a8f44f144a9dfc505" - -[[package]] -name = "pragma_lib" -version = "2.11.4" -source = "git+https://github.com/astraly-labs/pragma-lib#edb55442d36565cbd99c226e38c4f8040efb774b" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ - "openzeppelin", + "openzeppelin_interfaces", ] [[package]] @@ -173,6 +174,5 @@ version = "0.1.0" dependencies = [ "alexandria_math", "openzeppelin", - "pragma_lib", "snforge_std", ] diff --git a/Scarb.toml b/Scarb.toml index bedef851..b4943bfe 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -28,10 +28,9 @@ keywords = [ [workspace.dependencies] starknet = "2.12.0" -openzeppelin = "2.0.0" +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts" } snforge_std = "0.48.0" alexandria_math = "0.6.0" -pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } [dependencies] diff --git a/packages/vault/src/lib.cairo b/packages/vault/src/lib.cairo index 31bd5d14..9b6d63a5 100644 --- a/packages/vault/src/lib.cairo +++ b/packages/vault/src/lib.cairo @@ -8,8 +8,6 @@ pub mod vault { pub mod vault; } -// waiting for openzeppelin to release vault with external assets -pub mod oz_4626; pub mod redeem_request { pub mod errors; diff --git a/packages/vault/src/oz_4626.cairo b/packages/vault/src/oz_4626.cairo deleted file mode 100644 index 0ce5d83f..00000000 --- a/packages/vault/src/oz_4626.cairo +++ /dev/null @@ -1,795 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v3.0.0-alpha.0 -// (token/src/erc20/extensions/erc4626/erc4626.cairo) - -/// # ERC4626 Component -/// -/// The ERC4626 component is an extension of ERC20 and provides an implementation of the IERC4626 -/// interface which allows the minting and burning of "shares" in exchange for an underlying -/// "asset". The component leverages traits to configure fees, limits, and decimals. -/// -/// CAUTION: In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen -/// through frontrunning with a "donation" to the vault that inflates the price of a share. This is -/// variously known as a donation or inflation attack and is essentially a problem of slippage. -/// Vault deployers can protect against this attack by making an initial deposit of a non-trivial -/// amount of the asset, such that price manipulation becomes infeasible. Withdrawals may similarly -/// be affected by slippage. Users can protect against this attack as well as unexpected slippage in -/// general by verifying the amount received is as expected, using a wrapper that performs these -/// checks. -/// -/// This implementation offers configurable virtual assets and shares to help developers mitigate -/// that risk. `ImmutableConfig::DECIMALS_OFFSET` corresponds to an offset in the decimal -/// representation between the underlying asset's decimals and vault decimals. This offset also -/// determines the rate of virtual shares to virtual assets in the vault, which itself determines -/// the initial exchange rate. While not fully preventing the attack, analysis shows that the -/// default offset (0) makes it non-profitable even if an attacker is able to capture value from -/// multiple user deposits, as a result of the value being captured by the virtual shares (out of -/// the attacker's donation) matching the attacker's expected gains. With a larger offset, the -/// attack becomes orders of magnitude more expensive than it is profitable. -/// -/// The drawback of this approach is that the virtual shares do capture (a very small) part of the -/// value being accrued to the vault. Also, if the vault experiences losses and users try to exit -/// the vault, the virtual shares and assets will cause the first exiting user to experience reduced -/// losses to the detriment to the last users who will experience bigger losses. -#[starknet::component] -pub mod ERC4626Component { - use core::num::traits::{Bounded, Pow, Zero}; - use openzeppelin::token::erc20::ERC20Component; - use openzeppelin::token::erc20::ERC20Component::InternalImpl as ERC20InternalImpl; - use openzeppelin::token::erc20::extensions::erc4626::interface::IERC4626; - use openzeppelin::token::erc20::interface::{IERC20, IERC20Metadata}; - use openzeppelin::utils::math; - use openzeppelin::utils::math::Rounding; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - // The default values are only used when the DefaultConfig - // is in scope in the implementing contract. - pub const DEFAULT_UNDERLYING_DECIMALS: u8 = 18; - pub const DEFAULT_DECIMALS_OFFSET: u8 = 0; - - #[storage] - pub struct Storage { - pub ERC4626_asset: ContractAddress, - } - - #[event] - #[derive(Drop, PartialEq, starknet::Event)] - pub enum Event { - Deposit: Deposit, - Withdraw: Withdraw, - } - - /// Emitted when `sender` exchanges `assets` for `shares` and transfers those - /// `shares` to `owner`. - #[derive(Drop, PartialEq, starknet::Event)] - pub struct Deposit { - #[key] - pub sender: ContractAddress, - #[key] - pub owner: ContractAddress, - pub assets: u256, - pub shares: u256, - } - - /// Emitted when `sender` exchanges `shares`, owned by `owner`, for `assets` and transfers - /// those `assets` to `receiver`. - #[derive(Drop, PartialEq, starknet::Event)] - pub struct Withdraw { - #[key] - pub sender: ContractAddress, - #[key] - pub receiver: ContractAddress, - #[key] - pub owner: ContractAddress, - pub assets: u256, - pub shares: u256, - } - - pub mod Errors { - pub const EXCEEDED_MAX_DEPOSIT: felt252 = 'ERC4626: exceeds max deposit'; - pub const EXCEEDED_MAX_MINT: felt252 = 'ERC4626: exceeds max mint'; - pub const EXCEEDED_MAX_WITHDRAW: felt252 = 'ERC4626: exceeds max withdraw'; - pub const EXCEEDED_MAX_REDEEM: felt252 = 'ERC4626: exceeds max redeem'; - pub const TOKEN_TRANSFER_FAILED: felt252 = 'ERC4626: token transfer failed'; - pub const INVALID_ASSET_ADDRESS: felt252 = 'ERC4626: asset address set to 0'; - pub const DECIMALS_OVERFLOW: felt252 = 'ERC4626: decimals overflow'; - } - - /// Constants expected to be defined at the contract level which configure virtual - /// assets and shares. - /// - /// `UNDERLYING_DECIMALS` should match the underlying asset's decimals. The default - /// value is `18`. - /// - /// `DECIMALS_OFFSET` corresponds to the representational offset between `UNDERLYING_DECIMALS` - /// and the vault decimals. The greater the offset, the more expensive it is for attackers to - /// execute an inflation attack. - /// - /// Requirements: - /// - /// - `UNDERLYING_DECIMALS` + `DECIMALS_OFFSET` cannot exceed 255 (max u8). - pub trait ImmutableConfig { - const UNDERLYING_DECIMALS: u8; - const DECIMALS_OFFSET: u8; - - fn validate() { - assert( - Bounded::MAX - Self::UNDERLYING_DECIMALS >= Self::DECIMALS_OFFSET, - Errors::DECIMALS_OVERFLOW, - ) - } - } - - /// Adjustments for fees expected to be defined at the contract level. - /// Defaults to no entry or exit fees. - /// - /// NOTE: The FeeConfigTrait hooks directly into the preview methods of the ERC4626 component. - /// The preview methods must return as close to the exact amount of shares or assets as possible - /// if the actual (previewed) operation occurred in the same transaction (according to EIP-4626 - /// spec). - /// All operations use their corresponding preview method as the value of assets or shares being - /// moved. - /// Therefore, adjusting an operation's assets in FeeConfigTrait consequently adjusts the assets - /// (or assets to be converted into shares) in both the preview operation and the actual - /// operation. - /// - /// NOTE: To transfer fees, this trait needs to be coordinated with - /// `ERC4626Component::ERC4626Hooks`. - /// - /// See the ERC4626FeesMock example: - /// https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo - pub trait FeeConfigTrait> { - /// Adjusts deposits within `preview_deposit` to account for entry fees. - /// Entry fees should be transferred in the `after_deposit` hook. - fn adjust_deposit(self: @ComponentState, assets: u256) -> u256 { - assets - } - - /// Adjusts mints within `preview_mint` to account for entry fees. - /// Entry fees should be transferred in the `after_deposit` hook. - fn adjust_mint(self: @ComponentState, assets: u256) -> u256 { - assets - } - - /// Adjusts withdraws within `preview_withdraw` to account for exit fees. - /// Exit fees should be transferred in the `before_withdraw` hook. - fn adjust_withdraw(self: @ComponentState, assets: u256) -> u256 { - assets - } - - /// Adjusts redeems within `preview_redeem` to account for exit fees. - /// Exit fees should be transferred in the `before_withdraw` hook. - fn adjust_redeem(self: @ComponentState, assets: u256) -> u256 { - assets - } - } - - /// Sets limits to the target exchange type and is expected to be defined at the contract - /// level. - /// - /// It's important to note that these limits correspond directly to the `max_` - /// i.e. `deposit_limit` -> `max_deposit`. - /// - /// The EIP-4626 spec states that the `max_` methods must take into account all - /// global and user-specific limits. - /// If an operation is disabled (even temporarily), the corresponding limit MUST be `0` - /// and MUST NOT panic. - pub trait LimitConfigTrait> { - /// The max deposit allowed. - /// Defaults (`Option::None`) to 2 ** 256 - 1. - fn deposit_limit( - self: @ComponentState, receiver: ContractAddress, - ) -> Option { - Option::None - } - - /// The max mint allowed. - /// Defaults (`Option::None`) to 2 ** 256 - 1. - fn mint_limit( - self: @ComponentState, receiver: ContractAddress, - ) -> Option { - Option::None - } - - /// The max withdraw allowed. - /// Defaults (`Option::None`) to the full asset balance of `owner` converted from shares. - fn withdraw_limit( - self: @ComponentState, owner: ContractAddress, - ) -> Option { - Option::None - } - - /// The max redeem allowed. - /// Defaults (`Option::None`) to the full asset balance of `owner`. - fn redeem_limit( - self: @ComponentState, owner: ContractAddress, - ) -> Option { - Option::None - } - } - - /// Allows contracts to hook logic into deposit and withdraw transactions. - /// This is where contracts can transfer fees. - /// - /// NOTE: ERC4626 preview methods must be inclusive of any entry or exit fees. - /// The `AdjustFeesTrait` will adjust these values accordingly; therefore, - /// fees must be set in the `AdjustFeesTrait` if the using contract enforces - /// entry or exit fees. - /// - /// CAUTION: Special care must be taken when calling external contracts in these hooks. In - /// that case, consider implementing reentrancy protections. For example, in the - /// `withdraw` flow, the `withdraw_limit` is checked *before* the `before_withdraw` hook - /// is invoked. If this hook performs a reentrant call that invokes `withdraw` again, the - /// subsequent check on `withdraw_limit` will be done before the first withdrawal’s core logic - /// (e.g., burning shares and transferring assets) is executed. This could - /// lead to bypassing withdrawal constraints or draining funds. - /// - /// See the example: - /// https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo - pub trait ERC4626HooksTrait> { - /// Hooks into `InternalImpl::_withdraw`. - /// Executes logic before burning shares and transferring assets. - fn before_withdraw(ref self: ComponentState, assets: u256, shares: u256) {} - /// Hooks into `InternalImpl::_withdraw`. - /// Executes logic after burning shares and transferring assets. - fn after_withdraw(ref self: ComponentState, assets: u256, shares: u256) {} - /// Hooks into `InternalImpl::_deposit`. - /// Executes logic before transferring assets and minting shares. - fn before_deposit(ref self: ComponentState, assets: u256, shares: u256) {} - /// Hooks into `InternalImpl::_deposit`. - /// Executes logic after transferring assets and minting shares. - fn after_deposit(ref self: ComponentState, assets: u256, shares: u256) {} - } - - /// Defines how the ERC4626 vault manages its underlying assets. This trait provides the core - /// asset management functionality for the vault, abstracting the actual storage and transfer - /// mechanisms. - /// It enables two primary implementation patterns: - /// - /// 1. **Self-managed assets**: The vault contract holds assets directly on its own address. - /// This is the default behavior provided by `ERC4626SelfAssetsManagement` implementation. - /// - /// 2. **External vault**: Assets are managed by an external contract, allowing - /// for more complex asset management strategies. The exact implementation is expected to be - /// defined by the contract implementing the ERC4626 component. - /// - /// The trait methods are called during deposit, withdrawal, and total assets calculations, - /// ensuring that the vault's share pricing remains accurate regardless of the underlying - /// asset management strategy. - /// - /// CAUTION: Implementations must ensure that `get_total_assets` returns the actual amount - /// of assets that can be withdrawn by users. Inaccurate reporting can lead to incorrect - /// share valuations and potential economic attacks. - /// - /// See the examples: - /// - Self-managed vault: `ERC4626SelfAssetsManagement` at the end of the file. - /// - External vault: `ERC4626ExternalAssetsManagement` in `ERC4626ExternalVaultMock`. - pub trait AssetsManagementTrait> { - /// Returns the total amount of underlying assets under the vault's management. - /// Used for share price calculations and determining the vault's total value. - /// - /// This method should return the actual amount of assets that the vault controls - /// and that can be used to satisfy withdrawal requests. For self-managed vaults, - /// this is typically the vault contract's token balance. For external vaults, - /// this should include any assets deposited in external protocols, minus any - /// that are locked or unredeemable. - /// - /// The accuracy of this method is critical for proper vault operation: - /// - Overreporting can lead to share dilution and user losses. - /// - Underreporting can lead to share inflation and potential economic attacks. - fn get_total_assets(self: @ComponentState) -> u256; - - /// Transfers assets from an external address into the vault's management. - /// Called during `deposit` and `mint` operations. - /// - /// This method should handle the actual transfer of underlying assets from the `from` - /// address into the vault's control. For self-managed vaults, this typically means - /// transferring tokens to the vault contract's address. For external vaults, this - /// might involve transferring into an external contract. - /// - /// Requirements: - /// - /// - Must transfer exactly `assets` amount of the underlying token. - /// - Should revert if the transfer fails or insufficient allowance/balance. - fn transfer_assets_in( - ref self: ComponentState, from: ContractAddress, assets: u256, - ); - - /// Transfers assets from the vault's management to an external address. - /// Called during withdraw and redeem operations. - /// - /// This method should handle the actual transfer of underlying assets from the vault's - /// control to the `to` address. For self-managed vaults, this typically means - /// transferring tokens from the vault contract's address. For external vaults, this - /// might involve withdrawing from an external contract first. - /// - /// Requirements: - /// - /// - Must transfer exactly `assets` amount of the underlying token. - /// - Should revert if insufficient assets are available or transfer fails. - fn transfer_assets_out( - ref self: ComponentState, to: ContractAddress, assets: u256, - ); - } - - // - // External - // - - #[embeddable_as(ERC4626Impl)] - impl ERC4626< - TContractState, - +HasComponent, - impl Fee: FeeConfigTrait, - impl Limit: LimitConfigTrait, - impl Hooks: ERC4626HooksTrait, - impl Immutable: ImmutableConfig, - impl ERC20: ERC20Component::HasComponent, - impl Assets: AssetsManagementTrait, - +ERC20Component::ERC20HooksTrait, - +Drop, - > of IERC4626> { - /// Returns the address of the underlying token used for the Vault for accounting, - /// depositing, and withdrawing. - fn asset(self: @ComponentState) -> ContractAddress { - self.ERC4626_asset.read() - } - - /// Returns the total amount of the underlying asset that is “managed” by Vault. - fn total_assets(self: @ComponentState) -> u256 { - Assets::get_total_assets(self) - } - - /// Returns the amount of shares that the Vault would exchange for the amount of assets - /// provided irrespective of slippage or fees. - /// - /// NOTE: As per the EIP-4626 spec, this may panic _only_ if there's an overflow - /// from an unreasonably large input. - fn convert_to_shares(self: @ComponentState, assets: u256) -> u256 { - self._convert_to_shares(assets, Rounding::Floor) - } - - /// Returns the amount of assets that the Vault would exchange for the amount of shares - /// provided irrespective of slippage or fees. - /// - /// NOTE: As per the EIP-4626 spec, this may panic _only_ if there's an overflow - /// from an unreasonably large input. - fn convert_to_assets(self: @ComponentState, shares: u256) -> u256 { - self._convert_to_assets(shares, Rounding::Floor) - } - - /// Returns the maximum amount of the underlying asset that can be deposited into the Vault - /// for the receiver, through a deposit call. - /// - /// The default max deposit value is 2 ** 256 - 1. - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::deposit_limit`. - fn max_deposit(self: @ComponentState, receiver: ContractAddress) -> u256 { - Limit::deposit_limit(self, receiver).unwrap_or(Bounded::MAX) - } - - /// Allows an on-chain or off-chain user to simulate the effects of their deposit at the - /// current block, given current on-chain conditions. - /// - /// The default deposit preview value is the full amount of shares. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_deposit`. - /// - /// NOTE: `preview_deposit` must be inclusive of entry fees to be compliant with the - /// EIP-4626 spec. - fn preview_deposit(self: @ComponentState, assets: u256) -> u256 { - let adjusted_assets = Fee::adjust_deposit(self, assets); - self._convert_to_shares(adjusted_assets, Rounding::Floor) - } - - /// Mints Vault shares to `receiver` by depositing exactly `assets` of underlying tokens. - /// Returns the amount of newly-minted shares. - /// - /// Requirements: - /// - /// - `assets` is less than or equal to the max deposit amount for `receiver`. - /// - /// Emits a `Deposit` event. - fn deposit( - ref self: ComponentState, assets: u256, receiver: ContractAddress, - ) -> u256 { - let max_assets = self.max_deposit(receiver); - assert(assets <= max_assets, Errors::EXCEEDED_MAX_DEPOSIT); - - let shares = self.preview_deposit(assets); - let caller = starknet::get_caller_address(); - self._deposit(caller, receiver, assets, shares); - - shares - } - - /// Returns the maximum amount of the Vault shares that can be minted for `receiver` through - /// a `mint` call. - /// - /// The default max mint value is 2 ** 256 - 1. - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::mint_limit`. - fn max_mint(self: @ComponentState, receiver: ContractAddress) -> u256 { - Limit::mint_limit(self, receiver).unwrap_or(Bounded::MAX) - } - - /// Allows an on-chain or off-chain user to simulate the effects of their mint at the - /// current block, given current on-chain conditions. - /// - /// The default mint preview value is the full amount of assets. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_mint`. - /// - /// NOTE: `preview_mint` must be inclusive of entry fees to be compliant with the EIP-4626 - /// spec. - fn preview_mint(self: @ComponentState, shares: u256) -> u256 { - let full_assets = self._convert_to_assets(shares, Rounding::Ceil); - Fee::adjust_mint(self, full_assets) - } - - /// Mints exactly Vault `shares` to `receiver` by depositing amount of underlying tokens. - /// Returns the amount deposited assets. - /// - /// Requirements: - /// - /// - `shares` is less than or equal to the max shares amount for `receiver`. - /// - /// Emits a `Deposit` event. - fn mint( - ref self: ComponentState, shares: u256, receiver: ContractAddress, - ) -> u256 { - let max_shares = self.max_mint(receiver); - assert(shares <= max_shares, Errors::EXCEEDED_MAX_MINT); - - let assets = self.preview_mint(shares); - let caller = starknet::get_caller_address(); - self._deposit(caller, receiver, assets, shares); - - assets - } - - /// Returns the maximum amount of the underlying asset that can be withdrawn from the owner - /// balance in the Vault, through a `withdraw` call. - /// - /// The default max withdraw value is the full balance of assets for `owner` (converted from - /// shares). - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::withdraw_limit`. - /// Do note that with customized limits, the maximum withdraw amount will either be - /// the custom limit itself or ``owner``'s total asset balance, whichever value is less. - fn max_withdraw(self: @ComponentState, owner: ContractAddress) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let owner_shares = erc20_component.balance_of(owner); - let total_owner_assets = self._convert_to_assets(owner_shares, Rounding::Floor); - - match Limit::withdraw_limit(self, owner) { - Option::Some(limit) => { - if total_owner_assets < limit { - total_owner_assets - } else { - limit - } - }, - Option::None => { total_owner_assets }, - } - } - - /// Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the - /// current block, given current on-chain conditions. - /// - /// The default withdraw preview value is the full amount of shares. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_withdraw`. - /// - /// NOTE: `preview_withdraw` must be inclusive of exit fees to be compliant with the - /// EIP-4626 spec. - fn preview_withdraw(self: @ComponentState, assets: u256) -> u256 { - let adjusted_assets = Fee::adjust_withdraw(self, assets); - self._convert_to_shares(adjusted_assets, Rounding::Ceil) - } - - /// Burns shares from `owner` and sends exactly `assets` of underlying tokens to `receiver`. - /// - /// Requirements: - /// - /// - `assets` is less than or equal to the max withdraw amount of `owner`. - /// - /// Emits a `Withdraw` event. - fn withdraw( - ref self: ComponentState, - assets: u256, - receiver: ContractAddress, - owner: ContractAddress, - ) -> u256 { - let max_assets = self.max_withdraw(owner); - assert(assets <= max_assets, Errors::EXCEEDED_MAX_WITHDRAW); - - let shares = self.preview_withdraw(assets); - let caller = starknet::get_caller_address(); - self._withdraw(caller, receiver, owner, assets, shares); - - shares - } - - /// Returns the maximum amount of Vault shares that can be redeemed from the owner balance - /// in the Vault, through a `redeem` call. - /// - /// The default max redeem value is the full balance of assets for `owner`. - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::redeem_limit`. - /// Do note that with customized limits, the maximum redeem amount will either be - /// the custom limit itself or ``owner``'s total asset balance, whichever value is less. - fn max_redeem(self: @ComponentState, owner: ContractAddress) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let owner_shares = erc20_component.balance_of(owner); - - match Limit::redeem_limit(self, owner) { - Option::Some(limit) => { if owner_shares < limit { - owner_shares - } else { - limit - } }, - Option::None => { owner_shares }, - } - } - - /// Allows an on-chain or off-chain user to simulate the effects of their redemption at the - /// current block, given current on-chain conditions. - /// - /// The default redeem preview value is the full amount of assets. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_redeem`. - /// - /// NOTE: `preview_redeem` must be inclusive of exit fees to be compliant with the EIP-4626 - /// spec. - fn preview_redeem(self: @ComponentState, shares: u256) -> u256 { - let full_assets = self._convert_to_assets(shares, Rounding::Floor); - Fee::adjust_redeem(self, full_assets) - } - - /// Burns exactly `shares` from `owner` and sends assets of underlying tokens to `receiver`. - /// - /// Requirements: - /// - /// - `shares` is less than or equal to the max redeem amount of `owner`. - /// - /// Emits a `Withdraw` event. - fn redeem( - ref self: ComponentState, - shares: u256, - receiver: ContractAddress, - owner: ContractAddress, - ) -> u256 { - let max_shares = self.max_redeem(owner); - assert(shares <= max_shares, Errors::EXCEEDED_MAX_REDEEM); - - let assets = self.preview_redeem(shares); - let caller = starknet::get_caller_address(); - self._withdraw(caller, receiver, owner, assets, shares); - - assets - } - } - - #[embeddable_as(ERC4626MetadataImpl)] - impl ERC4626Metadata< - TContractState, - +HasComponent, - impl Immutable: ImmutableConfig, - impl ERC20: ERC20Component::HasComponent, - > of IERC20Metadata> { - /// Returns the name of the token. - fn name(self: @ComponentState) -> ByteArray { - let erc20_component = get_dep_component!(self, ERC20); - erc20_component.ERC20_name.read() - } - - /// Returns the ticker symbol of the token, usually a shorter version of the name. - fn symbol(self: @ComponentState) -> ByteArray { - let erc20_component = get_dep_component!(self, ERC20); - erc20_component.ERC20_symbol.read() - } - - /// Returns the cumulative number of decimals which includes both the underlying and offset - /// decimals. - /// Both of which must be defined in the `ImmutableConfig` inside the implementing contract. - fn decimals(self: @ComponentState) -> u8 { - Immutable::UNDERLYING_DECIMALS + Immutable::DECIMALS_OFFSET - } - } - - // - // Internal - // - - #[generate_trait] - pub impl InternalImpl< - TContractState, - +HasComponent, - impl Hooks: ERC4626HooksTrait, - impl Immutable: ImmutableConfig, - impl ERC20: ERC20Component::HasComponent, - impl Assets: AssetsManagementTrait, - +FeeConfigTrait, - +LimitConfigTrait, - +ERC20Component::ERC20HooksTrait, - +Drop, - > of InternalTrait { - /// Validates the `ImmutableConfig` constants and sets the `asset_address` to the vault. - /// This should be set in the contract's constructor. - /// - /// Requirements: - /// - /// - `asset_address` cannot be the zero address. - fn initializer(ref self: ComponentState, asset_address: ContractAddress) { - Immutable::validate(); - assert(asset_address.is_non_zero(), Errors::INVALID_ASSET_ADDRESS); - self.ERC4626_asset.write(asset_address); - } - - /// Internal logic for `deposit` and `mint`. - /// Transfers `assets` from `caller` to the Vault contract then mints `shares` to - /// `receiver`. - /// Fees can be transferred in the `ERC4626Hooks::after_deposit` hook which is executed - /// after assets are transferred and shares are minted. - /// - /// Requirements: - /// - /// - `ERC20::transfer_from` must return true. - /// - /// Emits two `ERC20::Transfer` events (`ERC20::mint` and `ERC20::transfer_from`). - /// Emits a `Deposit` event. - fn _deposit( - ref self: ComponentState, - caller: ContractAddress, - receiver: ContractAddress, - assets: u256, - shares: u256, - ) { - // Before deposit hook - Hooks::before_deposit(ref self, assets, shares); - - // Transfer assets first - Assets::transfer_assets_in(ref self, caller, assets); - - // Mint shares after transferring assets - let mut erc20_component = get_dep_component_mut!(ref self, ERC20); - erc20_component.mint(receiver, shares); - self.emit(Deposit { sender: caller, owner: receiver, assets, shares }); - - // After deposit hook - Hooks::after_deposit(ref self, assets, shares); - } - - /// Internal logic for `withdraw` and `redeem`. - /// Burns `shares` from `owner` and then transfers `assets` to `receiver`. - /// Fees can be transferred in the `ERC4626Hooks::before_withdraw` hook which is executed - /// before shares are burned and assets are transferred. - /// - /// Requirements: - /// - /// - `ERC20::transfer` must return true. - /// - /// Emits two `ERC20::Transfer` events (`ERC20::burn` and `ERC20::transfer`). - /// - /// Emits a `Withdraw` event. - fn _withdraw( - ref self: ComponentState, - caller: ContractAddress, - receiver: ContractAddress, - owner: ContractAddress, - assets: u256, - shares: u256, - ) { - // Before withdraw hook - Hooks::before_withdraw(ref self, assets, shares); - - // Burn shares first - let mut erc20_component = get_dep_component_mut!(ref self, ERC20); - if caller != owner { - erc20_component._spend_allowance(owner, caller, shares); - } - erc20_component.burn(owner, shares); - - // Transfer assets after burn - Assets::transfer_assets_out(ref self, receiver, assets); - - self.emit(Withdraw { sender: caller, receiver, owner, assets, shares }); - - // After withdraw hook - Hooks::after_withdraw(ref self, assets, shares); - } - - /// Internal conversion function (from assets to shares) with support for `rounding` - /// direction. - fn _convert_to_shares( - self: @ComponentState, assets: u256, rounding: Rounding, - ) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let total_shares = erc20_component.total_supply(); - - math::u256_mul_div( - assets, - total_shares + 10_u256.pow(Immutable::DECIMALS_OFFSET.into()), - self.total_assets() + 1, - rounding, - ) - } - - /// Internal conversion function (from shares to assets) with support for `rounding` - /// direction. - fn _convert_to_assets( - self: @ComponentState, shares: u256, rounding: Rounding, - ) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let total_shares = erc20_component.total_supply(); - - math::u256_mul_div( - shares, - self.total_assets() + 1, - total_shares + 10_u256.pow(Immutable::DECIMALS_OFFSET.into()), - rounding, - ) - } - } -} - -// -// Default (empty) traits -// - -use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; -use starknet::ContractAddress; -use starknet::storage::StoragePointerReadAccess; - -pub impl ERC4626HooksEmptyImpl< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::ERC4626HooksTrait {} - -pub impl ERC4626DefaultNoFees< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::FeeConfigTrait {} - -pub impl ERC4626DefaultLimits< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::LimitConfigTrait {} - -pub impl ERC4626SelfAssetsManagement< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::AssetsManagementTrait { - fn get_total_assets(self: @ERC4626Component::ComponentState) -> u256 { - let this = starknet::get_contract_address(); - let asset_dispatcher = IERC20Dispatcher { contract_address: self.ERC4626_asset.read() }; - asset_dispatcher.balance_of(this) - } - - fn transfer_assets_in( - ref self: ERC4626Component::ComponentState, - from: ContractAddress, - assets: u256, - ) { - let this = starknet::get_contract_address(); - let asset_dispatcher = IERC20Dispatcher { contract_address: self.ERC4626_asset.read() }; - assert( - asset_dispatcher.transfer_from(from, this, assets), - ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, - ); - } - - fn transfer_assets_out( - ref self: ERC4626Component::ComponentState, - to: ContractAddress, - assets: u256, - ) { - let asset_dispatcher = IERC20Dispatcher { contract_address: self.ERC4626_asset.read() }; - assert( - asset_dispatcher.transfer(to, assets), ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, - ); - } -} - -pub impl DefaultConfig of ERC4626Component::ImmutableConfig { - const UNDERLYING_DECIMALS: u8 = ERC4626Component::DEFAULT_UNDERLYING_DECIMALS; - const DECIMALS_OFFSET: u8 = ERC4626Component::DEFAULT_DECIMALS_OFFSET; -} diff --git a/packages/vault/src/redeem_request/redeem_request.cairo b/packages/vault/src/redeem_request/redeem_request.cairo index 43c8fa17..a30d2bab 100644 --- a/packages/vault/src/redeem_request/redeem_request.cairo +++ b/packages/vault/src/redeem_request/redeem_request.cairo @@ -4,12 +4,12 @@ #[starknet::contract] mod RedeemRequest { - use openzeppelin::access::accesscontrol::interface::{ + use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; - use openzeppelin::upgrades::interface::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, diff --git a/packages/vault/src/test/units/redeem_request.cairo b/packages/vault/src/test/units/redeem_request.cairo index c167c338..01e11b49 100644 --- a/packages/vault/src/test/units/redeem_request.cairo +++ b/packages/vault/src/test/units/redeem_request.cairo @@ -2,13 +2,12 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::access::accesscontrol::interface::{ +use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; -use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; -use snforge_std::{EventSpyAssertionsTrait, spy_events}; -use starknet::{ContractAddress, contract_address_const, get_caller_address}; +use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use starknet::ContractAddress; use vault::redeem_request::interface::{ IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait, RedeemRequestInfo, }; @@ -63,7 +62,7 @@ fn test_id_to_info_nonexistent() { #[test] #[should_panic(expected: "Caller is not vault")] fn test_mint_not_vault() { - let (vault_address, redeem_request) = set_up(); + let (_, redeem_request) = set_up(); let redeem_info = RedeemRequestInfo { epoch: 1, nominal: 100 }; redeem_request.mint(DUMMY_ADDRESS(), redeem_info); } @@ -212,7 +211,7 @@ fn test_burn_one_of_multiple() { #[test] #[should_panic(expected: "Caller is not vault owner")] fn test_upgrade_not_vault_owner() { - let (vault_address, redeem_request) = set_up(); + let (_, redeem_request) = set_up(); let (_, counter_class_hash) = deploy_counter(); IUpgradeableDispatcher { contract_address: redeem_request.contract_address } @@ -221,7 +220,7 @@ fn test_upgrade_not_vault_owner() { #[test] fn test_upgrade_success() { - let (vault_address, redeem_request) = set_up(); + let (_, redeem_request) = set_up(); let (_, counter_class_hash) = deploy_counter(); cheat_caller_address_once(redeem_request.contract_address, OWNER()); diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index f1b83278..b0052e9e 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -3,19 +3,17 @@ // Licensed under the MIT License. See LICENSE file for details. use core::num::traits::Zero; -use openzeppelin::access::accesscontrol::interface::{ +use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; -use openzeppelin::security::interface::{IPausableDispatcher, IPausableDispatcherTrait}; -use openzeppelin::token::erc20::extensions::erc4626::interface::{ - IERC4626Dispatcher, IERC4626DispatcherTrait, -}; -use openzeppelin::token::erc20::interface::{ +use openzeppelin::interfaces::erc20::{ ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20MetadataDispatcher, IERC20MetadataDispatcherTrait, }; -use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; +use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; +use openzeppelin::interfaces::security::pausable::{IPausableDispatcher, IPausableDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use openzeppelin::utils::math; use openzeppelin::utils::math::Rounding; use snforge_std::{ @@ -26,9 +24,9 @@ use starknet::{ContractAddress, get_block_timestamp}; use vault::redeem_request::interface::{IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait}; use vault::test::utils::{ DUMMY_ADDRESS, FEES_RECIPIENT, MANAGEMENT_FEES, MAX_DELTA, ORACLE, OTHER_DUMMY_ADDRESS, OWNER, - PAUSER, PERFORMANCE_FEES, REDEEM_FEES, REPORT_DELAY, USER1, USER2, VAULT_ALLOCATOR, VAULT_NAME, - VAULT_SYMBOL, between, cheat_caller_address_once, deploy_counter, deploy_erc20_mock, - deploy_redeem_request, deploy_vault, + PERFORMANCE_FEES, REDEEM_FEES, REPORT_DELAY, VAULT_ALLOCATOR, VAULT_NAME, VAULT_SYMBOL, between, + cheat_caller_address_once, deploy_counter, deploy_erc20_mock, deploy_redeem_request, + deploy_vault, }; use vault::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; use vault::vault::vault::Vault; @@ -1418,7 +1416,7 @@ fn test_report_when_vault_allocator_is_not_set() { let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); - let shares = erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); let mut cheat_calldata_vault_allocator = ArrayTrait::new(); cheat_calldata_vault_allocator.append(0); @@ -1454,7 +1452,7 @@ fn test_report_when_fees_recipient_is_not_set() { let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); - let shares = erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); let mut cheat_calldata_fees_recipient = ArrayTrait::new(); cheat_calldata_fees_recipient.append(0); diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index d0b95a89..2e4fbc80 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -21,16 +21,20 @@ pub mod Vault { use core::num::traits::Zero; use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::interfaces::erc20::{ + ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata, + }; + use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::security::pausable::PausableComponent; - use openzeppelin::token::erc20::interface::{ - ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata, + use openzeppelin::token::erc20::extensions::erc4626::ERC4626Component::Fee; + use openzeppelin::token::erc20::extensions::erc4626::{ + DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, ERC4626DefaultNoLimits, }; use openzeppelin::token::erc20::{ DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, }; - use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; - use openzeppelin::upgrades::interface::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use openzeppelin::utils::math; use openzeppelin::utils::math::Rounding; @@ -39,9 +43,6 @@ pub mod Vault { StoragePointerWriteAccess, }; use starknet::{ContractAddress, get_block_timestamp, get_caller_address}; - use vault::oz_4626::{ - DefaultConfig, ERC4626Component, ERC4626DefaultLimits, ERC4626DefaultNoFees, - }; use vault::redeem_request::interface::{ IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait, RedeemRequestInfo, }; @@ -308,30 +309,55 @@ pub mod Vault { /// Hook executed before burning shares and transferring assets during withdrawal /// Direct withdrawals are not supported - users must use epoched redemption system fn before_withdraw( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) { Errors::not_implemented(); // Withdrawals disabled - use request_redeem instead } + /// Hook executed after burning shares and transferring assets during withdrawal /// No additional logic needed since withdrawals are disabled fn after_withdraw( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) {} + /// Hook executed before transferring assets and minting shares during deposit /// Ensures vault is not paused before accepting deposits fn before_deposit( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) { let mut contract_state = self.get_contract_mut(); contract_state.pausable.assert_not_paused(); // Prevent deposits when paused } + /// Hook executed after transferring assets and minting shares during deposit /// Updates buffer to track assets available for immediate use fn after_deposit( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) { let mut contract_state = self.get_contract_mut(); contract_state.buffer.write(contract_state.buffer.read() + assets); diff --git a/packages/vault_allocator/Scarb.toml b/packages/vault_allocator/Scarb.toml index de1d3c7f..1541c1fc 100644 --- a/packages/vault_allocator/Scarb.toml +++ b/packages/vault_allocator/Scarb.toml @@ -46,7 +46,6 @@ block_id.number = "1794270" starknet.workspace = true openzeppelin.workspace = true alexandria_math.workspace = true -pragma_lib.workspace = true [dev-dependencies] diff --git a/packages/vault_allocator/src/integration_interfaces/pragma.cairo b/packages/vault_allocator/src/integration_interfaces/pragma.cairo new file mode 100644 index 00000000..3d844d64 --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/pragma.cairo @@ -0,0 +1,20 @@ +#[derive(Serde, Drop, Copy)] +pub struct PragmaPricesResponse { + pub price: u128, + pub decimals: u32, + pub last_updated_timestamp: u64, + pub num_sources_aggregated: u32, + pub expiration_timestamp: Option, +} + +#[derive(Drop, Copy, Serde)] +pub enum DataType { + SpotEntry: felt252, + FutureEntry: (felt252, u64), + GenericEntry: felt252, +} + +#[starknet::interface] +pub trait IPragmaABI { + fn get_data_median(self: @TContractState, data_type: DataType) -> PragmaPricesResponse; +} diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index d85f6c81..0133a949 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -16,6 +16,7 @@ pub mod manager { pub mod integration_interfaces { pub mod avnu; + pub mod pragma; pub mod vesu; } diff --git a/packages/vault_allocator/src/manager/manager.cairo b/packages/vault_allocator/src/manager/manager.cairo index ab6011de..1deeb830 100644 --- a/packages/vault_allocator/src/manager/manager.cairo +++ b/packages/vault_allocator/src/manager/manager.cairo @@ -11,11 +11,11 @@ pub mod Manager { use core::num::traits::Zero; use core::pedersen::PedersenTrait; use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::merkle_tree::merkle_proof; use openzeppelin::security::pausable::PausableComponent; - use openzeppelin::token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - use openzeppelin::upgrades::interface::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::account::Call; use starknet::storage::{ diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo index f378aa95..50585e57 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -4,9 +4,8 @@ #[starknet::contract] pub mod AvnuMiddleware { - use alexandria_math::ed25519::c; use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use starknet::{ContractAddress, get_caller_address, get_contract_address}; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; diff --git a/packages/vault_allocator/src/mocks/erc20.cairo b/packages/vault_allocator/src/mocks/erc20.cairo index 4578819d..9da89c8d 100644 --- a/packages/vault_allocator/src/mocks/erc20.cairo +++ b/packages/vault_allocator/src/mocks/erc20.cairo @@ -5,9 +5,9 @@ #[starknet::contract] pub mod Erc20Mock { use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl}; use openzeppelin::upgrades::UpgradeableComponent; - use openzeppelin::upgrades::interface::IUpgradeable; use starknet::{ClassHash, ContractAddress}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); diff --git a/packages/vault_allocator/src/mocks/erc4626.cairo b/packages/vault_allocator/src/mocks/erc4626.cairo index f3d2b4aa..66de550d 100644 --- a/packages/vault_allocator/src/mocks/erc4626.cairo +++ b/packages/vault_allocator/src/mocks/erc4626.cairo @@ -5,8 +5,8 @@ #[starknet::contract] pub mod Erc4626Mock { use openzeppelin::token::erc20::extensions::erc4626::{ - DefaultConfig, ERC4626Component, ERC4626DefaultLimits, ERC4626DefaultNoFees, - ERC4626HooksEmptyImpl, + DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, ERC4626DefaultNoLimits, + ERC4626EmptyHooks, ERC4626SelfAssetsManagement, }; use openzeppelin::token::erc20::{ DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, diff --git a/packages/vault_allocator/src/mocks/flashloan.cairo b/packages/vault_allocator/src/mocks/flashloan.cairo index 7829a13b..cc7ce590 100644 --- a/packages/vault_allocator/src/mocks/flashloan.cairo +++ b/packages/vault_allocator/src/mocks/flashloan.cairo @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use starknet::{ContractAddress, get_contract_address}; fn transfer_asset( diff --git a/packages/vault_allocator/src/periphery/price_router/interface.cairo b/packages/vault_allocator/src/periphery/price_router/interface.cairo index f51d8b26..9590a07f 100644 --- a/packages/vault_allocator/src/periphery/price_router/interface.cairo +++ b/packages/vault_allocator/src/periphery/price_router/interface.cairo @@ -2,8 +2,9 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use pragma_lib::types::PragmaPricesResponse; use starknet::ContractAddress; +use vault_allocator::integration_interfaces::pragma::PragmaPricesResponse; + #[starknet::interface] pub trait IPriceRouter { diff --git a/packages/vault_allocator/src/periphery/price_router/price_router.cairo b/packages/vault_allocator/src/periphery/price_router/price_router.cairo index f3a7fb32..6acff3e3 100644 --- a/packages/vault_allocator/src/periphery/price_router/price_router.cairo +++ b/packages/vault_allocator/src/periphery/price_router/price_router.cairo @@ -4,17 +4,17 @@ #[starknet::contract] pub mod PriceRouter { - use alexandria_math::ed25519::c; use core::num::traits::{Pow, Zero}; use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait}; - use pragma_lib::types::{AggregationMode, DataType, PragmaPricesResponse}; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use starknet::ContractAddress; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; + use vault_allocator::integration_interfaces::pragma::{ + DataType, IPragmaABIDispatcher, IPragmaABIDispatcherTrait, PragmaPricesResponse, + }; use vault_allocator::periphery::price_router::errors::Errors; use vault_allocator::periphery::price_router::interface::IPriceRouter; diff --git a/packages/vault_allocator/src/test/integrations/avnu.cairo b/packages/vault_allocator/src/test/integrations/avnu.cairo index 7cf5765f..4e8b69a0 100644 --- a/packages/vault_allocator/src/test/integrations/avnu.cairo +++ b/packages/vault_allocator/src/test/integrations/avnu.cairo @@ -4,7 +4,7 @@ use alexandria_math::i257::I257Impl; use core::num::traits::Zero; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use snforge_std::{map_entry_address, store}; use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; diff --git a/packages/vault_allocator/src/test/integrations/vesu.cairo b/packages/vault_allocator/src/test/integrations/vesu.cairo index 4b582fed..fab5582c 100644 --- a/packages/vault_allocator/src/test/integrations/vesu.cairo +++ b/packages/vault_allocator/src/test/integrations/vesu.cairo @@ -4,10 +4,8 @@ use alexandria_math::i257::{I257Impl, I257Trait}; use core::num::traits::Zero; -use openzeppelin::token::erc20::extensions::erc4626::interface::{ - IERC4626Dispatcher, IERC4626DispatcherTrait, -}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use snforge_std::{map_entry_address, store}; use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ @@ -20,8 +18,8 @@ use vault_allocator::integration_interfaces::vesu::{ use vault_allocator::manager::interface::IManagerDispatcherTrait; use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, VESU_SINGLETON, wstETH}; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_vesu_flash_loan_leafs, _add_vesu_leafs, - _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_manager, + ManageLeaf, OWNER, STRATEGIST, WAD, _add_vesu_leafs, _get_proofs_using_tree, + _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_manager, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, generate_merkle_tree, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; diff --git a/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo b/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo index d927b740..76e31018 100644 --- a/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo +++ b/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo @@ -3,21 +3,14 @@ // Licensed under the MIT License. See LICENSE file for details. use alexandria_math::i257::{I257Impl, I257Trait}; use core::num::traits::Zero; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use snforge_std::{map_entry_address, store}; use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - Amount, AmountDenomination, AmountType, Route, UnsignedAmount, -}; -use vault_allocator::integration_interfaces::vesu::{ - IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, - ISingletonV2DispatcherTrait, + Amount, AmountDenomination, AmountType, Route, }; use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::middlewares::avnu_middleware::interface::{ - IAvnuMiddlewareDispatcher, IAvnuMiddlewareDispatcherTrait, -}; -use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, USDC, USDT, VESU_SINGLETON, wstETH}; +use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, VESU_SINGLETON, wstETH}; use vault_allocator::test::utils::{ ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _add_vesu_flash_loan_leafs, _add_vesu_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, @@ -238,7 +231,6 @@ fn test_leveraged_loop_staked_ether() { ); routes.serialize(ref array_of_calldata_multi_route_swap); - println!("array_of_calldata_multi_route_swap: {:?}", array_of_calldata_multi_route_swap); flash_loan_data_calldata.append(array_of_calldata_multi_route_swap.span()); diff --git a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo index c2d37344..6915fd9a 100644 --- a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo +++ b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo @@ -3,11 +3,11 @@ // Licensed under the MIT License. See LICENSE file for details. use alexandria_math::i257::{I257Impl, I257Trait}; use core::num::traits::Zero; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use snforge_std::{map_entry_address, store}; use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - Amount, AmountDenomination, AmountType, Route, UnsignedAmount, + Amount, AmountDenomination, AmountType, Route, }; use vault_allocator::integration_interfaces::vesu::{ IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, diff --git a/packages/vault_allocator/src/test/units/manager.cairo b/packages/vault_allocator/src/test/units/manager.cairo index 585ad674..3df3f8ee 100644 --- a/packages/vault_allocator/src/test/units/manager.cairo +++ b/packages/vault_allocator/src/test/units/manager.cairo @@ -2,12 +2,12 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::access::accesscontrol::interface::{ +use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; -use openzeppelin::security::interface::{IPausableDispatcher, IPausableDispatcherTrait}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::security::pausable::{IPausableDispatcher, IPausableDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use starknet::ContractAddress; use vault_allocator::integration_interfaces::vesu::{ IFlashloanReceiverDispatcher, IFlashloanReceiverDispatcherTrait, @@ -26,9 +26,7 @@ use vault_allocator::test::utils::{ deploy_flashloan_mock, deploy_manager, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, generate_merkle_tree, }; -use vault_allocator::vault_allocator::interface::{ - IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, -}; +use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_constructor() { diff --git a/packages/vault_allocator/src/test/units/vault_allocator.cairo b/packages/vault_allocator/src/test/units/vault_allocator.cairo index 9d38e572..89eaeaa4 100644 --- a/packages/vault_allocator/src/test/units/vault_allocator.cairo +++ b/packages/vault_allocator/src/test/units/vault_allocator.cairo @@ -2,8 +2,8 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use openzeppelin::interfaces::ownable::{IOwnableDispatcher, IOwnableDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use starknet::ContractAddress; use starknet::account::Call; use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index 7f178d61..7cd60c01 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -5,14 +5,11 @@ use core::hash::HashStateTrait; use core::num::traits::Zero; use core::pedersen::PedersenTrait; +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use openzeppelin::merkle_tree::hashes::PedersenCHasher; -use openzeppelin::token::erc20::extensions::erc4626::interface::{ - IERC4626Dispatcher, IERC4626DispatcherTrait, -}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use snforge_std::{CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare}; use starknet::syscalls::call_contract_syscall; -use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; +use starknet::{ClassHash, ContractAddress}; use vault_allocator::integration_interfaces::vesu::{ IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, ISingletonV2DispatcherTrait, diff --git a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo index ecc76293..cdfdff2c 100644 --- a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo +++ b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo @@ -5,7 +5,7 @@ #[starknet::contract] pub mod VaultAllocator { use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::upgrades::interface::IUpgradeable; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::account::Call; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; From 6beb1d144913b90ee1b1f992742c932281f7bd0d Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:57:11 +0100 Subject: [PATCH 03/54] Refactor merkle tree generation and add json merkle creator --- export_merkle.sh | 77 +++++++ leafs/mytree.json | 206 ++++++++++++++++++ packages/vault_allocator/src/lib.cairo | 1 + .../vault_allocator/src/test/creator.cairo | 106 +++++++++ packages/vault_allocator/src/test/utils.cairo | 27 +-- 5 files changed, 404 insertions(+), 13 deletions(-) create mode 100755 export_merkle.sh create mode 100644 leafs/mytree.json create mode 100644 packages/vault_allocator/src/test/creator.cairo diff --git a/export_merkle.sh b/export_merkle.sh new file mode 100755 index 00000000..138eb1c7 --- /dev/null +++ b/export_merkle.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- Config --- +PKG="vault_allocator" +TEST="vault_allocator::test::creator::test_creator" +OUT_DIR="leafs" + +# Nom du fichier = premier argument (sinon "merkle") +NAME="${1:-merkle}" +OUT_PATH="$OUT_DIR/$NAME.json" +LOG_PATH="$OUT_DIR/$NAME.log" + +mkdir -p "$OUT_DIR" + +# --- Run test --- +snforge test -p "$PKG" "$TEST" 2>&1 | tee "$LOG_PATH" >/dev/null + +# --- Parse output --- +python3 - "$LOG_PATH" "$OUT_PATH" << 'PY' +import sys, re, json, pathlib +log_path, out_path = sys.argv[1], sys.argv[2] +s = pathlib.Path(log_path).read_text() + +def get(k): + m = re.search(rf"^{k}:\s*([0-9]+)\s*$", s, re.M) + return m.group(1) if m else "" + +blk = re.search(r"leaf_additional_data:\s*\[(.*)\]\s*tree:", s, re.S) +items = re.findall(r"ManageLeafAdditionalData\s*\{(.*?)\}", blk.group(1), re.S) if blk else [] +leafs = [] +for it in items: + g = lambda pat: (re.search(pat, it, re.S).group(1) if re.search(pat, it, re.S) else "") + argm = re.search(r"argument_addresses:\s*\[(.*?)\]", it, re.S) + args = re.findall(r"[0-9]+", argm.group(1)) if argm else [] + leafs.append({ + "decoder_and_sanitizer": g(r"decoder_and_sanitizer:\s*([0-9]+)"), + "target": g(r"target:\s*([0-9]+)"), + "selector": g(r"selector:\s*([0-9]+)"), + "argument_addresses": args, + "description": g(r'description:\s*"([^"]*)"'), + "leaf_index": int(g(r"leaf_index:\s*([0-9]+)") or 0), + "leaf_hash": g(r"leaf_hash:\s*([0-9]+)") + }) + +# tree via comptage de crochets +tree = [] +start = s.find("tree:") +if start != -1: + i = s.find("[", start) + if i != -1: + depth = 0; buf = "" + for ch in s[i:]: + buf += ch + if ch == "[": depth += 1 + elif ch == "]": depth -= 1 + if depth == 0: break + for row in re.findall(r"\[([0-9,\s]+)\]", buf): + tree.append(re.findall(r"[0-9]+", row)) + +doc = { + "metadata": { + "vault": get("vault"), + "vault_allocator": get("vault_allocator"), + "manager": get("manager"), + "decoder_and_sanitizer": get("decoder_and_sanitizer"), + "root": get("root"), + "tree_capacity": int(get("tree_capacity") or 0), + "leaf_used": int(get("leaf_used") or 0) + }, + "leafs": leafs, + "tree": tree +} + +pathlib.Path(out_path).write_text(json.dumps(doc, indent=2)) +print(f"Wrote {out_path} (log: {log_path})") +PY diff --git a/leafs/mytree.json b/leafs/mytree.json new file mode 100644 index 00000000..91304e73 --- /dev/null +++ b/leafs/mytree.json @@ -0,0 +1,206 @@ +{ + "metadata": { + "vault": "5413914729363742859539197023059", + "vault_allocator": "5413914729363742859539197023059", + "manager": "5413914729363742859539197023059", + "decoder_and_sanitizer": "5413914729363742859539197023059", + "root": "2009410845371992160865623192489255279553238721469696220270717266578317727536", + "tree_capacity": 16, + "leaf_used": 12 + }, + "leafs": [ + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "5413914729363742859539197023059", + "selector": "272736270449175597906601442304651169002384630994237278650276415012817394366", + "argument_addresses": [ + "5413914729363742859539197023059", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "0" + ], + "description": "Flash loan wstETH", + "leaf_index": 0, + "leaf_hash": "317606356264796957081494875492335589028851932487272643796381474436363864238" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "154717502686997779505242937237748798500912348117963555524611254740330341259", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "3525041238689230877912143146602053624361984120479485588303103220764281888840" + ], + "description": "Approve vWSTETH to spend wstETH", + "leaf_index": 1, + "leaf_hash": "2382129687266469781982741342878431266787679357347247675491511646191972799740" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "352040181584456735608515580760888541466059565068553383579463728554843487745", + "argument_addresses": [ + "5413914729363742859539197023059" + ], + "description": "Deposit wstETH for vWSTETH", + "leaf_index": 2, + "leaf_hash": "678303738440072355040482306291323122231389812748699335636114100561821006738" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "602617684354587743731238934093348436560137034424203693318834094005509508215", + "argument_addresses": [ + "5413914729363742859539197023059", + "5413914729363742859539197023059" + ], + "description": "Withdraw wstETH from vWSTETH", + "leaf_index": 3, + "leaf_hash": "259042544756615804735847710915085572036254733492650027317560391082052917849" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "1329909728320632088402217562277154056711815095720684343816173432540100887380", + "argument_addresses": [ + "5413914729363742859539197023059" + ], + "description": "Mint vWSTETH from wstETH", + "leaf_index": 4, + "leaf_hash": "2020394736186607945703320019579876486318886267839428950131345419282806178769" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "1326975239452520649139862114866922847703907595552193666703522601892858398217", + "argument_addresses": [ + "5413914729363742859539197023059", + "5413914729363742859539197023059" + ], + "description": "Redeem vWSTETH for wstETH", + "leaf_index": 5, + "leaf_hash": "3042765462205175563536591820905994748649291322464868128752071183187879121549" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "154717502686997779505242937237748798500912348117963555524611254740330341259", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "23945123541494432761473724903801722895187605313546851349842002150511018336" + ], + "description": "Approve singleton_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 to spend wstETH", + "leaf_index": 6, + "leaf_hash": "2445852729353229310731046560318695011241265124708031712248225808303118455325" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2205784456246912591591022705581145477202962715352127032183976536021813616724" + ], + "description": "Approve extension_pid_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 to spend vWSTETH", + "leaf_index": 7, + "leaf_hash": "3464090403020451920057379067729769391029911523431888726114405517374847538715" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "23945123541494432761473724903801722895187605313546851349842002150511018336", + "selector": "856336041243570760931994264144677337008736779890258883919015516521378842083", + "argument_addresses": [ + "2198503327643286920898110335698706244522220458610657370981979460625005526824", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "0", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "2205784456246912591591022705581145477202962715352127032183976536021813616724", + "5413914729363742859539197023059", + "0", + "0" + ], + "description": "Transfer position extension_pid_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 with collateral wstETH and debt ETH", + "leaf_index": 8, + "leaf_hash": "1819209779739977841040933056925283731595048071224895547241853612166795162382" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "23945123541494432761473724903801722895187605313546851349842002150511018336", + "selector": "1631880094539101464511272129576525542256330665932643760479341072373928343757", + "argument_addresses": [ + "2198503327643286920898110335698706244522220458610657370981979460625005526824", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "5413914729363742859539197023059", + "0" + ], + "description": "Modify position extension_pid_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 with collateral wstETH and debt ETH", + "leaf_index": 9, + "leaf_hash": "2634592553496285009292376176295427889867467179651684925541942771847164440290" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "5413914729363742859539197023059" + ], + "description": "Approve avnu_router to spend ETH", + "leaf_index": 10, + "leaf_hash": "2563898399473985563900934298259834363461328130575117797451263812333569086466" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "5413914729363742859539197023059", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "5413914729363742859539197023059" + ], + "description": "Multi route swap ETH for wstETH", + "leaf_index": 11, + "leaf_hash": "1402948939338977525922864200314918151613509013497338423302729291119418422970" + } + ], + "tree": [ + [ + "317606356264796957081494875492335589028851932487272643796381474436363864238", + "2382129687266469781982741342878431266787679357347247675491511646191972799740", + "678303738440072355040482306291323122231389812748699335636114100561821006738", + "259042544756615804735847710915085572036254733492650027317560391082052917849", + "2020394736186607945703320019579876486318886267839428950131345419282806178769", + "3042765462205175563536591820905994748649291322464868128752071183187879121549", + "2445852729353229310731046560318695011241265124708031712248225808303118455325", + "3464090403020451920057379067729769391029911523431888726114405517374847538715", + "1819209779739977841040933056925283731595048071224895547241853612166795162382", + "2634592553496285009292376176295427889867467179651684925541942771847164440290", + "2563898399473985563900934298259834363461328130575117797451263812333569086466", + "1402948939338977525922864200314918151613509013497338423302729291119418422970", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691" + ], + [ + "3423822769287180484021583579805443593830968587176109056997353008718099863284", + "1325742362178912360536139177417362640345586131374859855524232570863623463544", + "2211049742759667452469885564658058345369667690180880197147601523953370677968", + "2247797487024426298547368233360539032659181373376435894639190991549218093791", + "2408786195949745898962724526621934123371926682098616906200007259456869003567", + "1223523352300262617761383728743461619458442984187545862062756263135352083770", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341" + ], + [ + "2654567653756178410357495042196036381127818862667901540690741603802233635829", + "2106748485588187450465230936666810982274765978487413121986630047764081007834", + "2083218962872988229484764095511349082585743934726234369242472763664387490033", + "2002344887409916074004609464034933685885954831475272208035150614502178175918" + ], + [ + "3081352858128957529930488752811556657193091015231923829060236869527770054267", + "738594670216921787531516715860696216564232173815946286788857865369399678411" + ], + [ + "2009410845371992160865623192489255279553238721469696220270717266578317727536" + ] + ] +} \ No newline at end of file diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 0133a949..0d3b192b 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -64,6 +64,7 @@ pub mod mocks { #[cfg(test)] pub mod test { + pub mod creator; pub mod register; pub mod utils; pub mod units { diff --git a/packages/vault_allocator/src/test/creator.cairo b/packages/vault_allocator/src/test/creator.cairo new file mode 100644 index 00000000..b40b4796 --- /dev/null +++ b/packages/vault_allocator/src/test/creator.cairo @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. +use alexandria_math::i257::I257Impl; +use starknet::ContractAddress; +use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, wstETH}; +use vault_allocator::test::utils::{ + ManageLeaf, _add_avnu_leafs, _add_vesu_flash_loan_leafs, _add_vesu_leafs, + _pad_leafs_to_power_of_two, generate_merkle_tree, get_leaf_hash, +}; +use super::utils::DUMMY_ADDRESS; + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct ManageLeafAdditionalData { + pub decoder_and_sanitizer: ContractAddress, + pub target: ContractAddress, + pub selector: felt252, + pub argument_addresses: Span, + pub description: ByteArray, + pub leaf_index: u32, + pub leaf_hash: felt252, +} +#[fork("MAINNET")] +#[test] +fn test_creator() { + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + // MANDATORY + let vault: ContractAddress = DUMMY_ADDRESS(); + let vault_allocator = DUMMY_ADDRESS(); + let manager = DUMMY_ADDRESS(); + let decoder_and_sanitizer = DUMMY_ADDRESS(); + let router = DUMMY_ADDRESS(); + + // INTEGRATIONS + let flash_loan_asset = wstETH(); + let is_legacy = false; + _add_vesu_flash_loan_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + decoder_and_sanitizer, + manager, + flash_loan_asset, + is_legacy, + ); + + let pool_id = GENESIS_POOL_ID; + let mut assets_to_supply = ArrayTrait::new(); + assets_to_supply.append(wstETH()); + let mut assets_to_borrow_per_assets_to_supply = ArrayTrait::new(); + assets_to_borrow_per_assets_to_supply.append(array![ETH()].span()); + + _add_vesu_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + decoder_and_sanitizer, + pool_id, + assets_to_supply.span(), + assets_to_borrow_per_assets_to_supply.span(), + ); + + let mut pairs_to_swap = ArrayTrait::new(); + pairs_to_swap.append((ETH(), wstETH())); + + _add_avnu_leafs( + ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, router, pairs_to_swap, + ); + + let leaf_used = leafs.len(); + + // MERKLE TREE CREATION + _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); + let tree_capacity = leafs.len(); + let tree = generate_merkle_tree(leafs.span()); + let root = *tree.at(tree.len() - 1).at(0); + + let mut leaf_additional_data = ArrayTrait::new(); + for i in 0..leaf_used { + leaf_additional_data + .append( + ManageLeafAdditionalData { + decoder_and_sanitizer: *leafs.at(i).decoder_and_sanitizer, + target: *leafs.at(i).target, + selector: *leafs.at(i).selector, + argument_addresses: *leafs.at(i).argument_addresses, + description: leafs.at(i).description.clone(), + leaf_index: i, + leaf_hash: get_leaf_hash(leafs.at(i).clone()), + }, + ); + } + + // PRINT + println!("vault: {:?}", vault); + println!("vault_allocator: {:?}", vault_allocator); + println!("manager: {:?}", manager); + println!("decoder_and_sanitizer: {:?}", decoder_and_sanitizer); + println!("root: {:?}", root); + println!("tree_capacity: {:?}", tree_capacity); + println!("leaf_used: {:?}", leaf_used); + println!("leaf_additional_data: {:?}", leaf_additional_data); + println!("tree: {:?}", tree); +} diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index 7cd60c01..857226cb 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -173,24 +173,25 @@ pub struct ManageLeaf { pub description: ByteArray, } +pub fn get_leaf_hash(leaf: ManageLeaf) -> felt252 { + let mut serialized_struct: Array = ArrayTrait::new(); + leaf.decoder_and_sanitizer.serialize(ref serialized_struct); + leaf.target.serialize(ref serialized_struct); + leaf.selector.serialize(ref serialized_struct); + leaf.argument_addresses.serialize(ref serialized_struct); + let first_element = serialized_struct.pop_front().unwrap(); + let mut state = PedersenTrait::new(first_element); + while let Some(value) = serialized_struct.pop_front() { + state = state.update(value); + } + state.finalize() +} pub fn generate_merkle_tree(manage_leafs: Span) -> Array> { let mut first_layer = ArrayTrait::new(); let leafs_length = manage_leafs.len(); for i in 0..leafs_length { - let mut serialized_struct: Array = ArrayTrait::new(); - manage_leafs[i].decoder_and_sanitizer.serialize(ref serialized_struct); - manage_leafs[i].target.serialize(ref serialized_struct); - manage_leafs[i].selector.serialize(ref serialized_struct); - manage_leafs[i].argument_addresses.serialize(ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); - - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } - let leaf_hash = state.finalize(); - first_layer.append(leaf_hash); + first_layer.append(get_leaf_hash(manage_leafs.at(i).clone())); } let mut leafs = ArrayTrait::new(); leafs.append(first_layer); From 16e22e4084803ce63e12836635479ebabc1dfa96 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 20 Aug 2025 03:36:07 +0100 Subject: [PATCH 04/54] rename strategy merkle tree --- leafs/{mytree.json => myDegenStrategy.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename leafs/{mytree.json => myDegenStrategy.json} (100%) diff --git a/leafs/mytree.json b/leafs/myDegenStrategy.json similarity index 100% rename from leafs/mytree.json rename to leafs/myDegenStrategy.json From efc129b83a59281ed5b608253d721901ae7e4128 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 20 Aug 2025 03:37:21 +0100 Subject: [PATCH 05/54] Add backend repository structure Initialize NestJS-based backend with API, indexer, and relayer services. Includes shared libraries for config, database, logging, and core types. --- backend/.env.example | 14 + backend/.gitignore | 142 + backend/README.md | 141 + backend/apps/api/README.md | 29 + backend/apps/api/nest-cli.json | 8 + backend/apps/api/package.json | 39 + backend/apps/api/src/app.controller.ts | 25 + backend/apps/api/src/app.module.ts | 21 + backend/apps/api/src/app.service.ts | 8 + .../apps/api/src/health/health.constants.ts | 1 + .../apps/api/src/health/health.controller.ts | 23 + backend/apps/api/src/health/health.module.ts | 11 + backend/apps/api/src/health/index.ts | 3 + backend/apps/api/src/main.ts | 36 + backend/apps/api/src/starknet/index.ts | 2 + .../apps/api/src/starknet/starknet.module.ts | 10 + .../apps/api/src/starknet/starknet.service.ts | 55 + backend/apps/api/tsconfig.json | 8 + backend/apps/indexer/README.md | 29 + backend/apps/indexer/package.json | 27 + backend/apps/indexer/src/decoder.ts | 69 + backend/apps/indexer/src/indexer.constants.ts | 4 + backend/apps/indexer/src/indexer.service.ts | 453 ++ backend/apps/indexer/src/main.ts | 34 + backend/apps/indexer/tsconfig.json | 11 + backend/apps/relayer/README.md | 28 + backend/apps/relayer/package.json | 27 + backend/apps/relayer/src/jobs/example-job.ts | 17 + backend/apps/relayer/src/main.ts | 50 + .../relayer/src/services/scheduler.service.ts | 42 + backend/apps/relayer/tsconfig.json | 11 + backend/libs/config/package.json | 17 + backend/libs/config/src/config.module.ts | 16 + backend/libs/config/src/config.service.ts | 10 + backend/libs/config/src/env.validation.ts | 29 + backend/libs/config/src/index.ts | 3 + backend/libs/config/tsconfig.json | 9 + backend/libs/core/package.json | 15 + backend/libs/core/src/constants/strategy.ts | 6 + backend/libs/core/src/index.ts | 2 + backend/libs/core/src/types/strategy.ts | 4 + backend/libs/core/tsconfig.json | 8 + backend/libs/db/package.json | 15 + backend/libs/db/src/index.ts | 2 + backend/libs/db/src/prisma.module.ts | 8 + backend/libs/db/src/prisma.service.ts | 38 + backend/libs/db/tsconfig.json | 9 + backend/libs/logger/package.json | 12 + backend/libs/logger/src/index.ts | 1 + backend/libs/logger/src/logger.ts | 27 + backend/libs/logger/tsconfig.json | 9 + backend/package.json | 55 + backend/pnpm-lock.yaml | 6854 +++++++++++++++++ backend/pnpm-workspace.yaml | 3 + backend/prisma/schema.prisma | 68 + backend/tsconfig.base.json | 31 + backend/tsconfig.json | 8 + 57 files changed, 8637 insertions(+) create mode 100644 backend/.env.example create mode 100644 backend/.gitignore create mode 100644 backend/README.md create mode 100644 backend/apps/api/README.md create mode 100644 backend/apps/api/nest-cli.json create mode 100644 backend/apps/api/package.json create mode 100644 backend/apps/api/src/app.controller.ts create mode 100644 backend/apps/api/src/app.module.ts create mode 100644 backend/apps/api/src/app.service.ts create mode 100644 backend/apps/api/src/health/health.constants.ts create mode 100644 backend/apps/api/src/health/health.controller.ts create mode 100644 backend/apps/api/src/health/health.module.ts create mode 100644 backend/apps/api/src/health/index.ts create mode 100644 backend/apps/api/src/main.ts create mode 100644 backend/apps/api/src/starknet/index.ts create mode 100644 backend/apps/api/src/starknet/starknet.module.ts create mode 100644 backend/apps/api/src/starknet/starknet.service.ts create mode 100644 backend/apps/api/tsconfig.json create mode 100644 backend/apps/indexer/README.md create mode 100644 backend/apps/indexer/package.json create mode 100644 backend/apps/indexer/src/decoder.ts create mode 100644 backend/apps/indexer/src/indexer.constants.ts create mode 100644 backend/apps/indexer/src/indexer.service.ts create mode 100644 backend/apps/indexer/src/main.ts create mode 100644 backend/apps/indexer/tsconfig.json create mode 100644 backend/apps/relayer/README.md create mode 100644 backend/apps/relayer/package.json create mode 100644 backend/apps/relayer/src/jobs/example-job.ts create mode 100644 backend/apps/relayer/src/main.ts create mode 100644 backend/apps/relayer/src/services/scheduler.service.ts create mode 100644 backend/apps/relayer/tsconfig.json create mode 100644 backend/libs/config/package.json create mode 100644 backend/libs/config/src/config.module.ts create mode 100644 backend/libs/config/src/config.service.ts create mode 100644 backend/libs/config/src/env.validation.ts create mode 100644 backend/libs/config/src/index.ts create mode 100644 backend/libs/config/tsconfig.json create mode 100644 backend/libs/core/package.json create mode 100644 backend/libs/core/src/constants/strategy.ts create mode 100644 backend/libs/core/src/index.ts create mode 100644 backend/libs/core/src/types/strategy.ts create mode 100644 backend/libs/core/tsconfig.json create mode 100644 backend/libs/db/package.json create mode 100644 backend/libs/db/src/index.ts create mode 100644 backend/libs/db/src/prisma.module.ts create mode 100644 backend/libs/db/src/prisma.service.ts create mode 100644 backend/libs/db/tsconfig.json create mode 100644 backend/libs/logger/package.json create mode 100644 backend/libs/logger/src/index.ts create mode 100644 backend/libs/logger/src/logger.ts create mode 100644 backend/libs/logger/tsconfig.json create mode 100644 backend/package.json create mode 100644 backend/pnpm-lock.yaml create mode 100644 backend/pnpm-workspace.yaml create mode 100644 backend/prisma/schema.prisma create mode 100644 backend/tsconfig.base.json create mode 100644 backend/tsconfig.json diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 00000000..78104baf --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,14 @@ +# Database +DATABASE_URL="postgresql://username:password@localhost:5432/starknet_vault_db?schema=public" + +# Server +PORT=3000 + +# StarkNet +STARKNET_RPC_URL="https://starknet-sepolia.public.blastapi.io/rpc/v0_7" + +# Indexer +APIBARA_TOKEN="your_apibara_token_here" + +# Optional: Production URLs +# STARKNET_RPC_URL="https://starknet-mainnet.public.blastapi.io/rpc/v0_7" \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..9fdf324d --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,142 @@ +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Build artifacts +dist/ +*.js +*.d.ts +*.js.map +!node_modules/** + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..f6b97660 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,141 @@ +# StarkNet Vault Kit Backend + +A monorepo containing the backend services for the StarkNet Vault Kit project. + +## Architecture + +This project is organized as a monorepo with shared libraries and independent applications: + +### Applications (`apps/`) + +- **`api`** - NestJS HTTP API service +- **`indexer`** - Apibara event indexer +- **`relayer`** - Cron job worker for automated operations + +### Libraries (`libs/`) + +- **`@forge/core`** - Core types, DTOs, and constants +- **`@forge/config`** - Configuration management with validation +- **`@forge/db`** - Prisma client and database access layer +- **`@forge/logger`** - Logging utilities + +## Getting Started + +### Prerequisites + +- Node.js 18+ +- pnpm (recommended) or yarn/npm with workspaces +- PostgreSQL database +- StarkNet RPC access +- Apibara API token (for indexer) + +### Installation + +```bash +# Install all dependencies +pnpm install + +# Generate Prisma client +pnpm generate + +# Run database migrations +pnpm migrate:dev +``` + +### Development + +```bash +# Run all services in development mode +pnpm dev:all + +# Run individual services +pnpm dev:api +pnpm dev:indexer +pnpm dev:relayer +``` + +### Building + +```bash +# Build all packages +pnpm build + +# Build individual packages +pnpm build:api +pnpm build:indexer +pnpm build:relayer +``` + +### Database + +```bash +# Generate Prisma client +pnpm generate + +# Run migrations +pnpm migrate:dev + +# Open Prisma Studio +pnpm studio +``` + +## Environment Variables + +Create a `.env` file in the root with: + +```bash +DATABASE_URL="postgresql://user:password@localhost:5432/starknet_vault_kit" +STARKNET_RPC_URL="https://starknet-sepolia.public.blastapi.io/rpc/v0_7" +APIBARA_TOKEN="your_apibara_token" +PORT=3000 +``` + +## Project Structure + +``` +. +├── apps/ +│ ├── api/ # NestJS HTTP API +│ ├── indexer/ # Apibara event indexer +│ └── relayer/ # Cron job worker +├── libs/ +│ ├── core/ # Shared types and constants +│ ├── config/ # Configuration management +│ ├── db/ # Database client and DAL +│ └── logger/ # Logging utilities +├── prisma/ +│ └── schema.prisma # Database schema +├── package.json # Root package with workspace scripts +├── pnpm-workspace.yaml # pnpm workspace configuration +└── tsconfig.base.json # Base TypeScript configuration +``` + +## Scripts + +- `pnpm dev:all` - Run all services in development +- `pnpm build` - Build all packages +- `pnpm lint` - Lint all packages +- `pnpm test` - Run tests for all packages +- `pnpm migrate:dev` - Run database migrations +- `pnpm generate` - Generate Prisma client +- `pnpm studio` - Open Prisma Studio + +## Services + +### API Service (`apps/api`) + +- RESTful API endpoints +- Swagger documentation at `/api` +- Health checks at `/` and `/health` + +### Indexer Service (`apps/indexer`) + +- Real-time StarkNet event streaming via Apibara +- Automatic event decoding and database persistence +- Vault-specific event tracking (RedeemRequested, RedeemClaimed, Report) + +### Relayer Service (`apps/relayer`) + +- Scheduled job execution with cron +- Automated vault operations and maintenance +- Cross-chain bridging (planned) diff --git a/backend/apps/api/README.md b/backend/apps/api/README.md new file mode 100644 index 00000000..71681cd1 --- /dev/null +++ b/backend/apps/api/README.md @@ -0,0 +1,29 @@ +# API Service + +NestJS-based HTTP API for the StarkNet Vault Kit backend. + +## Features + +- RESTful API endpoints +- Swagger documentation +- Health checks +- StarkNet integration + +## Development + +```bash +# Run in development mode +pnpm dev:api + +# Build +pnpm build:api + +# Start production +pnpm --filter api start +``` + +## Endpoints + +- `GET /` - Basic health check +- `GET /health` - Detailed health status +- `GET /api` - Swagger UI documentation \ No newline at end of file diff --git a/backend/apps/api/nest-cli.json b/backend/apps/api/nest-cli.json new file mode 100644 index 00000000..bc097ceb --- /dev/null +++ b/backend/apps/api/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} \ No newline at end of file diff --git a/backend/apps/api/package.json b/backend/apps/api/package.json new file mode 100644 index 00000000..aca9b8dc --- /dev/null +++ b/backend/apps/api/package.json @@ -0,0 +1,39 @@ +{ + "name": "api", + "version": "0.0.1", + "description": "NestJS HTTP API for StarkNet Vault Kit", + "main": "dist/main.js", + "scripts": { + "build": "nest build", + "dev": "nest start --watch", + "start": "node dist/main.js", + "lint": "eslint \"src/**/*.ts\" --fix", + "test": "jest" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/db": "workspace:*", + "@forge/core": "workspace:*", + "@forge/logger": "workspace:*", + "@nestjs/axios": "^3.0.0", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/schedule": "^4.0.0", + "@nestjs/swagger": "^7.0.0", + "@nestjs/terminus": "^11.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1", + "starknet": "^6.0.0" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/jest": "^29.5.2", + "jest": "^29.5.0", + "ts-jest": "^29.1.0" + } +} \ No newline at end of file diff --git a/backend/apps/api/src/app.controller.ts b/backend/apps/api/src/app.controller.ts new file mode 100644 index 00000000..422400ed --- /dev/null +++ b/backend/apps/api/src/app.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get } from "@nestjs/common"; +import { ApiTags, ApiResponse } from "@nestjs/swagger"; +import { AppService } from "./app.service"; + +@ApiTags("health") +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + @ApiResponse({ status: 200, description: "Health check endpoint" }) + getHello(): string { + return this.appService.getHello(); + } + + @Get("health") + @ApiResponse({ status: 200, description: "Application health status" }) + getHealth() { + return { + status: "ok", + timestamp: new Date().toISOString(), + uptime: process.uptime(), + }; + } +} \ No newline at end of file diff --git a/backend/apps/api/src/app.module.ts b/backend/apps/api/src/app.module.ts new file mode 100644 index 00000000..a759a5d6 --- /dev/null +++ b/backend/apps/api/src/app.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { ConfigModule } from '@forge/config'; +import { PrismaModule } from '@forge/db'; +import { StarknetModule } from './starknet'; +import { HealthModule } from './health'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [ + ConfigModule, + ScheduleModule.forRoot(), + PrismaModule, + StarknetModule, + HealthModule, + ], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} \ No newline at end of file diff --git a/backend/apps/api/src/app.service.ts b/backend/apps/api/src/app.service.ts new file mode 100644 index 00000000..09567780 --- /dev/null +++ b/backend/apps/api/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class AppService { + getHello(): string { + return "StarkNet Vault Kit Backend API"; + } +} diff --git a/backend/apps/api/src/health/health.constants.ts b/backend/apps/api/src/health/health.constants.ts new file mode 100644 index 00000000..ff9ab0a5 --- /dev/null +++ b/backend/apps/api/src/health/health.constants.ts @@ -0,0 +1 @@ +export const HEALTH_URL = 'health'; \ No newline at end of file diff --git a/backend/apps/api/src/health/health.controller.ts b/backend/apps/api/src/health/health.controller.ts new file mode 100644 index 00000000..9a0e6c78 --- /dev/null +++ b/backend/apps/api/src/health/health.controller.ts @@ -0,0 +1,23 @@ +import { + HealthCheckService, + MemoryHealthIndicator, + HealthCheck, +} from "@nestjs/terminus"; +import { Controller, Get } from "@nestjs/common"; +import { HEALTH_URL } from "./health.constants"; + +@Controller(HEALTH_URL) +export class HealthController { + constructor( + private health: HealthCheckService, + private memory: MemoryHealthIndicator + ) {} + + @Get() + @HealthCheck() + check() { + return this.health.check([ + () => this.memory.checkHeap("memoryHeap", 1024 * 1024 * 1024), + ]); + } +} diff --git a/backend/apps/api/src/health/health.module.ts b/backend/apps/api/src/health/health.module.ts new file mode 100644 index 00000000..e0fcc4d3 --- /dev/null +++ b/backend/apps/api/src/health/health.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { TerminusModule } from "@nestjs/terminus"; +import { HealthController } from "./health.controller"; +import { HttpModule } from "@nestjs/axios"; + +@Module({ + providers: [], + controllers: [HealthController], + imports: [TerminusModule, HttpModule], +}) +export class HealthModule {} \ No newline at end of file diff --git a/backend/apps/api/src/health/index.ts b/backend/apps/api/src/health/index.ts new file mode 100644 index 00000000..8dda1073 --- /dev/null +++ b/backend/apps/api/src/health/index.ts @@ -0,0 +1,3 @@ +export * from './health.module'; +export * from './health.controller'; +export * from './health.constants'; \ No newline at end of file diff --git a/backend/apps/api/src/main.ts b/backend/apps/api/src/main.ts new file mode 100644 index 00000000..9a60c450 --- /dev/null +++ b/backend/apps/api/src/main.ts @@ -0,0 +1,36 @@ +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }), + ); + + app.enableCors(); + + const config = new DocumentBuilder() + .setTitle('StarkNet Vault Kit API') + .setDescription('API for StarkNet Vault Kit indexer and services') + .setVersion('1.0') + .addTag('indexer') + .addTag('starknet') + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api', app, document); + + const port = process.env.PORT || 3000; + await app.listen(port); + console.log(`Application is running on: http://localhost:${port}`); + console.log(`Swagger UI: http://localhost:${port}/api`); +} + +bootstrap(); \ No newline at end of file diff --git a/backend/apps/api/src/starknet/index.ts b/backend/apps/api/src/starknet/index.ts new file mode 100644 index 00000000..dab36561 --- /dev/null +++ b/backend/apps/api/src/starknet/index.ts @@ -0,0 +1,2 @@ +export * from './starknet.module'; +export * from './starknet.service'; \ No newline at end of file diff --git a/backend/apps/api/src/starknet/starknet.module.ts b/backend/apps/api/src/starknet/starknet.module.ts new file mode 100644 index 00000000..87c6a58b --- /dev/null +++ b/backend/apps/api/src/starknet/starknet.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { StarknetService } from "./starknet.service"; +import { ConfigModule } from "@forge/config"; + +@Module({ + imports: [ConfigModule], + providers: [StarknetService], + exports: [StarknetService], +}) +export class StarknetModule {} \ No newline at end of file diff --git a/backend/apps/api/src/starknet/starknet.service.ts b/backend/apps/api/src/starknet/starknet.service.ts new file mode 100644 index 00000000..14e9dcf0 --- /dev/null +++ b/backend/apps/api/src/starknet/starknet.service.ts @@ -0,0 +1,55 @@ +import { Injectable, OnModuleInit } from "@nestjs/common"; +import { ConfigService } from "@forge/config"; +import { Provider, RpcProvider, Contract } from "starknet"; + +@Injectable() +export class StarknetService implements OnModuleInit { + private provider: Provider; + + constructor(private configService: ConfigService) {} + + async onModuleInit() { + const rpcUrl = this.configService.get("STARKNET_RPC_URL") || "https://starknet-sepolia.public.blastapi.io/rpc/v0_7"; + this.provider = new RpcProvider({ nodeUrl: rpcUrl }); + } + + getProvider(): Provider { + return this.provider; + } + + async getBlockNumber(): Promise { + return await this.provider.getBlockNumber(); + } + + async getBlock(blockNumber: number) { + return await this.provider.getBlockWithTxHashes(blockNumber); + } + + async getTransaction(txHash: string) { + return await this.provider.getTransaction(txHash); + } + + async getTransactionReceipt(txHash: string) { + return await this.provider.getTransactionReceipt(txHash); + } + + async getContractClass(contractAddress: string) { + return await this.provider.getClassAt(contractAddress); + } + + createContract(address: string, abi: any): Contract { + return new Contract(abi, address, this.provider); + } + + async callContract( + contractAddress: string, + functionName: string, + calldata: any[] = [] + ) { + return await this.provider.callContract({ + contractAddress, + entrypoint: functionName, + calldata, + }); + } +} \ No newline at end of file diff --git a/backend/apps/api/tsconfig.json b/backend/apps/api/tsconfig.json new file mode 100644 index 00000000..fdf7ed1d --- /dev/null +++ b/backend/apps/api/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*", "../../libs/*/src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/apps/indexer/README.md b/backend/apps/indexer/README.md new file mode 100644 index 00000000..6f5dee1c --- /dev/null +++ b/backend/apps/indexer/README.md @@ -0,0 +1,29 @@ +# Indexer Service + +Apibara-based event indexer for StarkNet Vault Kit smart contracts. + +## Features + +- Real-time event streaming from StarkNet +- Event decoding and processing +- Database persistence with batching +- Automatic reconnection and error handling + +## Development + +```bash +# Run in development mode +pnpm dev:indexer + +# Build +pnpm build:indexer + +# Start production +pnpm --filter indexer start +``` + +## Environment Variables + +- `APIBARA_TOKEN` - Apibara API token +- `STARKNET_RPC_URL` - StarkNet RPC endpoint +- `DATABASE_URL` - PostgreSQL connection string \ No newline at end of file diff --git a/backend/apps/indexer/package.json b/backend/apps/indexer/package.json new file mode 100644 index 00000000..475f9e9a --- /dev/null +++ b/backend/apps/indexer/package.json @@ -0,0 +1,27 @@ +{ + "name": "indexer", + "version": "0.0.1", + "description": "Apibara indexer for StarkNet Vault Kit events", + "main": "dist/main.js", + "scripts": { + "build": "tsc", + "dev": "ts-node src/main.ts", + "start": "node dist/main.js", + "lint": "eslint \"src/**/*.ts\" --fix" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/db": "workspace:*", + "@forge/core": "workspace:*", + "@forge/logger": "workspace:*", + "@apibara/protocol": "^0.4.9", + "@apibara/starknet": "^0.5.0", + "starknet": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0" + }, + "devDependencies": { + "ts-node": "^10.9.1", + "@types/node": "^20.3.1" + } +} \ No newline at end of file diff --git a/backend/apps/indexer/src/decoder.ts b/backend/apps/indexer/src/decoder.ts new file mode 100644 index 00000000..42ddb51d --- /dev/null +++ b/backend/apps/indexer/src/decoder.ts @@ -0,0 +1,69 @@ +// Convert u256 from two field elements +export function parseU256(data: string[]): bigint { + const low = BigInt(data[0]); + const high = BigInt(data[1]); + return (high << 128n) | low; +} + +// Parse a u64 (1 felt) +export function parseFelt(felt: string): bigint { + return BigInt(felt); +} + +export function decodeRedeemRequestedEvent(rawData: string[]) { + let i = 0; + const owner = parseFelt(rawData[i++]); + const receiver = parseFelt(rawData[i++]); + const shares = parseU256([rawData[i++], rawData[i++]]); + const assets = parseU256([rawData[i++], rawData[i++]]); + const redeemId = parseU256([rawData[i++], rawData[i++]]); + const epoch = parseU256([rawData[i++], rawData[i++]]); + + return { + owner, + receiver, + shares, + assets, + redeemId, + epoch, + }; +} + +export function decodeRedeemClaimedEvent(rawData: string[]) { + let i = 0; + + // Parse ContractAddress value as bigint + const receiver = parseFelt(rawData[i++]); + + // Parse u256 values (each takes two array elements) + const redeemRequestNominal = parseU256([rawData[i++], rawData[i++]]); + const assets = parseU256([rawData[i++], rawData[i++]]); + const redeemId = parseU256([rawData[i++], rawData[i++]]); + const epoch = parseU256([rawData[i++], rawData[i++]]); + + return { + receiver, + redeemRequestNominal, + assets, + redeemId, + epoch, + }; +} + +export function decodeReportEvent(rawData: string[]) { + let i = 0; + const newEpoch = parseU256([rawData[i++], rawData[i++]]); + const newHandledEpochLen = parseU256([rawData[i++], rawData[i++]]); + const totalSupply = parseU256([rawData[i++], rawData[i++]]); + const totalAssets = parseU256([rawData[i++], rawData[i++]]); + const managementFeeShares = parseU256([rawData[i++], rawData[i++]]); + const performanceFeeShares = parseU256([rawData[i++], rawData[i++]]); + return { + newEpoch, + newHandledEpochLen, + totalSupply, + totalAssets, + managementFeeShares, + performanceFeeShares, + }; +} \ No newline at end of file diff --git a/backend/apps/indexer/src/indexer.constants.ts b/backend/apps/indexer/src/indexer.constants.ts new file mode 100644 index 00000000..02bde624 --- /dev/null +++ b/backend/apps/indexer/src/indexer.constants.ts @@ -0,0 +1,4 @@ +export const BATCH_SIZE = 1000; + +export const MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR = 60_000; +export const MAX_RETRY_AFTER_RECONNECT_NO_INTERNAL_ERROR = 20; \ No newline at end of file diff --git a/backend/apps/indexer/src/indexer.service.ts b/backend/apps/indexer/src/indexer.service.ts new file mode 100644 index 00000000..052d4afa --- /dev/null +++ b/backend/apps/indexer/src/indexer.service.ts @@ -0,0 +1,453 @@ +import { + Filter, + FieldElement, + StarkNetCursor, + v1alpha2, +} from "@apibara/starknet"; +import { StreamClient } from "@apibara/protocol"; +import { DataFinality } from "@apibara/protocol/dist/proto/apibara/node/v1alpha2/DataFinality"; +import { hash as starknetHash, validateAndParseAddress } from "starknet"; +import { ConfigService } from "@forge/config"; +import { PrismaService } from "@forge/db"; +import { STRATEGY } from "@forge/core"; +import { + decodeReportEvent, + decodeRedeemRequestedEvent, + decodeRedeemClaimedEvent, +} from "./decoder"; +import { + BATCH_SIZE, + MAX_RETRY_AFTER_RECONNECT_NO_INTERNAL_ERROR, + MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR, +} from "./indexer.constants"; + +interface EventData { + blockNumber: number; + timestamp: number; + transactionHash: string; + hexData: string[]; + eventTypeHex: string; +} + +export class IndexerService { + private static readonly EVENT_KEYS = { + redeemRequested: FieldElement.toHex( + FieldElement.fromBigInt(starknetHash.getSelector("RedeemRequested")) + ), + redeemClaimed: FieldElement.toHex( + FieldElement.fromBigInt(starknetHash.getSelector("RedeemClaimed")) + ), + report: FieldElement.toHex( + FieldElement.fromBigInt(starknetHash.getSelector("Report")) + ), + }; + + public lastBlockIndexedVault = 0; + private url: string; + private apibaraToken: string; + private vaultFe: v1alpha2.IFieldElement; + + private redeemRequestedBuffer: any[] = []; + private redeemClaimedBuffer: any[] = []; + private reportBuffer: any[] = []; + + constructor( + private readonly configService: ConfigService, + private readonly prismaService: PrismaService + ) { + this.url = "mainnet.starknet.a5a.ch"; + this.apibaraToken = this.configService.get("APIBARA_TOKEN"); + + const graceful = async () => { + try { + await this.flushAllBuffers(); + } finally { + process.exit(0); + } + }; + process.on("SIGINT", graceful); + process.on("SIGTERM", graceful); + } + + async run() { + await this.runVaultIndexer(); + } + + async runVaultIndexer() { + const client = new StreamClient({ + url: this.url, + token: this.apibaraToken, + onReconnect: async (err, retryCount) => { + console.error(`[Indexer] Connection lost. Attempt #${retryCount}`, err); + if (err.code !== 13 && err.code !== 14) { + // Code 13 = internal error, 14 = unavailable + return { reconnect: false }; + } + const base = Math.min( + MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR, + 1000 * 2 ** Math.min(retryCount, 6) + ); + const jitter = Math.floor(Math.random() * 500); + await new Promise((r) => setTimeout(r, base + jitter)); + return { + reconnect: retryCount < MAX_RETRY_AFTER_RECONNECT_NO_INTERNAL_ERROR, + }; + }, + }); + + const filterBuilder = Filter.create().withHeader({ weak: false }); + + try { + this.vaultFe = FieldElement.fromBigInt( + validateAndParseAddress(STRATEGY.vault) + ); + } catch (e) { + console.error("Invalid STRATEGY.vault address", { + vault: STRATEGY.vault, + error: e, + }); + throw e; + } + + [ + IndexerService.EVENT_KEYS.redeemRequested, + IndexerService.EVENT_KEYS.redeemClaimed, + IndexerService.EVENT_KEYS.report, + ].forEach((keyHex) => { + const key = FieldElement.fromBigInt(BigInt(keyHex)); + filterBuilder.addEvent((event) => + event + .withFromAddress(this.vaultFe) + .withIncludeTransaction(true) + .withIncludeReceipt(true) + .withKeys([key]) + ); + }); + + let startBlock = STRATEGY.startBlockIndexing; + + const [lastRedeemRequested, lastRedeemClaimed, lastReport] = + await Promise.all([ + this.prismaService.fetchLastRedeemRequested(), + this.prismaService.fetchLastRedeemClaimed(), + this.prismaService.fetchLastReport(), + ]); + + const maxBlock = Math.max( + lastRedeemRequested?.blockNumber || 0, + lastRedeemClaimed?.blockNumber || 0, + lastReport?.blockNumber || 0, + startBlock + ); + + startBlock = maxBlock + 1; + + console.log(`📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`); + + const cursor = StarkNetCursor.createWithBlockNumber(startBlock); + + client.configure({ + filter: filterBuilder.encode(), + finality: DataFinality.DATA_STATUS_ACCEPTED, + cursor, + }); + + for await (const message of client) { + if (message.data?.data) { + for (let item of message.data.data) { + const block = v1alpha2.Block.decode(item); + const blockNumber = block.header?.blockNumber; + const timestamp = block.header?.timestamp?.seconds; + if (!blockNumber) { + throw new Error("No block number"); + } + + const blockNum = +blockNumber; + + if (!timestamp) { + throw new Error("No timestamp in block header"); + } + + console.log( + `Processing block ${blockNum}, lastBlockIndexed: ${this.lastBlockIndexedVault}` + ); + + for (let event of block.events) { + const hash = event.transaction?.meta?.hash; + if (!hash) { + throw new Error("No hash"); + } + const hashHex = FieldElement.toHex(hash); + + const eventType = event.event?.keys?.[0]; + if (!eventType) { + throw new Error("No event type"); + } + const eventTypeHex = FieldElement.toHex(eventType); + + const data = event.event?.data; + if (!data) { + throw new Error("No data"); + } + const hexData: string[] = data.map((item) => { + try { + return FieldElement.toHex(item); + } catch (err) { + throw new Error(`Invalid FieldElement item ${item} ${err}`); + } + }); + + console.log( + `Event: ${this.getEventTypeName(eventTypeHex)}, block: ${blockNum}, tx: ${hashHex}` + ); + + try { + await this.processEvent(eventTypeHex, hexData, { + blockNumber: blockNum, + timestamp: Number(timestamp), + transactionHash: hashHex, + hexData, + eventTypeHex, + }); + } catch (error) { + console.error("Failed to process event:", { + block: blockNum, + tx: hashHex, + error, + }); + throw error; + } + } + + await this.flushAllBuffers(); + + if (this.lastBlockIndexedVault < blockNum) { + this.lastBlockIndexedVault = blockNum; + } + } + } + } + + await this.flushAllBuffers(); + throw new Error("Stream ended without reconnect"); + } + + private formatStarknetAddress(value: bigint): string { + return "0x" + value.toString(16).padStart(64, "0"); + } + + private async processEvent( + eventTypeHex: string, + hexData: string[], + eventData: EventData + ): Promise { + const { blockNumber, timestamp, transactionHash } = eventData; + + if (eventTypeHex === IndexerService.EVENT_KEYS.redeemRequested) { + await this.bufferRedeemRequestedEvent( + hexData, + blockNumber, + timestamp, + transactionHash + ); + } else if (eventTypeHex === IndexerService.EVENT_KEYS.redeemClaimed) { + await this.bufferRedeemClaimedEvent( + hexData, + blockNumber, + timestamp, + transactionHash + ); + } else if (eventTypeHex === IndexerService.EVENT_KEYS.report) { + await this.bufferReportEvent( + hexData, + blockNumber, + timestamp, + transactionHash + ); + } else { + console.log("Unknown event type:", eventTypeHex); + } + } + + private async bufferRedeemRequestedEvent( + hexData: string[], + blockNumber: number, + timestamp: number, + transactionHash: string + ): Promise { + try { + const redeemRequested = decodeRedeemRequestedEvent(hexData); + const data = { + blockNumber, + timestamp, + transactionHash, + owner: validateAndParseAddress( + this.formatStarknetAddress(redeemRequested.owner) + ), + receiver: validateAndParseAddress( + this.formatStarknetAddress(redeemRequested.receiver) + ), + shares: redeemRequested.shares, + assets: redeemRequested.assets, + redeemId: redeemRequested.redeemId, + epoch: redeemRequested.epoch, + }; + + this.redeemRequestedBuffer.push(data); + + if (this.redeemRequestedBuffer.length >= BATCH_SIZE) { + await this.flushRedeemRequestedBuffer(); + } + } catch (error) { + console.error("Failed to process RedeemRequested event:", { + blockNumber, + transactionHash, + error, + }); + } + } + + private async bufferRedeemClaimedEvent( + hexData: string[], + blockNumber: number, + timestamp: number, + transactionHash: string + ): Promise { + try { + const redeemClaimed = decodeRedeemClaimedEvent(hexData); + const data = { + blockNumber, + timestamp, + transactionHash, + receiver: validateAndParseAddress( + this.formatStarknetAddress(redeemClaimed.receiver) + ), + redeemRequestNominal: redeemClaimed.redeemRequestNominal, + assets: redeemClaimed.assets, + redeemId: redeemClaimed.redeemId, + epoch: redeemClaimed.epoch, + }; + + this.redeemClaimedBuffer.push(data); + + if (this.redeemClaimedBuffer.length >= BATCH_SIZE) { + await this.flushRedeemClaimedBuffer(); + } + } catch (error) { + console.error("Failed to process RedeemClaimed event:", { + blockNumber, + transactionHash, + error, + }); + } + } + + private async bufferReportEvent( + hexData: string[], + blockNumber: number, + timestamp: number, + transactionHash: string + ): Promise { + try { + const report = decodeReportEvent(hexData); + const data = { + blockNumber, + timestamp, + transactionHash, + newEpoch: report.newEpoch, + newHandledEpochLen: report.newHandledEpochLen, + totalSupply: report.totalSupply, + totalAssets: report.totalAssets, + managementFeeShares: report.managementFeeShares, + performanceFeeShares: report.performanceFeeShares, + }; + + this.reportBuffer.push(data); + + if (this.reportBuffer.length >= BATCH_SIZE) { + await this.flushReportBuffer(); + } + } catch (error) { + console.error("Failed to process Report event:", { + blockNumber, + transactionHash, + error, + }); + } + } + + private async flushRedeemRequestedBuffer(): Promise { + if (this.redeemRequestedBuffer.length === 0) return; + + try { + await this.prismaService.redeemRequested.createMany({ + data: this.redeemRequestedBuffer, + skipDuplicates: true, + }); + console.log( + `Flushed ${this.redeemRequestedBuffer.length} RedeemRequested events` + ); + this.redeemRequestedBuffer = []; + } catch (err) { + console.error("Failed to flush RedeemRequested buffer:", err); + throw err; + } + } + + private async flushRedeemClaimedBuffer(): Promise { + if (this.redeemClaimedBuffer.length === 0) return; + + try { + await this.prismaService.redeemClaimed.createMany({ + data: this.redeemClaimedBuffer, + skipDuplicates: true, + }); + console.log( + `Flushed ${this.redeemClaimedBuffer.length} RedeemClaimed events` + ); + this.redeemClaimedBuffer = []; + } catch (err) { + console.error("Failed to flush RedeemClaimed buffer:", err); + throw err; + } + } + + private async flushReportBuffer(): Promise { + if (this.reportBuffer.length === 0) return; + + try { + await this.prismaService.report.createMany({ + data: this.reportBuffer, + skipDuplicates: true, + }); + console.log(`Flushed ${this.reportBuffer.length} Report events`); + this.reportBuffer = []; + } catch (err) { + console.error("Failed to flush Report buffer:", err); + throw err; + } + } + + private async flushAllBuffers(): Promise { + await Promise.all([ + this.flushRedeemRequestedBuffer(), + this.flushRedeemClaimedBuffer(), + this.flushReportBuffer(), + ]); + } + + private getEventTypeName(eventTypeHex: string): string { + if (eventTypeHex === IndexerService.EVENT_KEYS.redeemRequested) + return "RedeemRequested"; + if (eventTypeHex === IndexerService.EVENT_KEYS.redeemClaimed) + return "RedeemClaimed"; + if (eventTypeHex === IndexerService.EVENT_KEYS.report) return "Report"; + return "Unknown"; + } + + getStatus() { + return { + lastBlockIndexedVault: this.lastBlockIndexedVault, + }; + } +} \ No newline at end of file diff --git a/backend/apps/indexer/src/main.ts b/backend/apps/indexer/src/main.ts new file mode 100644 index 00000000..ca318d7f --- /dev/null +++ b/backend/apps/indexer/src/main.ts @@ -0,0 +1,34 @@ +import "reflect-metadata"; +import { ConfigService, validate } from "@forge/config"; +import { PrismaService } from "@forge/db"; +import { IndexerService } from "./indexer.service"; + +async function bootstrap() { + // Validate environment variables + const config = validate(process.env); + + // Create services + const configService = new ConfigService({}); + const prismaService = new PrismaService(); + + // Connect to database + await prismaService.$connect(); + + console.log("🔥 Starting StarkNet Vault Kit Indexer..."); + + // Create and start indexer + const indexer = new IndexerService(configService, prismaService); + + try { + await indexer.run(); + } catch (error) { + console.error("❌ Indexer failed:", error); + await prismaService.$disconnect(); + process.exit(1); + } +} + +bootstrap().catch((error) => { + console.error("❌ Bootstrap failed:", error); + process.exit(1); +}); \ No newline at end of file diff --git a/backend/apps/indexer/tsconfig.json b/backend/apps/indexer/tsconfig.json new file mode 100644 index 00000000..10a1485d --- /dev/null +++ b/backend/apps/indexer/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*", "../../libs/*/src/**/*"], + "exclude": ["node_modules", "dist"], + "ts-node": { + "esm": false + } +} \ No newline at end of file diff --git a/backend/apps/relayer/README.md b/backend/apps/relayer/README.md new file mode 100644 index 00000000..59dda7bf --- /dev/null +++ b/backend/apps/relayer/README.md @@ -0,0 +1,28 @@ +# Relayer Service + +Cron job scheduler and worker service for automated vault operations. + +## Features + +- Scheduled job execution +- Database operations +- Cross-chain bridging (planned) +- Vault management automation + +## Development + +```bash +# Run in development mode +pnpm dev:relayer + +# Build +pnpm build:relayer + +# Start production +pnpm --filter relayer start +``` + +## Jobs + +- Example Job: Runs every 5 minutes for demonstration +- Add your custom jobs in `src/jobs/` \ No newline at end of file diff --git a/backend/apps/relayer/package.json b/backend/apps/relayer/package.json new file mode 100644 index 00000000..882f7a10 --- /dev/null +++ b/backend/apps/relayer/package.json @@ -0,0 +1,27 @@ +{ + "name": "relayer", + "version": "0.0.1", + "description": "Relayer service for StarkNet Vault Kit - handles cron jobs and cross-chain operations", + "main": "dist/main.js", + "scripts": { + "build": "tsc", + "dev": "ts-node src/main.ts", + "start": "node dist/main.js", + "lint": "eslint \"src/**/*.ts\" --fix" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/db": "workspace:*", + "@forge/core": "workspace:*", + "@forge/logger": "workspace:*", + "cron": "^3.0.0", + "starknet": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0" + }, + "devDependencies": { + "ts-node": "^10.9.1", + "@types/node": "^20.3.1", + "@types/cron": "^2.0.0" + } +} \ No newline at end of file diff --git a/backend/apps/relayer/src/jobs/example-job.ts b/backend/apps/relayer/src/jobs/example-job.ts new file mode 100644 index 00000000..20a97393 --- /dev/null +++ b/backend/apps/relayer/src/jobs/example-job.ts @@ -0,0 +1,17 @@ +import { Logger } from "@forge/logger"; +import { PrismaService } from "@forge/db"; + +export class ExampleJob { + private logger = Logger.create('ExampleJob'); + + constructor(private prismaService: PrismaService) {} + + async execute(): Promise { + this.logger.log('Executing example job...'); + + // Add your job logic here + // For example, processing vault rebalancing, fee collection, etc. + + this.logger.log('Example job completed'); + } +} \ No newline at end of file diff --git a/backend/apps/relayer/src/main.ts b/backend/apps/relayer/src/main.ts new file mode 100644 index 00000000..3d698822 --- /dev/null +++ b/backend/apps/relayer/src/main.ts @@ -0,0 +1,50 @@ +import "reflect-metadata"; +import { ConfigService, validate } from "@forge/config"; +import { PrismaService } from "@forge/db"; +import { Logger } from "@forge/logger"; +import { ExampleJob } from "./jobs/example-job"; +import { SchedulerService } from "./services/scheduler.service"; + +async function bootstrap() { + const logger = Logger.create("RelayerBootstrap"); + + try { + // Validate environment variables + const config = validate(process.env); + + // Create services + const configService = new ConfigService({}); + const prismaService = new PrismaService(); + + // Connect to database + await prismaService.$connect(); + logger.log("Connected to database"); + + // Create jobs + const exampleJob = new ExampleJob(prismaService); + + // Create and start scheduler + const scheduler = new SchedulerService(exampleJob); + + logger.log("🔥 Starting StarkNet Vault Kit Relayer..."); + scheduler.start(); + + // Handle graceful shutdown + const gracefulShutdown = async (signal: string) => { + logger.log(`Received ${signal}, shutting down gracefully...`); + scheduler.stop(); + await prismaService.$disconnect(); + process.exit(0); + }; + + process.on("SIGINT", () => gracefulShutdown("SIGINT")); + process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); + + logger.log("✅ Relayer started successfully"); + } catch (error) { + logger.error("❌ Bootstrap failed:", error); + process.exit(1); + } +} + +bootstrap(); diff --git a/backend/apps/relayer/src/services/scheduler.service.ts b/backend/apps/relayer/src/services/scheduler.service.ts new file mode 100644 index 00000000..9dc6e828 --- /dev/null +++ b/backend/apps/relayer/src/services/scheduler.service.ts @@ -0,0 +1,42 @@ +import { CronJob } from 'cron'; +import { Logger } from "@forge/logger"; +import { ExampleJob } from '../jobs/example-job'; + +export class SchedulerService { + private logger = Logger.create('SchedulerService'); + private jobs: CronJob[] = []; + + constructor(private exampleJob: ExampleJob) {} + + start(): void { + this.logger.log('Starting scheduler service...'); + + // Example: run example job every 5 minutes + const exampleJobCron = new CronJob( + '*/5 * * * *', + async () => { + try { + await this.exampleJob.execute(); + } catch (error) { + this.logger.error('Example job failed:', error); + } + }, + null, + false, + 'UTC' + ); + + this.jobs.push(exampleJobCron); + + // Start all jobs + this.jobs.forEach(job => job.start()); + + this.logger.log(`Started ${this.jobs.length} scheduled jobs`); + } + + stop(): void { + this.logger.log('Stopping scheduler service...'); + this.jobs.forEach(job => job.stop()); + this.jobs = []; + } +} \ No newline at end of file diff --git a/backend/apps/relayer/tsconfig.json b/backend/apps/relayer/tsconfig.json new file mode 100644 index 00000000..10a1485d --- /dev/null +++ b/backend/apps/relayer/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*", "../../libs/*/src/**/*"], + "exclude": ["node_modules", "dist"], + "ts-node": { + "esm": false + } +} \ No newline at end of file diff --git a/backend/libs/config/package.json b/backend/libs/config/package.json new file mode 100644 index 00000000..46ba1574 --- /dev/null +++ b/backend/libs/config/package.json @@ -0,0 +1,17 @@ +{ + "name": "@forge/config", + "version": "0.0.1", + "description": "Configuration management for StarkNet Vault Kit", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0" + } +} \ No newline at end of file diff --git a/backend/libs/config/src/config.module.ts b/backend/libs/config/src/config.module.ts new file mode 100644 index 00000000..f4c9e875 --- /dev/null +++ b/backend/libs/config/src/config.module.ts @@ -0,0 +1,16 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule as NestConfigModule } from "@nestjs/config"; +import { ConfigService } from "./config.service"; +import { validate } from "./env.validation"; + +@Module({ + imports: [ + NestConfigModule.forRoot({ + isGlobal: true, + validate, + }), + ], + providers: [ConfigService], + exports: [ConfigService], +}) +export class ConfigModule {} \ No newline at end of file diff --git a/backend/libs/config/src/config.service.ts b/backend/libs/config/src/config.service.ts new file mode 100644 index 00000000..a7f6cbc4 --- /dev/null +++ b/backend/libs/config/src/config.service.ts @@ -0,0 +1,10 @@ +import { ConfigService as ConfigServiceSource } from "@nestjs/config"; +import { EnvironmentVariables } from "./env.validation"; + +export class ConfigService extends ConfigServiceSource { + public get( + key: T + ): EnvironmentVariables[T] { + return super.get(key, { infer: true }) as EnvironmentVariables[T]; + } +} \ No newline at end of file diff --git a/backend/libs/config/src/env.validation.ts b/backend/libs/config/src/env.validation.ts new file mode 100644 index 00000000..c045617b --- /dev/null +++ b/backend/libs/config/src/env.validation.ts @@ -0,0 +1,29 @@ +import { plainToClass, Transform } from "class-transformer"; +import { IsNumber, IsOptional, validateSync, IsString } from "class-validator"; + +export class EnvironmentVariables { + @IsOptional() + @IsNumber() + @Transform(({ value }) => Number(value)) + PORT = 3000; + + @IsString() + STARKNET_RPC_URL: string; + + @IsString() + APIBARA_TOKEN: string; +} + +export function validate(config: Record) { + const validatedConfig = plainToClass(EnvironmentVariables, config); + + const validatorOptions = { skipMissingProperties: false }; + const errors = validateSync(validatedConfig, validatorOptions); + + if (errors.length > 0) { + console.error(errors.toString()); + process.exit(1); + } + + return validatedConfig; +} diff --git a/backend/libs/config/src/index.ts b/backend/libs/config/src/index.ts new file mode 100644 index 00000000..853b7711 --- /dev/null +++ b/backend/libs/config/src/index.ts @@ -0,0 +1,3 @@ +export * from './config.service'; +export * from './config.module'; +export * from './env.validation'; \ No newline at end of file diff --git a/backend/libs/config/tsconfig.json b/backend/libs/config/tsconfig.json new file mode 100644 index 00000000..33034f95 --- /dev/null +++ b/backend/libs/config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/libs/core/package.json b/backend/libs/core/package.json new file mode 100644 index 00000000..b15131b8 --- /dev/null +++ b/backend/libs/core/package.json @@ -0,0 +1,15 @@ +{ + "name": "@forge/core", + "version": "0.0.1", + "description": "Core types, DTOs, and constants for StarkNet Vault Kit", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "dependencies": { + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0" + } +} \ No newline at end of file diff --git a/backend/libs/core/src/constants/strategy.ts b/backend/libs/core/src/constants/strategy.ts new file mode 100644 index 00000000..a4d6e3dd --- /dev/null +++ b/backend/libs/core/src/constants/strategy.ts @@ -0,0 +1,6 @@ +import { Strategy } from "../types/strategy"; + +export const STRATEGY: Strategy = { + vault: "0x0000000000000000000000000000000000000000000000000000000000000000", + startBlockIndexing: 12993, +}; \ No newline at end of file diff --git a/backend/libs/core/src/index.ts b/backend/libs/core/src/index.ts new file mode 100644 index 00000000..8075966d --- /dev/null +++ b/backend/libs/core/src/index.ts @@ -0,0 +1,2 @@ +export * from './types/strategy'; +export * from './constants/strategy'; \ No newline at end of file diff --git a/backend/libs/core/src/types/strategy.ts b/backend/libs/core/src/types/strategy.ts new file mode 100644 index 00000000..72dd43b1 --- /dev/null +++ b/backend/libs/core/src/types/strategy.ts @@ -0,0 +1,4 @@ +export interface Strategy { + vault: string; + startBlockIndexing: number; +} \ No newline at end of file diff --git a/backend/libs/core/tsconfig.json b/backend/libs/core/tsconfig.json new file mode 100644 index 00000000..90a106c3 --- /dev/null +++ b/backend/libs/core/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/libs/db/package.json b/backend/libs/db/package.json new file mode 100644 index 00000000..ef687c17 --- /dev/null +++ b/backend/libs/db/package.json @@ -0,0 +1,15 @@ +{ + "name": "@forge/db", + "version": "0.0.1", + "description": "Database client and DAL for StarkNet Vault Kit", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@prisma/client": "^5.0.0" + } +} \ No newline at end of file diff --git a/backend/libs/db/src/index.ts b/backend/libs/db/src/index.ts new file mode 100644 index 00000000..8704fd9b --- /dev/null +++ b/backend/libs/db/src/index.ts @@ -0,0 +1,2 @@ +export * from './prisma.service'; +export * from './prisma.module'; \ No newline at end of file diff --git a/backend/libs/db/src/prisma.module.ts b/backend/libs/db/src/prisma.module.ts new file mode 100644 index 00000000..7856e928 --- /dev/null +++ b/backend/libs/db/src/prisma.module.ts @@ -0,0 +1,8 @@ +import { Module } from "@nestjs/common"; +import { PrismaService } from "./prisma.service"; + +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} \ No newline at end of file diff --git a/backend/libs/db/src/prisma.service.ts b/backend/libs/db/src/prisma.service.ts new file mode 100644 index 00000000..077f96ab --- /dev/null +++ b/backend/libs/db/src/prisma.service.ts @@ -0,0 +1,38 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common"; +import { PrismaClient, RedeemClaimed } from "@prisma/client"; +import { Report, RedeemRequested } from "@prisma/client"; + +@Injectable() +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ + async onModuleInit() { + await this.$connect(); + } + + async onModuleDestroy() { + await this.$disconnect(); + } + + async fetchLastRedeemRequested(): Promise { + const latest = await this.redeemRequested.findFirst({ + orderBy: { blockNumber: "desc" }, + }); + return latest as RedeemRequested | undefined; + } + + async fetchLastReport(): Promise { + const latest = await this.report.findFirst({ + orderBy: { blockNumber: "desc" }, + }); + return latest as Report | undefined; + } + + async fetchLastRedeemClaimed(): Promise { + const latest = await this.redeemClaimed.findFirst({ + orderBy: { blockNumber: "desc" }, + }); + return latest as RedeemClaimed | undefined; + } +} \ No newline at end of file diff --git a/backend/libs/db/tsconfig.json b/backend/libs/db/tsconfig.json new file mode 100644 index 00000000..33034f95 --- /dev/null +++ b/backend/libs/db/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/libs/logger/package.json b/backend/libs/logger/package.json new file mode 100644 index 00000000..b30a38fa --- /dev/null +++ b/backend/libs/logger/package.json @@ -0,0 +1,12 @@ +{ + "name": "@forge/logger", + "version": "0.0.1", + "description": "Logger factory for StarkNet Vault Kit", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "dependencies": {} +} \ No newline at end of file diff --git a/backend/libs/logger/src/index.ts b/backend/libs/logger/src/index.ts new file mode 100644 index 00000000..7754e4e2 --- /dev/null +++ b/backend/libs/logger/src/index.ts @@ -0,0 +1 @@ +export * from './logger'; \ No newline at end of file diff --git a/backend/libs/logger/src/logger.ts b/backend/libs/logger/src/logger.ts new file mode 100644 index 00000000..5c169684 --- /dev/null +++ b/backend/libs/logger/src/logger.ts @@ -0,0 +1,27 @@ +export class Logger { + private context: string; + + constructor(context?: string) { + this.context = context || 'Application'; + } + + log(message: string, ...optionalParams: any[]) { + console.log(`[${this.context}] ${message}`, ...optionalParams); + } + + error(message: string, ...optionalParams: any[]) { + console.error(`[${this.context}] ${message}`, ...optionalParams); + } + + warn(message: string, ...optionalParams: any[]) { + console.warn(`[${this.context}] ${message}`, ...optionalParams); + } + + debug(message: string, ...optionalParams: any[]) { + console.debug(`[${this.context}] ${message}`, ...optionalParams); + } + + static create(context?: string): Logger { + return new Logger(context); + } +} \ No newline at end of file diff --git a/backend/libs/logger/tsconfig.json b/backend/libs/logger/tsconfig.json new file mode 100644 index 00000000..33034f95 --- /dev/null +++ b/backend/libs/logger/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 00000000..ffc99e16 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,55 @@ +{ + "name": "@forge/root", + "version": "0.0.1", + "description": "StarkNet Vault Kit Backend Monorepo", + "author": "", + "private": true, + "license": "UNLICENSED", + "workspaces": [ + "apps/*", + "libs/*" + ], + "scripts": { + "dev:api": "pnpm --filter api dev", + "dev:indexer": "pnpm --filter indexer dev", + "dev:relayer": "pnpm --filter relayer dev", + "dev:all": "concurrently \"pnpm dev:api\" \"pnpm dev:indexer\" \"pnpm dev:relayer\"", + "build": "pnpm --recursive --filter='!@forge/root' run build", + "build:api": "pnpm --filter api build", + "build:indexer": "pnpm --filter indexer build", + "build:relayer": "pnpm --filter relayer build", + "lint": "pnpm --recursive --filter='!@forge/root' run lint", + "test": "pnpm --recursive --filter='!@forge/root' run test", + "migrate:dev": "prisma migrate dev", + "migrate:deploy": "prisma migrate deploy", + "generate": "prisma generate", + "studio": "prisma studio" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "concurrently": "^8.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "prisma": "^5.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + }, + "dependencies": { + "@prisma/client": "^5.0.0" + } +} \ No newline at end of file diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml new file mode 100644 index 00000000..10964317 --- /dev/null +++ b/backend/pnpm-lock.yaml @@ -0,0 +1,6854 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@prisma/client': + specifier: ^5.0.0 + version: 5.22.0(prisma@5.22.0) + devDependencies: + '@nestjs/cli': + specifier: ^10.0.0 + version: 10.4.9 + '@nestjs/schematics': + specifier: ^10.0.0 + version: 10.2.3(chokidar@3.6.0)(typescript@5.9.2) + '@nestjs/testing': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)) + '@types/express': + specifier: ^4.17.17 + version: 4.17.23 + '@types/jest': + specifier: ^29.5.2 + version: 29.5.14 + '@types/node': + specifier: ^20.3.1 + version: 20.19.11 + '@typescript-eslint/eslint-plugin': + specifier: ^8.0.0 + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.40.0(eslint@8.57.1)(typescript@5.9.2) + concurrently: + specifier: ^8.0.0 + version: 8.2.2 + eslint: + specifier: ^8.42.0 + version: 8.57.1 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.1.2(eslint@8.57.1) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2) + jest: + specifier: ^29.5.0 + version: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + prettier: + specifier: ^3.0.0 + version: 3.6.2 + prisma: + specifier: ^5.0.0 + version: 5.22.0 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.1.4 + ts-jest: + specifier: ^29.1.0 + version: 29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)))(typescript@5.9.2) + ts-loader: + specifier: ^9.4.3 + version: 9.5.2(typescript@5.9.2)(webpack@5.97.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.1.3 + version: 5.9.2 + + apps/api: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/core': + specifier: workspace:* + version: link:../../libs/core + '@forge/db': + specifier: workspace:* + version: link:../../libs/db + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + '@nestjs/axios': + specifier: ^3.0.0 + version: 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^3.0.0 + version: 3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + '@nestjs/schedule': + specifier: ^4.0.0 + version: 4.1.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/swagger': + specifier: ^7.0.0 + version: 7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + '@nestjs/terminus': + specifier: ^11.0.0 + version: 11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.2) + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + reflect-metadata: + specifier: ^0.2.0 + version: 0.2.2 + rxjs: + specifier: ^7.8.1 + version: 7.8.2 + starknet: + specifier: ^6.0.0 + version: 6.24.1 + devDependencies: + '@nestjs/cli': + specifier: ^10.0.0 + version: 10.4.9 + '@nestjs/testing': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)) + '@types/jest': + specifier: ^29.5.2 + version: 29.5.14 + jest: + specifier: ^29.5.0 + version: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + ts-jest: + specifier: ^29.1.0 + version: 29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)))(typescript@5.9.2) + + apps/indexer: + dependencies: + '@apibara/protocol': + specifier: ^0.4.9 + version: 0.4.9 + '@apibara/starknet': + specifier: ^0.5.0 + version: 0.5.0 + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/core': + specifier: workspace:* + version: link:../../libs/core + '@forge/db': + specifier: workspace:* + version: link:../../libs/db + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + starknet: + specifier: ^6.0.0 + version: 6.24.1 + devDependencies: + '@types/node': + specifier: ^20.3.1 + version: 20.19.11 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + + apps/relayer: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/core': + specifier: workspace:* + version: link:../../libs/core + '@forge/db': + specifier: workspace:* + version: link:../../libs/db + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + cron: + specifier: ^3.0.0 + version: 3.5.0 + starknet: + specifier: ^6.0.0 + version: 6.24.1 + devDependencies: + '@types/cron': + specifier: ^2.0.0 + version: 2.4.3 + '@types/node': + specifier: ^20.3.1 + version: 20.19.11 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + + libs/config: + dependencies: + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^3.0.0 + version: 3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + + libs/core: + dependencies: + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + + libs/db: + dependencies: + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@prisma/client': + specifier: ^5.0.0 + version: 5.22.0(prisma@5.22.0) + + libs/logger: {} + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@angular-devkit/core@17.3.11': + resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@17.3.11': + resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@17.3.11': + resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@apibara/protocol@0.4.9': + resolution: {integrity: sha512-RTpZ9u8zmDHbgMYDO47lnJlGxUrfCjKUGZ10SmicoN7OkNyAWmVGrwXZYfQJMw81bSObet7vU76iQ1HznyiySA==} + + '@apibara/starknet@0.5.0': + resolution: {integrity: sha512-xzpXMlwdDeBb7IpY9CYKs7uBDz1uz9gb8rR3QgWVx7xipB/fQY6InHldyX5zfLIuBVblZOlenviNrBXOd7Wlsw==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@grpc/grpc-js@1.13.4': + resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@ljharb/through@2.3.14': + resolution: {integrity: sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==} + engines: {node: '>= 0.4'} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@microsoft/tsdoc@0.15.1': + resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + + '@nestjs/axios@3.1.3': + resolution: {integrity: sha512-RZ/63c1tMxGLqyG3iOCVt7A72oy4x1eM6QEhd4KzCYpaVWW0igq0WSREeRoEZhIxRcZfDfIIkvsOMiM7yfVGZQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 + + '@nestjs/cli@10.4.9': + resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} + engines: {node: '>= 16.14'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + + '@nestjs/common@10.4.20': + resolution: {integrity: sha512-hxJxZF7jcKGuUzM9EYbuES80Z/36piJbiqmPy86mk8qOn5gglFebBTvcx7PWVbRNSb4gngASYnefBj/Y2HAzpQ==} + peerDependencies: + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/config@3.3.0': + resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 + + '@nestjs/core@10.4.20': + resolution: {integrity: sha512-kRdtyKA3+Tu70N3RQ4JgmO1E3LzAMs/eppj7SfjabC7TgqNWoS4RLhWl4BqmsNVmjj6D5jgfPVtHtgYkU3AfpQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + + '@nestjs/mapped-types@2.0.5': + resolution: {integrity: sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/platform-express@10.4.20': + resolution: {integrity: sha512-rh97mX3rimyf4xLMLHuTOBKe6UD8LOJ14VlJ1F/PTd6C6ZK9Ak6EHuJvdaGcSFQhd3ZMBh3I6CuujKGW9pNdIg==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + + '@nestjs/schedule@4.1.2': + resolution: {integrity: sha512-hCTQ1lNjIA5EHxeu8VvQu2Ed2DBLS1GSC6uKPYlBiQe6LL9a7zfE9iVSK+zuK8E2odsApteEBmfAQchc8Hx0Gg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + + '@nestjs/schematics@10.2.3': + resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} + peerDependencies: + typescript: '>=4.8.2' + + '@nestjs/swagger@7.4.2': + resolution: {integrity: sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/terminus@11.0.0': + resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/microservices': ^10.0.0 || ^11.0.0 + '@nestjs/mongoose': ^11.0.0 + '@nestjs/sequelize': ^10.0.0 || ^11.0.0 + '@nestjs/typeorm': ^10.0.0 || ^11.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + + '@nestjs/testing@10.4.20': + resolution: {integrity: sha512-nMkRDukDKskdPruM6EsgMq7yJua+CPZM6I6FrLP8yXw8BiVSPv9Nm0CtcGGwt3kgZF9hfxKjGqLjsvVBsv6Vfw==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + + '@noble/curves@1.7.0': + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=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} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nuxtjs/opencollective@0.3.2': + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + '@paralleldrive/cuid2@2.2.2': + resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@prisma/client@5.22.0': + resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==} + engines: {node: '>=16.13'} + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + + '@prisma/debug@5.22.0': + resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==} + + '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': + resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==} + + '@prisma/engines@5.22.0': + resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==} + + '@prisma/fetch-engine@5.22.0': + resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==} + + '@prisma/get-platform@5.22.0': + resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@scure/base@1.2.1': + resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} + + '@scure/starknet@1.1.0': + resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@starknet-io/types-js@0.7.10': + resolution: {integrity: sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==} + + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cron@2.4.3': + resolution: {integrity: sha512-ViRBkoZD9Rk0hGeMdd2GHGaOaZuH9mDmwsE5/Zo53Ftwcvh7h9VJc8lIt2wdgEwS4EW5lbtTX6vlE0idCLPOyA==} + deprecated: This is a stub types definition. cron provides its own type definitions, so you do not need this installed. + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/luxon@3.4.2': + resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@20.19.11': + resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/validator@13.15.2': + resolution: {integrity: sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/eslint-plugin@8.40.0': + resolution: {integrity: sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.40.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.40.0': + resolution: {integrity: sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.40.0': + resolution: {integrity: sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.40.0': + resolution: {integrity: sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.40.0': + resolution: {integrity: sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.40.0': + resolution: {integrity: sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.40.0': + resolution: {integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.40.0': + resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.40.0': + resolution: {integrity: sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.40.0': + resolution: {integrity: sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + abi-wan-kanabi@2.2.4: + resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} + hasBin: true + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + 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'} + + ansi-regex@6.2.0: + resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.3: + resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + 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'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001735: + resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} + + cardinal@2.1.1: + resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} + hasBin: true + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.0: + resolution: {integrity: sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.2: + resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==} + + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + 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'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-json@4.2.5: + resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} + engines: {node: '>= 6'} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + + consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cron@3.2.1: + resolution: {integrity: sha512-w2n5l49GMmmkBFEsH9FIDhjZ1n1QgTMOCMGuQtOXs5veNiosZmso6bQGuqOJSYAXXrG84WQFVneNk+Yt0Ua9iw==} + + cron@3.5.0: + resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.6.0: + resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.207: + resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.2: + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.4: + resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.0.6: + resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fetch-cookie@3.0.1: + resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-type@20.4.1: + resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fork-ts-checker-webpack-plugin@9.0.2: + resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + google-protobuf@3.21.4: + resolution: {integrity: sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@8.2.6: + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} + + inquirer@9.2.15: + resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} + engines: {node: '>=18'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + libphonenumber-js@1.12.12: + resolution: {integrity: sha512-aWVR6xXYYRvnK0v/uIwkf5Lthq9Jpn0N8TISW/oDTWlYB2sOimuiLn9Q26aUw4KxkJoiT8ACdiw44Y8VwKFIfQ==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lossless-json@4.1.1: + resolution: {integrity: sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + luxon@3.5.0: + resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + engines: {node: '>=12'} + + magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + 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 + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@2.0.2: + resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} + engines: {node: '>= 10.16.0'} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + 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} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + 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-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + 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'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.1: + resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prisma@5.22.0: + resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==} + engines: {node: '>=16.13'} + hasBin: true + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redeyed@2.1.1: + resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.2: + resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} + engines: {node: '>= 10.13.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + 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'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + 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@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + starknet@6.24.1: + resolution: {integrity: sha512-g7tiCt73berhcNi41otlN3T3kxZnIvZhMi8WdC21Y6GC6zoQgbI2z1t7JAZF9c4xZiomlanwVnurcpyfEdyMpg==} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + 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'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + + superagent@10.2.3: + resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} + engines: {node: '>=14.18.0'} + + supertest@7.1.4: + resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==} + engines: {node: '>=14.18.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swagger-ui-dist@5.17.14: + resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + + terser-webpack-plugin@5.3.14: + resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.43.1: + resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-jest@29.4.1: + resolution: {integrity: sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + ts-loader@9.5.2: + resolution: {integrity: sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.4.1: + resolution: {integrity: sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==} + engines: {node: '>=18'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.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==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@11.0.3: + resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + validator@13.15.15: + resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + watchpack@2.4.4: + resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.3.3: + resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + engines: {node: '>=10.13.0'} + + webpack@5.97.1: + resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + 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'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@angular-devkit/core@17.3.11(chokidar@3.6.0)': + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + jsonc-parser: 3.2.1 + picomatch: 4.0.1 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 3.6.0 + + '@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + ansi-colors: 4.1.3 + inquirer: 9.2.15 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/schematics@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + jsonc-parser: 3.2.1 + magic-string: 0.30.8 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@apibara/protocol@0.4.9': + dependencies: + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 + google-protobuf: 3.21.4 + long: 5.3.2 + protobufjs: 7.5.4 + + '@apibara/starknet@0.5.0': + dependencies: + google-protobuf: 3.21.4 + long: 5.3.2 + protobufjs: 7.5.4 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.3': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/runtime@7.28.3': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.3': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bcoe/v8-coverage@0.2.3': {} + + '@borewit/text-codec@0.1.1': {} + + '@colors/colors@1.5.0': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@grpc/grpc-js@1.13.4': + dependencies: + '@grpc/proto-loader': 0.7.15 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.19.11 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.30 + '@types/node': 20.19.11 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.2.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.28.3 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.30 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.19.11 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@ljharb/through@2.3.14': + dependencies: + call-bind: 1.0.8 + + '@lukeed/csprng@1.1.0': {} + + '@microsoft/tsdoc@0.15.1': {} + + '@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + axios: 1.11.0 + rxjs: 7.8.2 + + '@nestjs/cli@10.4.9': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) + '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2) + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1) + glob: 10.4.5 + inquirer: 8.2.6 + node-emoji: 1.11.0 + ora: 5.4.1 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.2.0 + typescript: 5.7.2 + webpack: 5.97.1 + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - esbuild + - uglify-js + - webpack-cli + + '@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + file-type: 20.4.1 + iterare: 1.2.1 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.2 + transitivePeerDependencies: + - supports-color + + '@nestjs/config@3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 16.4.5 + dotenv-expand: 10.0.0 + lodash: 4.17.21 + rxjs: 7.8.2 + + '@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nuxtjs/opencollective': 0.3.2 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + transitivePeerDependencies: + - encoding + + '@nestjs/mapped-types@2.0.5(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.2 + + '@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + body-parser: 1.20.3 + cors: 2.8.5 + express: 4.21.2 + multer: 2.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@nestjs/schedule@4.1.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cron: 3.2.1 + uuid: 11.0.3 + + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.7.2)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.7.2 + transitivePeerDependencies: + - chokidar + + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.9.2)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.9.2 + transitivePeerDependencies: + - chokidar + + '@nestjs/swagger@7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': + dependencies: + '@microsoft/tsdoc': 0.15.1 + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + js-yaml: 4.1.0 + lodash: 4.17.21 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.17.14 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.2 + + '@nestjs/terminus@11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optionalDependencies: + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 + '@nestjs/axios': 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@prisma/client': 5.22.0(prisma@5.22.0) + + '@nestjs/testing@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20))': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 + optionalDependencies: + '@nestjs/platform-express': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + + '@noble/hashes@1.6.0': {} + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@nuxtjs/opencollective@0.3.2': + dependencies: + chalk: 4.1.2 + consola: 2.15.3 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@paralleldrive/cuid2@2.2.2': + dependencies: + '@noble/hashes': 1.8.0 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@prisma/client@5.22.0(prisma@5.22.0)': + optionalDependencies: + prisma: 5.22.0 + + '@prisma/debug@5.22.0': {} + + '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {} + + '@prisma/engines@5.22.0': + dependencies: + '@prisma/debug': 5.22.0 + '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 + '@prisma/fetch-engine': 5.22.0 + '@prisma/get-platform': 5.22.0 + + '@prisma/fetch-engine@5.22.0': + dependencies: + '@prisma/debug': 5.22.0 + '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 + '@prisma/get-platform': 5.22.0 + + '@prisma/get-platform@5.22.0': + dependencies: + '@prisma/debug': 5.22.0 + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@scure/base@1.2.1': {} + + '@scure/starknet@1.1.0': + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@starknet-io/types-js@0.7.10': {} + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.1 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.2 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.2 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.19.11 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.11 + + '@types/cron@2.4.3': + dependencies: + cron: 3.5.0 + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 20.19.11 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 20.19.11 + + '@types/http-errors@2.0.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/json-schema@7.0.15': {} + + '@types/luxon@3.4.2': {} + + '@types/mime@1.3.5': {} + + '@types/node@20.19.11': + dependencies: + undici-types: 6.21.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.19.11 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.19.11 + '@types/send': 0.17.5 + + '@types/stack-utils@2.0.3': {} + + '@types/validator@13.15.2': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.40.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/type-utils': 8.40.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.40.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.40.0 + debug: 4.4.1 + eslint: 8.57.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.40.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) + '@typescript-eslint/types': 8.40.0 + debug: 4.4.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.40.0': + dependencies: + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 + + '@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.2)': + dependencies: + typescript: 5.9.2 + + '@typescript-eslint/type-utils@8.40.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@8.57.1)(typescript@5.9.2) + debug: 4.4.1 + eslint: 8.57.1 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.40.0': {} + + '@typescript-eslint/typescript-estree@8.40.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/project-service': 8.40.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/visitor-keys': 8.40.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.40.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.40.0 + '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) + eslint: 8.57.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.40.0': + dependencies: + '@typescript-eslint/types': 8.40.0 + eslint-visitor-keys: 4.2.1 + + '@ungap/structured-clone@1.3.0': {} + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + abi-wan-kanabi@2.2.4: + dependencies: + ansicolors: 0.3.2 + cardinal: 2.1.1 + fs-extra: 10.1.0 + yargs: 17.7.2 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-formats@2.1.1(ajv@8.12.0): + optionalDependencies: + ajv: 8.12.0 + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv-keywords@5.1.0(ajv@8.17.1): + dependencies: + ajv: 8.17.1 + fast-deep-equal: 3.1.3 + + 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 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.6 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + ansicolors@0.3.2: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + append-field@1.0.0: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + array-timsort@1.0.3: {} + + asap@2.0.6: {} + + asynckit@0.4.0: {} + + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-jest@29.7.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.28.3) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.3) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.3) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3) + + babel-preset-jest@29.6.3(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.3: + dependencies: + caniuse-lite: 1.0.30001735 + electron-to-chromium: 1.5.207 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.3) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + 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 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001735: {} + + cardinal@2.1.1: + dependencies: + ansicolors: 0.3.2 + redeyed: 2.1.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.0: {} + + char-regex@1.0.2: {} + + chardet@0.7.0: {} + + check-disk-space@3.4.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chrome-trace-event@1.0.4: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + class-transformer@0.5.1: {} + + class-validator@0.14.2: + dependencies: + '@types/validator': 13.15.2 + libphonenumber-js: 1.12.12 + validator: 13.15.15 + + cli-boxes@2.2.1: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@3.0.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 + + clone@1.0.4: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + commander@4.1.1: {} + + comment-json@4.2.5: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + + component-emitter@1.3.1: {} + + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.3 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + consola@2.15.3: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + cookiejar@2.1.4: {} + + core-util-is@1.0.3: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@8.3.6(typescript@5.7.2): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.7.2 + + create-jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-require@1.1.1: {} + + cron@3.2.1: + dependencies: + '@types/luxon': 3.4.2 + luxon: 3.5.0 + + cron@3.5.0: + dependencies: + '@types/luxon': 3.4.2 + luxon: 3.5.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.28.3 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + dedent@1.6.0: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-newline@3.1.0: {} + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + diff-sequences@29.6.3: {} + + diff@4.0.2: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv-expand@10.0.0: {} + + dotenv@16.4.5: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.207: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2): + dependencies: + eslint: 8.57.1 + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 9.1.2(eslint@8.57.1) + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + events@3.3.0: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fetch-cookie@3.0.1: + dependencies: + set-cookie-parser: 2.7.1 + tough-cookie: 4.1.4 + + fflate@0.8.2: {} + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-type@20.4.1: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.4.1 + transitivePeerDependencies: + - supports-color + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1): + dependencies: + '@babel/code-frame': 7.27.1 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.7.2) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.2 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.7.2 + tapable: 2.2.2 + typescript: 5.7.2 + webpack: 5.97.1 + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.2.2 + dezalgo: 1.0.4 + once: 1.4.0 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-monkey@1.1.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + google-protobuf@3.21.4: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@4.0.0: {} + + has-own-prop@2.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + html-escaper@2.0.2: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inquirer@8.2.6: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + + inquirer@9.2.15: + dependencies: + '@ljharb/through': 2.3.14 + ansi-escapes: 4.3.2 + chalk: 5.6.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-stream@2.0.1: {} + + is-unicode-supported@0.1.0: {} + + isexe@2.0.0: {} + + isomorphic-fetch@3.0.0: + dependencies: + node-fetch: 2.7.0 + whatwg-fetch: 3.6.20 + transitivePeerDependencies: + - encoding + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterare@1.2.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.6.0 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + dependencies: + '@babel/core': 7.28.3 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.3) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.19.11 + ts-node: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.19.11 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.27.1 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.10 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) + '@babel/types': 7.28.2 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.11 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@27.5.1: + dependencies: + '@types/node': 20.19.11 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest-worker@29.7.0: + dependencies: + '@types/node': 20.19.11 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.2.1: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + libphonenumber-js@1.12.12: {} + + lines-and-columns@1.2.4: {} + + loader-runner@4.3.0: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + long@5.3.2: {} + + lossless-json@4.1.1: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + luxon@3.5.0: {} + + magic-string@0.30.8: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.1.0 + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + ms@2.0.0: {} + + ms@2.1.3: {} + + multer@2.0.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + mute-stream@0.0.8: {} + + mute-stream@1.0.0: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + neo-async@2.6.2: {} + + node-abort-controller@3.1.1: {} + + node-emoji@1.11.0: + dependencies: + lodash: 4.17.21 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-int64@0.4.0: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + os-tmpdir@1.0.2: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + pako@2.1.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@0.1.12: {} + + path-to-regexp@3.3.0: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.1: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pluralize@8.0.0: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.2: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prisma@5.22.0: + dependencies: + '@prisma/engines': 5.22.0 + optionalDependencies: + fsevents: 2.3.3 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.19.11 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@1.1.0: {} + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react-is@18.3.1: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + redeyed@2.1.1: + dependencies: + esprima: 4.0.1 + + reflect-metadata@0.2.2: {} + + repeat-string@1.6.1: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + requires-port@1.0.0: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-async@2.4.1: {} + + run-async@3.0.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + schema-utils@4.3.2: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + ajv-keywords: 5.1.0(ajv@8.17.1) + + semver@6.3.1: {} + + semver@7.7.2: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + 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 + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + 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@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + source-map@0.7.6: {} + + spawn-command@0.0.2: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + starknet@6.24.1: + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + '@scure/base': 1.2.1 + '@scure/starknet': 1.1.0 + abi-wan-kanabi: 2.2.4 + fetch-cookie: 3.0.1 + isomorphic-fetch: 3.0.0 + lossless-json: 4.1.1 + pako: 2.1.0 + starknet-types-07: '@starknet-io/types-js@0.7.10' + ts-mixer: 6.0.4 + transitivePeerDependencies: + - encoding + + statuses@2.0.1: {} + + streamsearch@1.1.0: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.2.0 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + + superagent@10.2.3: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.1 + fast-safe-stringify: 2.1.1 + form-data: 4.0.4 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.14.0 + transitivePeerDependencies: + - supports-color + + supertest@7.1.4: + dependencies: + methods: 1.1.2 + superagent: 10.2.3 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swagger-ui-dist@5.17.14: {} + + symbol-observable@4.0.0: {} + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + tapable@2.2.2: {} + + terser-webpack-plugin@5.3.14(webpack@5.97.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.43.1 + webpack: 5.97.1 + + terser@5.43.1: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + text-table@0.2.0: {} + + through@2.3.8: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@0.0.3: {} + + tree-kill@1.2.2: {} + + ts-api-utils@2.1.0(typescript@5.9.2): + dependencies: + typescript: 5.9.2 + + ts-jest@29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)))(typescript@5.9.2): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.9.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.28.3 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.3) + jest-util: 29.7.0 + + ts-loader@9.5.2(typescript@5.9.2)(webpack@5.97.1): + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.18.3 + micromatch: 4.0.8 + semver: 7.7.2 + source-map: 0.7.6 + typescript: 5.9.2 + webpack: 5.97.1 + + ts-mixer@6.0.4: {} + + ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.11 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig-paths-webpack-plugin@4.2.0: + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.18.3 + tapable: 2.2.2 + tsconfig-paths: 4.2.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typedarray@0.0.6: {} + + typescript@5.7.2: {} + + typescript@5.9.2: {} + + uglify-js@3.19.3: + optional: true + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + uint8array-extras@1.4.1: {} + + undici-types@6.21.0: {} + + universalify@0.2.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.1.3(browserslist@4.25.3): + dependencies: + browserslist: 4.25.3 + escalade: 3.2.0 + picocolors: 1.1.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: {} + + utils-merge@1.0.1: {} + + uuid@11.0.3: {} + + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + validator@13.15.15: {} + + vary@1.1.2: {} + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + watchpack@2.4.4: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + + webpack-node-externals@3.0.0: {} + + webpack-sources@3.3.3: {} + + webpack@5.97.1: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + browserslist: 4.25.3 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.14(webpack@5.97.1) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + whatwg-fetch@3.6.20: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + word-wrap@1.2.5: {} + + wordwrap@1.0.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 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/backend/pnpm-workspace.yaml b/backend/pnpm-workspace.yaml new file mode 100644 index 00000000..f911ef41 --- /dev/null +++ b/backend/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'apps/*' + - 'libs/*' \ No newline at end of file diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 00000000..42d09619 --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,68 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Report { + id String @id @default(uuid()) + blockNumber Int + timestamp Int + transactionHash String + + newEpoch BigInt + newHandledEpochLen BigInt + totalSupply BigInt + totalAssets BigInt + managementFeeShares BigInt + performanceFeeShares BigInt + + createdAt DateTime @default(now()) + + @@unique([newEpoch]) +} + + +model RedeemRequested { + id String @id @default(uuid()) + blockNumber Int + timestamp Int + transactionHash String + + owner String + receiver String + shares BigInt + assets BigInt + redeemId BigInt + epoch BigInt + + createdAt DateTime @default(now()) + + @@unique([redeemId]) +} + +model RedeemClaimed { + id String @id @default(uuid()) + blockNumber Int + timestamp Int + transactionHash String + + receiver String + redeemRequestNominal BigInt + assets BigInt + redeemId BigInt + epoch BigInt + + createdAt DateTime @default(now()) + + @@unique([redeemId]) +} + +model IndexerStatus { + id Int @id @default(1) + currentBlock Int + updatedAt DateTime @updatedAt +} diff --git a/backend/tsconfig.base.json b/backend/tsconfig.base.json new file mode 100644 index 00000000..d69d88c5 --- /dev/null +++ b/backend/tsconfig.base.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "baseUrl": ".", + "paths": { + "@forge/db": ["libs/db/src"], + "@forge/db/*": ["libs/db/src/*"], + "@forge/config": ["libs/config/src"], + "@forge/config/*": ["libs/config/src/*"], + "@forge/logger": ["libs/logger/src"], + "@forge/logger/*": ["libs/logger/src/*"], + "@forge/core": ["libs/core/src"], + "@forge/core/*": ["libs/core/src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 00000000..b263fc33 --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["apps/**/*", "libs/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file From 993f2e202081186b0b6219f261a729798b711f11 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Sat, 23 Aug 2025 20:25:04 +0100 Subject: [PATCH 06/54] enhance backend infrastructure - Enhanced @forge/logger with Winston-based structured logging - Added daily log rotation, error-specific files, and JSON/simple formats - Implemented comprehensive logging in indexer service with event tracking - Added production-ready logging to API service with request tracing - Removed deprecated relayer and core packages - Updated package dependencies and configurations - Added Docker support with API and indexer containers - Enhanced environment validation and configuration management --- backend/.env.example | 17 +- backend/Dockerfile.api | 31 + backend/Dockerfile.indexer | 29 + backend/README.md | 175 +- backend/apps/api/package.json | 2 - backend/apps/api/src/app.controller.ts | 59 +- backend/apps/api/src/app.module.ts | 2 - backend/apps/api/src/app.service.ts | 225 ++- backend/apps/api/src/main.ts | 89 +- backend/apps/api/src/starknet/abis/vault.json | 1581 +++++++++++++++++ .../apps/api/src/starknet/starknet.service.ts | 130 +- backend/apps/api/src/types/strategy.ts | 14 + backend/apps/indexer/package.json | 1 - backend/apps/indexer/src/indexer.service.ts | 91 +- backend/apps/indexer/src/main.ts | 64 +- backend/apps/relayer/README.md | 28 - backend/apps/relayer/package.json | 27 - backend/apps/relayer/src/jobs/example-job.ts | 17 - backend/apps/relayer/src/main.ts | 50 - .../relayer/src/services/scheduler.service.ts | 42 - backend/apps/relayer/tsconfig.json | 11 - backend/docker-compose.yml | 48 + backend/libs/config/src/config.service.ts | 10 +- backend/libs/config/src/env.validation.ts | 36 +- backend/libs/core/package.json | 15 - backend/libs/core/src/constants/strategy.ts | 6 - backend/libs/core/src/index.ts | 2 - backend/libs/core/src/types/strategy.ts | 4 - backend/libs/core/tsconfig.json | 8 - backend/libs/db/src/prisma.service.ts | 41 +- backend/libs/logger/package.json | 8 +- backend/libs/logger/src/index.ts | 3 +- backend/libs/logger/src/logger.ts | 161 +- backend/package.json | 14 +- backend/pnpm-lock.yaml | 317 ++-- backend/tsconfig.base.json | 5 +- packages/vault/src/vault/interface.cairo | 2 + packages/vault/src/vault/vault.cairo | 21 + 38 files changed, 2780 insertions(+), 606 deletions(-) create mode 100644 backend/Dockerfile.api create mode 100644 backend/Dockerfile.indexer create mode 100644 backend/apps/api/src/starknet/abis/vault.json create mode 100644 backend/apps/api/src/types/strategy.ts delete mode 100644 backend/apps/relayer/README.md delete mode 100644 backend/apps/relayer/package.json delete mode 100644 backend/apps/relayer/src/jobs/example-job.ts delete mode 100644 backend/apps/relayer/src/main.ts delete mode 100644 backend/apps/relayer/src/services/scheduler.service.ts delete mode 100644 backend/apps/relayer/tsconfig.json create mode 100644 backend/docker-compose.yml delete mode 100644 backend/libs/core/package.json delete mode 100644 backend/libs/core/src/constants/strategy.ts delete mode 100644 backend/libs/core/src/index.ts delete mode 100644 backend/libs/core/src/types/strategy.ts delete mode 100644 backend/libs/core/tsconfig.json diff --git a/backend/.env.example b/backend/.env.example index 78104baf..f23418c6 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,14 +1,17 @@ # Database -DATABASE_URL="postgresql://username:password@localhost:5432/starknet_vault_db?schema=public" +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/starknet_vault_kit" # Server PORT=3000 -# StarkNet -STARKNET_RPC_URL="https://starknet-sepolia.public.blastapi.io/rpc/v0_7" +# StarkNet RPC +RPC_URL="https://starknet-mainnet.public.blastapi.io" -# Indexer -APIBARA_TOKEN="your_apibara_token_here" +# Vault Contract Address (replace with actual address) +VAULT_ADDRESS="0x0000000000000000000000000000000000000000000000000000000000000000" -# Optional: Production URLs -# STARKNET_RPC_URL="https://starknet-mainnet.public.blastapi.io/rpc/v0_7" \ No newline at end of file +# Indexing start block +START_BLOCK=12993 + +# Apibara token for indexing (required for indexer) +APIBARA_TOKEN="your_apibara_token_here" \ No newline at end of file diff --git a/backend/Dockerfile.api b/backend/Dockerfile.api new file mode 100644 index 00000000..09744202 --- /dev/null +++ b/backend/Dockerfile.api @@ -0,0 +1,31 @@ +FROM node:18-alpine + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/api/package.json ./apps/api/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/core/package.json ./libs/core/ +COPY libs/logger/package.json ./libs/logger/ + +# Install pnpm and dependencies +RUN npm install -g pnpm +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY prisma ./prisma/ +COPY apps/api ./apps/api/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client +RUN pnpm run prisma:generate + +# Build the application +RUN pnpm run build:api + +EXPOSE 3000 + +CMD ["pnpm", "run", "start:api"] \ No newline at end of file diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer new file mode 100644 index 00000000..0fa81d72 --- /dev/null +++ b/backend/Dockerfile.indexer @@ -0,0 +1,29 @@ +FROM node:18-alpine + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/indexer/package.json ./apps/indexer/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/core/package.json ./libs/core/ +COPY libs/logger/package.json ./libs/logger/ + +# Install pnpm and dependencies +RUN npm install -g pnpm +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY prisma ./prisma/ +COPY apps/indexer ./apps/indexer/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client +RUN pnpm run prisma:generate + +# Build the application +RUN pnpm run build:indexer + +CMD ["pnpm", "run", "start:indexer"] \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index f6b97660..58469cc1 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,141 +1,122 @@ -# StarkNet Vault Kit Backend +# StarkNet Vault Kit Backend - OSS Version -A monorepo containing the backend services for the StarkNet Vault Kit project. +**This is a reference backend (read-only) for the StarkNet Vault Kit.** -## Architecture +It provides minimal functionality to index vault events and expose basic API endpoints. This OSS version does not include premium features like pricing, automated redeems, SLA monitoring, or advanced analytics. -This project is organized as a monorepo with shared libraries and independent applications: +**For production deployments with full features, see our premium services.** -### Applications (`apps/`) +## What's Included -- **`api`** - NestJS HTTP API service -- **`indexer`** - Apibara event indexer -- **`relayer`** - Cron job worker for automated operations +### Applications -### Libraries (`libs/`) +- **`api`** - Minimal HTTP API with 4 endpoints +- **`indexer`** - Event indexer for vault reports and redeems -- **`@forge/core`** - Core types, DTOs, and constants -- **`@forge/config`** - Configuration management with validation -- **`@forge/db`** - Prisma client and database access layer +### Libraries + +- **`@forge/config`** - Configuration management +- **`@forge/db`** - Prisma database client - **`@forge/logger`** - Logging utilities -## Getting Started +## API Endpoints (4 total) -### Prerequisites +1. `GET /health` - Health check (`{ status: "ok" }`) +2. `GET /pending-redeems/:address` - Pending redeems for an address (with limit/offset) +3. `GET /reports/last` - Latest report from database +4. `GET /redeems/:id` - Redeem details by ID -- Node.js 18+ -- pnpm (recommended) or yarn/npm with workspaces -- PostgreSQL database -- StarkNet RPC access -- Apibara API token (for indexer) +## Events Indexed (3 total) -### Installation +The indexer only tracks these events: -```bash -# Install all dependencies -pnpm install +1. **Report** - Vault reports with epoch, supply, and assets +2. **RedeemRequested** - User redeem requests +3. **RedeemClaimed** - Completed redeem claims -# Generate Prisma client -pnpm generate +## Database Schema (3 models + status) -# Run database migrations -pnpm migrate:dev -``` +- `Report` +- `RedeemRequested` +- `RedeemClaimed` +- `IndexerStatus` -### Development +## Quick Start with Docker -```bash -# Run all services in development mode -pnpm dev:all +1. **Clone and setup:** -# Run individual services -pnpm dev:api -pnpm dev:indexer -pnpm dev:relayer +```bash +cp .env.example .env +# Edit .env with your configuration ``` -### Building +2. **Run with Docker Compose:** ```bash -# Build all packages -pnpm build - -# Build individual packages -pnpm build:api -pnpm build:indexer -pnpm build:relayer +docker-compose up -d ``` -### Database +This starts: -```bash -# Generate Prisma client -pnpm generate +- PostgreSQL database +- Indexer service +- API service on port 3000 -# Run migrations -pnpm migrate:dev +3. **Check health:** -# Open Prisma Studio -pnpm studio +```bash +curl http://localhost:3000/health ``` -## Environment Variables +## Manual Setup -Create a `.env` file in the root with: +### Prerequisites + +- Node.js 18+ +- pnpm +- PostgreSQL +- Apibara token (for indexing) + +### Installation ```bash -DATABASE_URL="postgresql://user:password@localhost:5432/starknet_vault_kit" -STARKNET_RPC_URL="https://starknet-sepolia.public.blastapi.io/rpc/v0_7" -APIBARA_TOKEN="your_apibara_token" -PORT=3000 -``` +# Install dependencies +pnpm install -## Project Structure +# Setup database +pnpm prisma:generate +pnpm prisma:migrate:deploy -``` -. -├── apps/ -│ ├── api/ # NestJS HTTP API -│ ├── indexer/ # Apibara event indexer -│ └── relayer/ # Cron job worker -├── libs/ -│ ├── core/ # Shared types and constants -│ ├── config/ # Configuration management -│ ├── db/ # Database client and DAL -│ └── logger/ # Logging utilities -├── prisma/ -│ └── schema.prisma # Database schema -├── package.json # Root package with workspace scripts -├── pnpm-workspace.yaml # pnpm workspace configuration -└── tsconfig.base.json # Base TypeScript configuration +# Build +pnpm build ``` -## Scripts +### Run Services -- `pnpm dev:all` - Run all services in development -- `pnpm build` - Build all packages -- `pnpm lint` - Lint all packages -- `pnpm test` - Run tests for all packages -- `pnpm migrate:dev` - Run database migrations -- `pnpm generate` - Generate Prisma client -- `pnpm studio` - Open Prisma Studio +```bash +# Start API +pnpm start:api -## Services +# Start Indexer (separate terminal) +pnpm start:indexer +``` -### API Service (`apps/api`) +## Configuration -- RESTful API endpoints -- Swagger documentation at `/api` -- Health checks at `/` and `/health` +Copy `.env.example` to `.env` and configure: + +```bash +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/starknet_vault_kit" +RPC_URL="https://starknet-mainnet.public.blastapi.io" +VAULT_ADDRESS="0x..." # Your vault contract address +START_BLOCK=12993 +APIBARA_TOKEN="your_token_here" +``` -### Indexer Service (`apps/indexer`) +## License -- Real-time StarkNet event streaming via Apibara -- Automatic event decoding and database persistence -- Vault-specific event tracking (RedeemRequested, RedeemClaimed, Report) +This project is licensed for reference use only. For production deployments with full features and support, please contact us. -### Relayer Service (`apps/relayer`) +--- -- Scheduled job execution with cron -- Automated vault operations and maintenance -- Cross-chain bridging (planned) +_Built for the StarkNet ecosystem 🚀_ diff --git a/backend/apps/api/package.json b/backend/apps/api/package.json index aca9b8dc..dd6dd02f 100644 --- a/backend/apps/api/package.json +++ b/backend/apps/api/package.json @@ -13,14 +13,12 @@ "dependencies": { "@forge/config": "workspace:*", "@forge/db": "workspace:*", - "@forge/core": "workspace:*", "@forge/logger": "workspace:*", "@nestjs/axios": "^3.0.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", - "@nestjs/schedule": "^4.0.0", "@nestjs/swagger": "^7.0.0", "@nestjs/terminus": "^11.0.0", "class-transformer": "^0.5.1", diff --git a/backend/apps/api/src/app.controller.ts b/backend/apps/api/src/app.controller.ts index 422400ed..cda78b68 100644 --- a/backend/apps/api/src/app.controller.ts +++ b/backend/apps/api/src/app.controller.ts @@ -1,25 +1,54 @@ -import { Controller, Get } from "@nestjs/common"; -import { ApiTags, ApiResponse } from "@nestjs/swagger"; +import { Controller, Get, Param, Query } from "@nestjs/common"; +import { ApiTags, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger"; import { AppService } from "./app.service"; +import { PendingRedeem } from "./types/strategy"; -@ApiTags("health") +@ApiTags("api") @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() - @ApiResponse({ status: 200, description: "Health check endpoint" }) - getHello(): string { - return this.appService.getHello(); + @ApiResponse({ status: 200, description: "API information and available endpoints" }) + getApiInfo() { + return this.appService.getApiInfo(); } - @Get("health") - @ApiResponse({ status: 200, description: "Application health status" }) - getHealth() { - return { - status: "ok", - timestamp: new Date().toISOString(), - uptime: process.uptime(), - }; + @Get("pending-redeems/:address") + @ApiParam({ name: "address", description: "User address" }) + @ApiQuery({ + name: "limit", + required: false, + description: "Number of pending redeems to return", + type: Number, + }) + @ApiQuery({ + name: "offset", + required: false, + description: "Number of pending redeems to skip", + type: Number, + }) + @ApiResponse({ status: 200, description: "Pending redeems for the address" }) + async getPendingRedeems( + @Param("address") address: string, + @Query("limit") limit?: string, + @Query("offset") offset?: string + ): Promise { + const limitNumber = limit ? parseInt(limit, 10) : undefined; + const offsetNumber = offset ? parseInt(offset, 10) : undefined; + return this.appService.getPendingRedeems(address, limitNumber, offsetNumber); } -} \ No newline at end of file + + @Get("reports/last") + @ApiResponse({ status: 200, description: "Latest report from database" }) + async getLastReport() { + return this.appService.getLastReport(); + } + + @Get("redeems/:id") + @ApiParam({ name: "id", description: "Redeem ID" }) + @ApiResponse({ status: 200, description: "Redeem details by ID" }) + async getRedeemById(@Param("id") id: string) { + return this.appService.getRedeemById(id); + } +} diff --git a/backend/apps/api/src/app.module.ts b/backend/apps/api/src/app.module.ts index a759a5d6..1a479dd3 100644 --- a/backend/apps/api/src/app.module.ts +++ b/backend/apps/api/src/app.module.ts @@ -1,5 +1,4 @@ import { Module } from '@nestjs/common'; -import { ScheduleModule } from '@nestjs/schedule'; import { ConfigModule } from '@forge/config'; import { PrismaModule } from '@forge/db'; import { StarknetModule } from './starknet'; @@ -10,7 +9,6 @@ import { AppService } from './app.service'; @Module({ imports: [ ConfigModule, - ScheduleModule.forRoot(), PrismaModule, StarknetModule, HealthModule, diff --git a/backend/apps/api/src/app.service.ts b/backend/apps/api/src/app.service.ts index 09567780..e3c682e1 100644 --- a/backend/apps/api/src/app.service.ts +++ b/backend/apps/api/src/app.service.ts @@ -1,8 +1,225 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable, OnModuleInit } from "@nestjs/common"; +import { validateAndParseAddress } from "starknet"; +import { PendingRedeem } from "./types/strategy"; +import { PrismaService } from "@forge/db"; +import { StarknetService } from "./starknet/starknet.service"; +import Decimal from "decimal.js"; +import { ConfigService } from "@forge/config"; +import { Logger } from "@forge/logger"; @Injectable() -export class AppService { - getHello(): string { - return "StarkNet Vault Kit Backend API"; +export class AppService implements OnModuleInit { + strategyDecimals: number; + private readonly logger = Logger.create('AppService'); + + constructor( + private readonly prismaService: PrismaService, + private readonly starknetService: StarknetService, + private readonly configService: ConfigService + ) {} + + async onModuleInit() { + try { + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + this.logger.info('Initializing AppService', { vaultAddress }); + + this.strategyDecimals = Number( + await this.starknetService.vault_decimals(vaultAddress) + ); + + this.logger.info('AppService initialized successfully', { + strategyDecimals: this.strategyDecimals + }); + } catch (error) { + this.logger.error('Failed to initialize AppService', error); + throw error; + } } + + getApiInfo() { + return { + name: "StarkNet Vault Kit API", + version: "1.0.0", + description: "Backend API for StarkNet Vault Kit", + endpoints: { + health: "/health", + pendingRedeems: "/pending-redeems/:address", + lastReport: "/reports/last", + redeemById: "/redeems/:id", + }, + documentation: "/api", + }; + } + + public async getPendingRedeems( + address: string, + limit?: number, + offset?: number + ): Promise { + try { + const addressToUse = validateAndParseAddress(address); + this.logger.info('Fetching pending redeems', { + address: addressToUse, + limit, + offset + }); + + // Fetch pending redeems directly from PrismaService + const pendingRedeemsForStrategy = + await this.prismaService.fetchPendingRedeemsForAddress( + addressToUse, + limit, + offset + ); + + this.logger.debug('Found pending redeems from database', { + count: pendingRedeemsForStrategy.length + }); + + // Map database results to PendingRedeem type with parallel processing + const pendingRedeemPromises = pendingRedeemsForStrategy.map( + async (redeem) => { + try { + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + const dueAssets = await this.starknetService.vault_due_assets_from_id( + vaultAddress, + Number(redeem.redeemId) + ); + + return { + epoch: Number(redeem.epoch), + sharesBurn: this.formatBigIntToDecimal(redeem.shares), + nominal: this.formatBigIntToDecimal(redeem.assets), + assets: this.formatBigIntToDecimal(dueAssets), + redeemId: redeem.redeemId.toString(), + timestamp: redeem.timestamp, + transactionHash: redeem.transactionHash, + }; + } catch (error) { + this.logger.error('Failed to process pending redeem', error, { + redeemId: redeem.redeemId.toString() + }); + throw error; + } + } + ); + + const resolvedPendingRedeems = await Promise.all(pendingRedeemPromises); + + this.logger.info('Successfully processed pending redeems', { + address: addressToUse, + count: resolvedPendingRedeems.length + }); + + return resolvedPendingRedeems; + } catch (error) { + this.logger.error('Failed to get pending redeems', error, { address }); + throw error; + } + } + + public async getLastReport() { + try { + this.logger.debug('Fetching last report'); + + const lastReport = await this.prismaService.fetchLastReport(); + if (!lastReport) { + this.logger.info('No reports found in database'); + return null; + } + + const result = { + id: lastReport.id, + blockNumber: lastReport.blockNumber, + timestamp: lastReport.timestamp, + transactionHash: lastReport.transactionHash, + newEpoch: lastReport.newEpoch.toString(), + newHandledEpochLen: lastReport.newHandledEpochLen.toString(), + totalSupply: this.formatBigIntToDecimal(lastReport.totalSupply), + totalAssets: this.formatBigIntToDecimal(lastReport.totalAssets), + managementFeeShares: this.formatBigIntToDecimal(lastReport.managementFeeShares), + performanceFeeShares: this.formatBigIntToDecimal(lastReport.performanceFeeShares), + }; + + this.logger.info('Successfully fetched last report', { + reportId: result.id, + blockNumber: result.blockNumber, + epoch: result.newEpoch + }); + + return result; + } catch (error) { + this.logger.error('Failed to get last report', error); + throw error; + } + } + + public async getRedeemById(redeemId: string) { + try { + this.logger.info('Fetching redeem by ID', { redeemId }); + + const redeemRequested = await this.prismaService.redeemRequested.findUnique({ + where: { redeemId: BigInt(redeemId) } + }); + + if (!redeemRequested) { + this.logger.info('Redeem request not found', { redeemId }); + return null; + } + + const redeemClaimed = await this.prismaService.redeemClaimed.findUnique({ + where: { redeemId: BigInt(redeemId) } + }); + + // Get current due assets from the contract (only if not claimed yet) + let currentDueAssets: bigint | null = null; + if (!redeemClaimed) { + try { + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + currentDueAssets = await this.starknetService.vault_due_assets_from_id( + vaultAddress, + Number(redeemId) + ); + this.logger.debug('Fetched current due assets from contract', { + redeemId, + dueAssets: currentDueAssets?.toString() + }); + } catch (error) { + this.logger.warn('Failed to fetch due assets for redeem ID', { redeemId, error: error.message }); + } + } + + const result = { + redeemId: redeemRequested.redeemId.toString(), + owner: redeemRequested.owner, + receiver: redeemRequested.receiver, + epoch: redeemRequested.epoch.toString(), + sharesBurn: this.formatBigIntToDecimal(redeemRequested.shares), + nominal: this.formatBigIntToDecimal(redeemRequested.assets), + assets: currentDueAssets ? this.formatBigIntToDecimal(currentDueAssets) : null, + requestTimestamp: redeemRequested.timestamp, + requestTransactionHash: redeemRequested.transactionHash, + claimedTimestamp: redeemClaimed?.timestamp, + claimedTransactionHash: redeemClaimed?.transactionHash, + claimedAssets: redeemClaimed ? this.formatBigIntToDecimal(redeemClaimed.assets) : null, + }; + + this.logger.info('Successfully fetched redeem details', { + redeemId, + isClaimed: !!redeemClaimed, + owner: result.owner + }); + + return result; + } catch (error) { + this.logger.error('Failed to get redeem by ID', error, { redeemId }); + throw error; + } + } + + formatBigIntToDecimal = (value: bigint): string => { + return new Decimal(value.toString()) + .div(new Decimal(10).pow(this.strategyDecimals)) + .toString(); + }; } diff --git a/backend/apps/api/src/main.ts b/backend/apps/api/src/main.ts index 9a60c450..9f45c7f3 100644 --- a/backend/apps/api/src/main.ts +++ b/backend/apps/api/src/main.ts @@ -1,36 +1,71 @@ import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { validateApiConfig } from '@forge/config'; +import { Logger, LogLevel, LoggerConfig } from '@forge/logger'; import { AppModule } from './app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule); - - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - forbidNonWhitelisted: true, - transform: true, - }), - ); - - app.enableCors(); - - const config = new DocumentBuilder() - .setTitle('StarkNet Vault Kit API') - .setDescription('API for StarkNet Vault Kit indexer and services') - .setVersion('1.0') - .addTag('indexer') - .addTag('starknet') - .build(); - - const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('api', app, document); - - const port = process.env.PORT || 3000; - await app.listen(port); - console.log(`Application is running on: http://localhost:${port}`); - console.log(`Swagger UI: http://localhost:${port}/api`); + // Configure global logger + const loggerConfig: LoggerConfig = { + level: (process.env.LOG_LEVEL as LogLevel) || LogLevel.INFO, + service: 'vault-api', + enableConsole: true, + enableFile: process.env.NODE_ENV === 'production', + logDir: process.env.LOG_DIR || 'logs', + format: process.env.NODE_ENV === 'production' ? 'json' : 'simple', + }; + + Logger.configure(loggerConfig); + const logger = Logger.create('Bootstrap'); + + try { + // Validate environment variables + const envConfig = validateApiConfig(process.env); + logger.info('Environment configuration validated successfully'); + + const app = await NestFactory.create(AppModule); + + // Configure global validation pipes + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }), + ); + logger.info('Global validation pipes configured'); + + // Enable CORS + app.enableCors(); + logger.info('CORS enabled'); + + // Setup Swagger documentation + const config = new DocumentBuilder() + .setTitle('StarkNet Vault Kit API') + .setDescription('API for StarkNet Vault Kit indexer and services') + .setVersion('1.0') + .addTag('indexer') + .addTag('starknet') + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api', app, document); + logger.info('Swagger documentation configured at /api'); + + const port = process.env.PORT || 3000; + await app.listen(port); + + logger.info('🚀 StarkNet Vault Kit API started successfully', { + port, + environment: process.env.NODE_ENV || 'development', + apiUrl: `http://localhost:${port}`, + docsUrl: `http://localhost:${port}/api` + }); + } catch (error) { + logger.error('❌ Failed to start API server', error); + process.exit(1); + } } bootstrap(); \ No newline at end of file diff --git a/backend/apps/api/src/starknet/abis/vault.json b/backend/apps/api/src/starknet/abis/vault.json new file mode 100644 index 00000000..131f20be --- /dev/null +++ b/backend/apps/api/src/starknet/abis/vault.json @@ -0,0 +1,1581 @@ +[ + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "openzeppelin_interfaces::upgrades::IUpgradeable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::upgrades::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "VaultMetadataImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20Metadata" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20Metadata", + "items": [ + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "VaultImpl", + "interface_name": "vault::vault::interface::IVault" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "interface", + "name": "vault::vault::interface::IVault", + "items": [ + { + "type": "function", + "name": "register_redeem_request", + "inputs": [ + { + "name": "redeem_request", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_vault_allocator", + "inputs": [ + { + "name": "vault_allocator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "request_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "claim_redeem", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_fees_config", + "inputs": [ + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_report_delay", + "inputs": [ + { + "name": "report_delay", + "type": "core::integer::u64" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_max_delta", + "inputs": [ + { + "name": "max_delta", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "report", + "inputs": [ + { + "name": "new_aum", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "bring_liquidity", + "inputs": [ + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "epoch", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "handled_epoch_len", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "buffer", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "aum", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_assets", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_nominal", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "fees_recipient", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "management_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "performance_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_request", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "report_delay", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "vault_allocator", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "last_report_timestamp", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_delta", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "due_assets_from_id", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ERC4626Impl", + "interface_name": "openzeppelin_interfaces::token::erc4626::IERC4626" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc4626::IERC4626", + "items": [ + { + "type": "function", + "name": "asset", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "total_assets", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_shares", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_assets", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_deposit", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_mint", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_withdraw", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_redeem", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20Impl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20", + "items": [ + { + "type": "function", + "name": "total_supply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balance_of", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "transfer_from", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20CamelOnlyImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly", + "items": [ + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "AccessControlImpl", + "interface_name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl", + "items": [ + { + "type": "function", + "name": "has_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_role_admin", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "renounce_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "PausableImpl", + "interface_name": "openzeppelin_interfaces::security::pausable::IPausable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::security::pausable::IPausable", + "items": [ + { + "type": "function", + "name": "is_paused", + "inputs": [], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + }, + { + "name": "symbol", + "type": "core::byte_array::ByteArray" + }, + { + "name": "underlying_asset", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + }, + { + "name": "report_delay", + "type": "core::integer::u64" + }, + { + "name": "max_delta", + "type": "core::integer::u256" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "struct", + "members": [ + { + "name": "from", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "nested" + }, + { + "name": "Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Deposit", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "nested" + }, + { + "name": "Withdraw", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "delay", + "type": "core::integer::u64", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "previous_admin_role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "new_admin_role", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "RoleGranted", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "nested" + }, + { + "name": "RoleGrantedWithDelay", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "nested" + }, + { + "name": "RoleRevoked", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "nested" + }, + { + "name": "RoleAdminChanged", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Paused", + "type": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "nested" + }, + { + "name": "Unpaused", + "type": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemRequested", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "struct", + "members": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "redeem_request_nominal", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Report", + "kind": "struct", + "members": [ + { + "name": "new_epoch", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "new_handled_epoch_len", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_supply", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "management_fee_shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "performance_fee_shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Event", + "kind": "enum", + "variants": [ + { + "name": "ERC20Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "nested" + }, + { + "name": "ERC4626Event", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "nested" + }, + { + "name": "SRC5Event", + "type": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "nested" + }, + { + "name": "AccessControlEvent", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "nested" + }, + { + "name": "UpgradeableEvent", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "nested" + }, + { + "name": "PausableEvent", + "type": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "nested" + }, + { + "name": "RedeemRequested", + "type": "vault::vault::vault::Vault::RedeemRequested", + "kind": "nested" + }, + { + "name": "RedeemClaimed", + "type": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "nested" + }, + { + "name": "Report", + "type": "vault::vault::vault::Vault::Report", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/backend/apps/api/src/starknet/starknet.service.ts b/backend/apps/api/src/starknet/starknet.service.ts index 14e9dcf0..4bd30242 100644 --- a/backend/apps/api/src/starknet/starknet.service.ts +++ b/backend/apps/api/src/starknet/starknet.service.ts @@ -1,55 +1,113 @@ import { Injectable, OnModuleInit } from "@nestjs/common"; import { ConfigService } from "@forge/config"; import { Provider, RpcProvider, Contract } from "starknet"; +import { Logger } from "@forge/logger"; +import * as VAULT_ABI from "./abis/vault.json"; @Injectable() export class StarknetService implements OnModuleInit { private provider: Provider; + private readonly logger = Logger.create('StarknetService'); constructor(private configService: ConfigService) {} async onModuleInit() { - const rpcUrl = this.configService.get("STARKNET_RPC_URL") || "https://starknet-sepolia.public.blastapi.io/rpc/v0_7"; - this.provider = new RpcProvider({ nodeUrl: rpcUrl }); + try { + const rpcUrl: string = + (this.configService.get("RPC_URL") as string) || + "https://starknet-mainnet.public.blastapi.io"; + + this.logger.info('Initializing StarkNet provider', { rpcUrl }); + this.provider = new RpcProvider({ nodeUrl: rpcUrl }); + this.logger.info('StarkNet provider initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize StarkNet provider', error); + throw error; + } } - getProvider(): Provider { - return this.provider; - } - - async getBlockNumber(): Promise { - return await this.provider.getBlockNumber(); - } - - async getBlock(blockNumber: number) { - return await this.provider.getBlockWithTxHashes(blockNumber); - } - - async getTransaction(txHash: string) { - return await this.provider.getTransaction(txHash); - } - - async getTransactionReceipt(txHash: string) { - return await this.provider.getTransactionReceipt(txHash); - } + async view( + address: string, + abi: any, + functionName: string, + calldata?: any[] + ) { + try { + this.logger.debug('Making contract view call', { + address, + functionName, + calldataLength: calldata?.length || 0 + }); + + const abiArray = Array.isArray(abi) ? abi : Object.values(abi); + const contract = new Contract(abiArray, address, this.provider); - async getContractClass(contractAddress: string) { - return await this.provider.getClassAt(contractAddress); + const result = calldata + ? await contract.call(functionName, calldata) + : await contract.call(functionName); + + this.logger.debug('Contract view call successful', { + address, + functionName, + resultType: typeof result + }); + + return result; + } catch (error) { + this.logger.error('Contract view call failed', error, { + address, + functionName, + calldata + }); + throw error; + } } - createContract(address: string, abi: any): Contract { - return new Contract(abi, address, this.provider); + async vault_due_assets_from_id(address: string, id: number): Promise { + try { + this.logger.debug('Fetching due assets from vault', { address, id }); + + const due_assets = (await this.view( + address, + VAULT_ABI, + "due_assets_from_id", + [id] + )) as any; + + this.logger.debug('Successfully fetched due assets', { + address, + id, + dueAssets: due_assets?.toString() + }); + + return due_assets; + } catch (error) { + this.logger.error('Failed to fetch due assets from vault', error, { + address, + id + }); + throw error; + } } - async callContract( - contractAddress: string, - functionName: string, - calldata: any[] = [] - ) { - return await this.provider.callContract({ - contractAddress, - entrypoint: functionName, - calldata, - }); + async vault_decimals(address: string): Promise { + try { + this.logger.debug('Fetching vault decimals', { address }); + + const decimals = (await this.provider.callContract({ + contractAddress: address, + entrypoint: "decimals", + })) as any; + + this.logger.debug('Successfully fetched vault decimals', { + address, + decimals: decimals?.toString() + }); + + return decimals; + } catch (error) { + this.logger.error('Failed to fetch vault decimals', error, { address }); + throw error; + } } -} \ No newline at end of file +} diff --git a/backend/apps/api/src/types/strategy.ts b/backend/apps/api/src/types/strategy.ts new file mode 100644 index 00000000..f94a11ee --- /dev/null +++ b/backend/apps/api/src/types/strategy.ts @@ -0,0 +1,14 @@ +export interface Strategy { + vault: string; + startBlockIndexing: number; +} + +export interface PendingRedeem { + epoch: number; + sharesBurn: string; + nominal: string; + assets: string; + redeemId: string; + timestamp: number; + transactionHash: string; +} \ No newline at end of file diff --git a/backend/apps/indexer/package.json b/backend/apps/indexer/package.json index 475f9e9a..13e5e06a 100644 --- a/backend/apps/indexer/package.json +++ b/backend/apps/indexer/package.json @@ -12,7 +12,6 @@ "dependencies": { "@forge/config": "workspace:*", "@forge/db": "workspace:*", - "@forge/core": "workspace:*", "@forge/logger": "workspace:*", "@apibara/protocol": "^0.4.9", "@apibara/starknet": "^0.5.0", diff --git a/backend/apps/indexer/src/indexer.service.ts b/backend/apps/indexer/src/indexer.service.ts index 052d4afa..8fae34b5 100644 --- a/backend/apps/indexer/src/indexer.service.ts +++ b/backend/apps/indexer/src/indexer.service.ts @@ -9,7 +9,7 @@ import { DataFinality } from "@apibara/protocol/dist/proto/apibara/node/v1alpha2 import { hash as starknetHash, validateAndParseAddress } from "starknet"; import { ConfigService } from "@forge/config"; import { PrismaService } from "@forge/db"; -import { STRATEGY } from "@forge/core"; +import { Logger } from "@forge/logger"; import { decodeReportEvent, decodeRedeemRequestedEvent, @@ -46,6 +46,7 @@ export class IndexerService { private url: string; private apibaraToken: string; private vaultFe: v1alpha2.IFieldElement; + private logger: Logger; private redeemRequestedBuffer: any[] = []; private redeemClaimedBuffer: any[] = []; @@ -55,8 +56,9 @@ export class IndexerService { private readonly configService: ConfigService, private readonly prismaService: PrismaService ) { + this.logger = Logger.create('IndexerService'); this.url = "mainnet.starknet.a5a.ch"; - this.apibaraToken = this.configService.get("APIBARA_TOKEN"); + this.apibaraToken = this.configService.get("APIBARA_TOKEN") as string; const graceful = async () => { try { @@ -78,7 +80,7 @@ export class IndexerService { url: this.url, token: this.apibaraToken, onReconnect: async (err, retryCount) => { - console.error(`[Indexer] Connection lost. Attempt #${retryCount}`, err); + this.logger.error('Connection lost', err, { retryCount, errorCode: err.code }); if (err.code !== 13 && err.code !== 14) { // Code 13 = internal error, 14 = unavailable return { reconnect: false }; @@ -97,15 +99,13 @@ export class IndexerService { const filterBuilder = Filter.create().withHeader({ weak: false }); + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; try { this.vaultFe = FieldElement.fromBigInt( - validateAndParseAddress(STRATEGY.vault) + validateAndParseAddress(vaultAddress) ); } catch (e) { - console.error("Invalid STRATEGY.vault address", { - vault: STRATEGY.vault, - error: e, - }); + this.logger.error('Invalid vault address', e, { vaultAddress }); throw e; } @@ -124,7 +124,7 @@ export class IndexerService { ); }); - let startBlock = STRATEGY.startBlockIndexing; + let startBlock = Number(this.configService.get('START_BLOCK')); const [lastRedeemRequested, lastRedeemClaimed, lastReport] = await Promise.all([ @@ -142,7 +142,12 @@ export class IndexerService { startBlock = maxBlock + 1; - console.log(`📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`); + this.logger.info(`📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`, { + startBlock, + lastRedeemRequestedBlock: lastRedeemRequested?.blockNumber || 0, + lastRedeemClaimedBlock: lastRedeemClaimed?.blockNumber || 0, + lastReportBlock: lastReport?.blockNumber || 0, + }); const cursor = StarkNetCursor.createWithBlockNumber(startBlock); @@ -168,9 +173,11 @@ export class IndexerService { throw new Error("No timestamp in block header"); } - console.log( - `Processing block ${blockNum}, lastBlockIndexed: ${this.lastBlockIndexedVault}` - ); + this.logger.debug('Processing block', { + blockNumber: blockNum, + lastBlockIndexed: this.lastBlockIndexedVault, + timestamp: Number(timestamp) + }); for (let event of block.events) { const hash = event.transaction?.meta?.hash; @@ -197,9 +204,12 @@ export class IndexerService { } }); - console.log( - `Event: ${this.getEventTypeName(eventTypeHex)}, block: ${blockNum}, tx: ${hashHex}` - ); + this.logger.debug('Processing event', { + eventType: this.getEventTypeName(eventTypeHex), + blockNumber: blockNum, + transactionHash: hashHex, + eventTypeHex + }); try { await this.processEvent(eventTypeHex, hexData, { @@ -210,10 +220,10 @@ export class IndexerService { eventTypeHex, }); } catch (error) { - console.error("Failed to process event:", { - block: blockNum, - tx: hashHex, - error, + this.logger.error('Failed to process event', error, { + blockNumber: blockNum, + transactionHash: hashHex, + eventType: this.getEventTypeName(eventTypeHex) }); throw error; } @@ -265,7 +275,7 @@ export class IndexerService { transactionHash ); } else { - console.log("Unknown event type:", eventTypeHex); + this.logger.warn('Unknown event type', { eventTypeHex }); } } @@ -299,10 +309,9 @@ export class IndexerService { await this.flushRedeemRequestedBuffer(); } } catch (error) { - console.error("Failed to process RedeemRequested event:", { + this.logger.error('Failed to process RedeemRequested event', error, { blockNumber, - transactionHash, - error, + transactionHash }); } } @@ -334,10 +343,9 @@ export class IndexerService { await this.flushRedeemClaimedBuffer(); } } catch (error) { - console.error("Failed to process RedeemClaimed event:", { + this.logger.error('Failed to process RedeemClaimed event', error, { blockNumber, - transactionHash, - error, + transactionHash }); } } @@ -368,10 +376,9 @@ export class IndexerService { await this.flushReportBuffer(); } } catch (error) { - console.error("Failed to process Report event:", { + this.logger.error('Failed to process Report event', error, { blockNumber, - transactionHash, - error, + transactionHash }); } } @@ -384,12 +391,12 @@ export class IndexerService { data: this.redeemRequestedBuffer, skipDuplicates: true, }); - console.log( - `Flushed ${this.redeemRequestedBuffer.length} RedeemRequested events` - ); + this.logger.info('Flushed RedeemRequested events', { + count: this.redeemRequestedBuffer.length + }); this.redeemRequestedBuffer = []; } catch (err) { - console.error("Failed to flush RedeemRequested buffer:", err); + this.logger.error('Failed to flush RedeemRequested buffer', err); throw err; } } @@ -402,12 +409,12 @@ export class IndexerService { data: this.redeemClaimedBuffer, skipDuplicates: true, }); - console.log( - `Flushed ${this.redeemClaimedBuffer.length} RedeemClaimed events` - ); + this.logger.info('Flushed RedeemClaimed events', { + count: this.redeemClaimedBuffer.length + }); this.redeemClaimedBuffer = []; } catch (err) { - console.error("Failed to flush RedeemClaimed buffer:", err); + this.logger.error('Failed to flush RedeemClaimed buffer', err); throw err; } } @@ -420,10 +427,12 @@ export class IndexerService { data: this.reportBuffer, skipDuplicates: true, }); - console.log(`Flushed ${this.reportBuffer.length} Report events`); + this.logger.info('Flushed Report events', { + count: this.reportBuffer.length + }); this.reportBuffer = []; } catch (err) { - console.error("Failed to flush Report buffer:", err); + this.logger.error('Failed to flush Report buffer', err); throw err; } } @@ -450,4 +459,4 @@ export class IndexerService { lastBlockIndexedVault: this.lastBlockIndexedVault, }; } -} \ No newline at end of file +} diff --git a/backend/apps/indexer/src/main.ts b/backend/apps/indexer/src/main.ts index ca318d7f..bbceb387 100644 --- a/backend/apps/indexer/src/main.ts +++ b/backend/apps/indexer/src/main.ts @@ -1,34 +1,60 @@ import "reflect-metadata"; -import { ConfigService, validate } from "@forge/config"; +import { ConfigService, validateIndexerConfig } from "@forge/config"; import { PrismaService } from "@forge/db"; +import { Logger, LogLevel, LoggerConfig } from "@forge/logger"; import { IndexerService } from "./indexer.service"; async function bootstrap() { - // Validate environment variables - const config = validate(process.env); - - // Create services - const configService = new ConfigService({}); - const prismaService = new PrismaService(); - - // Connect to database - await prismaService.$connect(); - - console.log("🔥 Starting StarkNet Vault Kit Indexer..."); - - // Create and start indexer - const indexer = new IndexerService(configService, prismaService); + // Configure global logger + const loggerConfig: LoggerConfig = { + level: (process.env.LOG_LEVEL as LogLevel) || LogLevel.INFO, + service: 'vault-indexer', + enableConsole: true, + enableFile: process.env.NODE_ENV === 'production', + logDir: process.env.LOG_DIR || 'logs', + format: process.env.NODE_ENV === 'production' ? 'json' : 'simple', + }; + Logger.configure(loggerConfig); + const logger = Logger.create('Bootstrap'); + try { + // Validate environment variables + const config = validateIndexerConfig(process.env); + logger.info('Environment configuration validated successfully'); + + // Create services + const configService = new ConfigService({}); + const prismaService = new PrismaService(); + + // Connect to database + await prismaService.$connect(); + logger.info('Database connection established'); + + logger.info('🔥 Starting StarkNet Vault Kit Indexer...'); + + // Create and start indexer + const indexer = new IndexerService(configService, prismaService); + await indexer.run(); } catch (error) { - console.error("❌ Indexer failed:", error); - await prismaService.$disconnect(); + logger.error('❌ Indexer failed', error); + + // Attempt graceful cleanup + try { + const prismaService = new PrismaService(); + await prismaService.$disconnect(); + logger.info('Database connection closed gracefully'); + } catch (cleanupError) { + logger.error('Failed to cleanup database connection', cleanupError); + } + process.exit(1); } } bootstrap().catch((error) => { - console.error("❌ Bootstrap failed:", error); + const logger = Logger.create('Bootstrap'); + logger.error('❌ Bootstrap failed', error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/backend/apps/relayer/README.md b/backend/apps/relayer/README.md deleted file mode 100644 index 59dda7bf..00000000 --- a/backend/apps/relayer/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Relayer Service - -Cron job scheduler and worker service for automated vault operations. - -## Features - -- Scheduled job execution -- Database operations -- Cross-chain bridging (planned) -- Vault management automation - -## Development - -```bash -# Run in development mode -pnpm dev:relayer - -# Build -pnpm build:relayer - -# Start production -pnpm --filter relayer start -``` - -## Jobs - -- Example Job: Runs every 5 minutes for demonstration -- Add your custom jobs in `src/jobs/` \ No newline at end of file diff --git a/backend/apps/relayer/package.json b/backend/apps/relayer/package.json deleted file mode 100644 index 882f7a10..00000000 --- a/backend/apps/relayer/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "relayer", - "version": "0.0.1", - "description": "Relayer service for StarkNet Vault Kit - handles cron jobs and cross-chain operations", - "main": "dist/main.js", - "scripts": { - "build": "tsc", - "dev": "ts-node src/main.ts", - "start": "node dist/main.js", - "lint": "eslint \"src/**/*.ts\" --fix" - }, - "dependencies": { - "@forge/config": "workspace:*", - "@forge/db": "workspace:*", - "@forge/core": "workspace:*", - "@forge/logger": "workspace:*", - "cron": "^3.0.0", - "starknet": "^6.0.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.0" - }, - "devDependencies": { - "ts-node": "^10.9.1", - "@types/node": "^20.3.1", - "@types/cron": "^2.0.0" - } -} \ No newline at end of file diff --git a/backend/apps/relayer/src/jobs/example-job.ts b/backend/apps/relayer/src/jobs/example-job.ts deleted file mode 100644 index 20a97393..00000000 --- a/backend/apps/relayer/src/jobs/example-job.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Logger } from "@forge/logger"; -import { PrismaService } from "@forge/db"; - -export class ExampleJob { - private logger = Logger.create('ExampleJob'); - - constructor(private prismaService: PrismaService) {} - - async execute(): Promise { - this.logger.log('Executing example job...'); - - // Add your job logic here - // For example, processing vault rebalancing, fee collection, etc. - - this.logger.log('Example job completed'); - } -} \ No newline at end of file diff --git a/backend/apps/relayer/src/main.ts b/backend/apps/relayer/src/main.ts deleted file mode 100644 index 3d698822..00000000 --- a/backend/apps/relayer/src/main.ts +++ /dev/null @@ -1,50 +0,0 @@ -import "reflect-metadata"; -import { ConfigService, validate } from "@forge/config"; -import { PrismaService } from "@forge/db"; -import { Logger } from "@forge/logger"; -import { ExampleJob } from "./jobs/example-job"; -import { SchedulerService } from "./services/scheduler.service"; - -async function bootstrap() { - const logger = Logger.create("RelayerBootstrap"); - - try { - // Validate environment variables - const config = validate(process.env); - - // Create services - const configService = new ConfigService({}); - const prismaService = new PrismaService(); - - // Connect to database - await prismaService.$connect(); - logger.log("Connected to database"); - - // Create jobs - const exampleJob = new ExampleJob(prismaService); - - // Create and start scheduler - const scheduler = new SchedulerService(exampleJob); - - logger.log("🔥 Starting StarkNet Vault Kit Relayer..."); - scheduler.start(); - - // Handle graceful shutdown - const gracefulShutdown = async (signal: string) => { - logger.log(`Received ${signal}, shutting down gracefully...`); - scheduler.stop(); - await prismaService.$disconnect(); - process.exit(0); - }; - - process.on("SIGINT", () => gracefulShutdown("SIGINT")); - process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); - - logger.log("✅ Relayer started successfully"); - } catch (error) { - logger.error("❌ Bootstrap failed:", error); - process.exit(1); - } -} - -bootstrap(); diff --git a/backend/apps/relayer/src/services/scheduler.service.ts b/backend/apps/relayer/src/services/scheduler.service.ts deleted file mode 100644 index 9dc6e828..00000000 --- a/backend/apps/relayer/src/services/scheduler.service.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CronJob } from 'cron'; -import { Logger } from "@forge/logger"; -import { ExampleJob } from '../jobs/example-job'; - -export class SchedulerService { - private logger = Logger.create('SchedulerService'); - private jobs: CronJob[] = []; - - constructor(private exampleJob: ExampleJob) {} - - start(): void { - this.logger.log('Starting scheduler service...'); - - // Example: run example job every 5 minutes - const exampleJobCron = new CronJob( - '*/5 * * * *', - async () => { - try { - await this.exampleJob.execute(); - } catch (error) { - this.logger.error('Example job failed:', error); - } - }, - null, - false, - 'UTC' - ); - - this.jobs.push(exampleJobCron); - - // Start all jobs - this.jobs.forEach(job => job.start()); - - this.logger.log(`Started ${this.jobs.length} scheduled jobs`); - } - - stop(): void { - this.logger.log('Stopping scheduler service...'); - this.jobs.forEach(job => job.stop()); - this.jobs = []; - } -} \ No newline at end of file diff --git a/backend/apps/relayer/tsconfig.json b/backend/apps/relayer/tsconfig.json deleted file mode 100644 index 10a1485d..00000000 --- a/backend/apps/relayer/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist" - }, - "include": ["src/**/*", "../../libs/*/src/**/*"], - "exclude": ["node_modules", "dist"], - "ts-node": { - "esm": false - } -} \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 00000000..dfc80bc9 --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,48 @@ +version: '3.8' + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: starknet_vault_kit + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + indexer: + build: + context: . + dockerfile: Dockerfile.indexer + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - APIBARA_TOKEN=${APIBARA_TOKEN} + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + + api: + build: + context: . + dockerfile: Dockerfile.api + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + ports: + - "3000:3000" + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + +volumes: + postgres_data: \ No newline at end of file diff --git a/backend/libs/config/src/config.service.ts b/backend/libs/config/src/config.service.ts index a7f6cbc4..e87e27d3 100644 --- a/backend/libs/config/src/config.service.ts +++ b/backend/libs/config/src/config.service.ts @@ -1,10 +1,8 @@ import { ConfigService as ConfigServiceSource } from "@nestjs/config"; -import { EnvironmentVariables } from "./env.validation"; +import { BaseEnvironmentVariables } from "./env.validation"; -export class ConfigService extends ConfigServiceSource { - public get( - key: T - ): EnvironmentVariables[T] { - return super.get(key, { infer: true }) as EnvironmentVariables[T]; +export class ConfigService extends ConfigServiceSource { + public get(key: string): string | number | undefined { + return super.get(key, { infer: true }); } } \ No newline at end of file diff --git a/backend/libs/config/src/env.validation.ts b/backend/libs/config/src/env.validation.ts index c045617b..1f98ac8b 100644 --- a/backend/libs/config/src/env.validation.ts +++ b/backend/libs/config/src/env.validation.ts @@ -1,21 +1,35 @@ import { plainToClass, Transform } from "class-transformer"; import { IsNumber, IsOptional, validateSync, IsString } from "class-validator"; -export class EnvironmentVariables { +// Base environment variables needed by all services +export class BaseEnvironmentVariables { + @IsString() + DATABASE_URL: string; +} + +// API-specific environment variables +export class ApiEnvironmentVariables extends BaseEnvironmentVariables { @IsOptional() @IsNumber() @Transform(({ value }) => Number(value)) PORT = 3000; @IsString() - STARKNET_RPC_URL: string; + RPC_URL: string; +} +// Indexer-specific environment variables +export class IndexerEnvironmentVariables extends BaseEnvironmentVariables { @IsString() APIBARA_TOKEN: string; } -export function validate(config: Record) { - const validatedConfig = plainToClass(EnvironmentVariables, config); +// Generic validation function +function validateConfig( + EnvClass: new () => T, + config: Record +): T { + const validatedConfig = plainToClass(EnvClass, config); const validatorOptions = { skipMissingProperties: false }; const errors = validateSync(validatedConfig, validatorOptions); @@ -27,3 +41,17 @@ export function validate(config: Record) { return validatedConfig; } + +// Export specific validation functions for each service +export function validateApiConfig(config: Record) { + return validateConfig(ApiEnvironmentVariables, config); +} + +export function validateIndexerConfig(config: Record) { + return validateConfig(IndexerEnvironmentVariables, config); +} + +// Legacy export for backward compatibility +export function validate(config: Record) { + return validateApiConfig(config); +} diff --git a/backend/libs/core/package.json b/backend/libs/core/package.json deleted file mode 100644 index b15131b8..00000000 --- a/backend/libs/core/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@forge/core", - "version": "0.0.1", - "description": "Core types, DTOs, and constants for StarkNet Vault Kit", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "dev": "tsc --watch" - }, - "dependencies": { - "class-transformer": "^0.5.1", - "class-validator": "^0.14.0" - } -} \ No newline at end of file diff --git a/backend/libs/core/src/constants/strategy.ts b/backend/libs/core/src/constants/strategy.ts deleted file mode 100644 index a4d6e3dd..00000000 --- a/backend/libs/core/src/constants/strategy.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Strategy } from "../types/strategy"; - -export const STRATEGY: Strategy = { - vault: "0x0000000000000000000000000000000000000000000000000000000000000000", - startBlockIndexing: 12993, -}; \ No newline at end of file diff --git a/backend/libs/core/src/index.ts b/backend/libs/core/src/index.ts deleted file mode 100644 index 8075966d..00000000 --- a/backend/libs/core/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './types/strategy'; -export * from './constants/strategy'; \ No newline at end of file diff --git a/backend/libs/core/src/types/strategy.ts b/backend/libs/core/src/types/strategy.ts deleted file mode 100644 index 72dd43b1..00000000 --- a/backend/libs/core/src/types/strategy.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Strategy { - vault: string; - startBlockIndexing: number; -} \ No newline at end of file diff --git a/backend/libs/core/tsconfig.json b/backend/libs/core/tsconfig.json deleted file mode 100644 index 90a106c3..00000000 --- a/backend/libs/core/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} \ No newline at end of file diff --git a/backend/libs/db/src/prisma.service.ts b/backend/libs/db/src/prisma.service.ts index 077f96ab..f1365f95 100644 --- a/backend/libs/db/src/prisma.service.ts +++ b/backend/libs/db/src/prisma.service.ts @@ -29,10 +29,49 @@ export class PrismaService return latest as Report | undefined; } + async fetchLastReports(limit: number, offset?: number): Promise { + const reports = await this.report.findMany({ + orderBy: { blockNumber: "desc" }, + take: limit, + ...(offset && { skip: offset }), + }); + return reports as Report[]; + } + async fetchLastRedeemClaimed(): Promise { const latest = await this.redeemClaimed.findFirst({ orderBy: { blockNumber: "desc" }, }); return latest as RedeemClaimed | undefined; } -} \ No newline at end of file + + + public async fetchPendingRedeemsForAddress( + receiver: string, + limit?: number, + offset?: number + ): Promise { + // Get all claimed redeem IDs for this address + const claimedRedeemIds = await this.redeemClaimed.findMany({ + where: { receiver }, + select: { redeemId: true }, + }); + + const claimedIds = claimedRedeemIds.map(r => r.redeemId); + + // Find all requested redeems that are NOT in the claimed list + return this.redeemRequested.findMany({ + where: { + receiver, + redeemId: { + notIn: claimedIds, + }, + }, + orderBy: { + redeemId: "desc", + }, + ...(limit && { take: limit }), + ...(offset && { skip: offset }), + }); + } +} diff --git a/backend/libs/logger/package.json b/backend/libs/logger/package.json index b30a38fa..c4578697 100644 --- a/backend/libs/logger/package.json +++ b/backend/libs/logger/package.json @@ -8,5 +8,11 @@ "build": "tsc", "dev": "tsc --watch" }, - "dependencies": {} + "dependencies": { + "winston": "^3.11.0", + "winston-daily-rotate-file": "^4.7.1" + }, + "devDependencies": { + "@types/node": "^20.0.0" + } } \ No newline at end of file diff --git a/backend/libs/logger/src/index.ts b/backend/libs/logger/src/index.ts index 7754e4e2..f556ad70 100644 --- a/backend/libs/logger/src/index.ts +++ b/backend/libs/logger/src/index.ts @@ -1 +1,2 @@ -export * from './logger'; \ No newline at end of file +export * from './logger'; +export { Logger, LogLevel, LoggerConfig } from './logger'; \ No newline at end of file diff --git a/backend/libs/logger/src/logger.ts b/backend/libs/logger/src/logger.ts index 5c169684..a91257f3 100644 --- a/backend/libs/logger/src/logger.ts +++ b/backend/libs/logger/src/logger.ts @@ -1,27 +1,164 @@ +import * as winston from 'winston'; +import * as DailyRotateFile from 'winston-daily-rotate-file'; + +export enum LogLevel { + ERROR = 'error', + WARN = 'warn', + INFO = 'info', + DEBUG = 'debug', +} + +export interface LoggerConfig { + level?: LogLevel; + service?: string; + enableConsole?: boolean; + enableFile?: boolean; + logDir?: string; + maxFiles?: string; + maxSize?: string; + format?: 'json' | 'simple'; +} + export class Logger { + private winston: winston.Logger; private context: string; + private static instances: Map = new Map(); + + private constructor(context: string, config: LoggerConfig = {}) { + this.context = context; + this.winston = this.createWinstonLogger(config); + } + + private createWinstonLogger(config: LoggerConfig): winston.Logger { + const { + level = LogLevel.INFO, + service = 'starknet-vault-kit', + enableConsole = true, + enableFile = true, + logDir = 'logs', + maxFiles = '14d', + maxSize = '20m', + format = 'json', + } = config; + + const logFormat = format === 'json' + ? winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json(), + winston.format.printf(({ timestamp, level, message, service, context, ...meta }) => { + const baseLog = { + timestamp, + level, + service, + context, + message, + }; + return JSON.stringify(Object.keys(meta).length ? { ...baseLog, ...meta } : baseLog); + }) + ) + : winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.printf(({ timestamp, level, message, context, service, ...meta }) => { + const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; + return `${timestamp} [${level.toUpperCase()}] [${service}:${context}] ${message}${metaStr}`; + }) + ); + + const transports: winston.transport[] = []; + + if (enableConsole) { + transports.push( + new winston.transports.Console({ + level, + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp({ format: 'HH:mm:ss' }), + winston.format.printf(({ timestamp, level, message, context, service, ...meta }) => { + const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; + return `${timestamp} [${level}] [${service}:${context}] ${message}${metaStr}`; + }) + ), + }) + ); + } + + if (enableFile) { + transports.push( + new DailyRotateFile({ + dirname: logDir, + filename: `${service}-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + maxFiles, + maxSize, + level, + format: logFormat, + }), + new DailyRotateFile({ + dirname: logDir, + filename: `${service}-error-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + level: 'error', + maxFiles, + maxSize, + format: logFormat, + }) + ); + } - constructor(context?: string) { - this.context = context || 'Application'; + return winston.createLogger({ + level, + defaultMeta: { service, context: this.context }, + transports, + exitOnError: false, + }); } - log(message: string, ...optionalParams: any[]) { - console.log(`[${this.context}] ${message}`, ...optionalParams); + info(message: string, meta?: any): void { + this.winston.info(message, meta); } - error(message: string, ...optionalParams: any[]) { - console.error(`[${this.context}] ${message}`, ...optionalParams); + log(message: string, meta?: any): void { + this.winston.info(message, meta); } - warn(message: string, ...optionalParams: any[]) { - console.warn(`[${this.context}] ${message}`, ...optionalParams); + error(message: string, error?: Error | any, meta?: any): void { + const errorMeta = error instanceof Error + ? { error: error.message, stack: error.stack, ...meta } + : { error, ...meta }; + + this.winston.error(message, errorMeta); } - debug(message: string, ...optionalParams: any[]) { - console.debug(`[${this.context}] ${message}`, ...optionalParams); + warn(message: string, meta?: any): void { + this.winston.warn(message, meta); } - static create(context?: string): Logger { - return new Logger(context); + debug(message: string, meta?: any): void { + this.winston.debug(message, meta); + } + + child(childContext: string): Logger { + const fullContext = `${this.context}:${childContext}`; + return Logger.create(fullContext); + } + + static create(context: string = 'Application', config?: LoggerConfig): Logger { + if (!Logger.instances.has(context)) { + Logger.instances.set(context, new Logger(context, config)); + } + return Logger.instances.get(context)!; + } + + static configure(globalConfig: LoggerConfig): void { + Logger.instances.clear(); + Logger.defaultConfig = globalConfig; + } + + private static defaultConfig: LoggerConfig = {}; + + static getDefaultConfig(): LoggerConfig { + return Logger.defaultConfig; } } \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index ffc99e16..eb6fd29d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,18 +12,18 @@ "scripts": { "dev:api": "pnpm --filter api dev", "dev:indexer": "pnpm --filter indexer dev", - "dev:relayer": "pnpm --filter relayer dev", - "dev:all": "concurrently \"pnpm dev:api\" \"pnpm dev:indexer\" \"pnpm dev:relayer\"", + "dev:all": "concurrently \"pnpm dev:api\" \"pnpm dev:indexer\"", + "start:api": "pnpm --filter api start", + "start:indexer": "pnpm --filter indexer start", "build": "pnpm --recursive --filter='!@forge/root' run build", "build:api": "pnpm --filter api build", "build:indexer": "pnpm --filter indexer build", - "build:relayer": "pnpm --filter relayer build", "lint": "pnpm --recursive --filter='!@forge/root' run lint", "test": "pnpm --recursive --filter='!@forge/root' run test", - "migrate:dev": "prisma migrate dev", - "migrate:deploy": "prisma migrate deploy", - "generate": "prisma generate", - "studio": "prisma studio" + "prisma:generate": "prisma generate", + "prisma:migrate:dev": "prisma migrate dev", + "prisma:migrate:deploy": "prisma migrate deploy", + "prisma:studio": "prisma studio" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 10964317..492a69ca 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -84,9 +84,6 @@ importers: '@forge/config': specifier: workspace:* version: link:../../libs/config - '@forge/core': - specifier: workspace:* - version: link:../../libs/core '@forge/db': specifier: workspace:* version: link:../../libs/db @@ -108,9 +105,6 @@ importers: '@nestjs/platform-express': specifier: ^10.0.0 version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) - '@nestjs/schedule': - specifier: ^4.0.0 - version: 4.1.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2)) '@nestjs/swagger': specifier: ^7.0.0 version: 7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) @@ -160,40 +154,6 @@ importers: '@forge/config': specifier: workspace:* version: link:../../libs/config - '@forge/core': - specifier: workspace:* - version: link:../../libs/core - '@forge/db': - specifier: workspace:* - version: link:../../libs/db - '@forge/logger': - specifier: workspace:* - version: link:../../libs/logger - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.14.0 - version: 0.14.2 - starknet: - specifier: ^6.0.0 - version: 6.24.1 - devDependencies: - '@types/node': - specifier: ^20.3.1 - version: 20.19.11 - ts-node: - specifier: ^10.9.1 - version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) - - apps/relayer: - dependencies: - '@forge/config': - specifier: workspace:* - version: link:../../libs/config - '@forge/core': - specifier: workspace:* - version: link:../../libs/core '@forge/db': specifier: workspace:* version: link:../../libs/db @@ -206,16 +166,10 @@ importers: class-validator: specifier: ^0.14.0 version: 0.14.2 - cron: - specifier: ^3.0.0 - version: 3.5.0 starknet: specifier: ^6.0.0 version: 6.24.1 devDependencies: - '@types/cron': - specifier: ^2.0.0 - version: 2.4.3 '@types/node': specifier: ^20.3.1 version: 20.19.11 @@ -238,15 +192,6 @@ importers: specifier: ^0.14.0 version: 0.14.2 - libs/core: - dependencies: - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.14.0 - version: 0.14.2 - libs/db: dependencies: '@nestjs/common': @@ -256,7 +201,18 @@ importers: specifier: ^5.0.0 version: 5.22.0(prisma@5.22.0) - libs/logger: {} + libs/logger: + dependencies: + winston: + specifier: ^3.11.0 + version: 3.17.0 + winston-daily-rotate-file: + specifier: ^4.7.1 + version: 4.7.1(winston@3.17.0) + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.11 packages: @@ -464,10 +420,17 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -694,12 +657,6 @@ packages: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 - '@nestjs/schedule@4.1.2': - resolution: {integrity: sha512-hCTQ1lNjIA5EHxeu8VvQu2Ed2DBLS1GSC6uKPYlBiQe6LL9a7zfE9iVSK+zuK8E2odsApteEBmfAQchc8Hx0Gg==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 - '@nestjs/schematics@10.2.3': resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} peerDependencies: @@ -932,10 +889,6 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/cron@2.4.3': - resolution: {integrity: sha512-ViRBkoZD9Rk0hGeMdd2GHGaOaZuH9mDmwsE5/Zo53Ftwcvh7h9VJc8lIt2wdgEwS4EW5lbtTX6vlE0idCLPOyA==} - deprecated: This is a stub types definition. cron provides its own type definitions, so you do not need this installed. - '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -972,9 +925,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/luxon@3.4.2': - resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} - '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -996,6 +946,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/validator@13.15.2': resolution: {integrity: sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==} @@ -1226,6 +1179,9 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1424,13 +1380,28 @@ packages: collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1509,12 +1480,6 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cron@3.2.1: - resolution: {integrity: sha512-w2n5l49GMmmkBFEsH9FIDhjZ1n1QgTMOCMGuQtOXs5veNiosZmso6bQGuqOJSYAXXrG84WQFVneNk+Yt0Ua9iw==} - - cron@3.5.0: - resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1624,6 +1589,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -1804,6 +1772,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + 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==} @@ -1818,6 +1789,9 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} + file-stream-rotator@0.6.1: + resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} + file-type@20.4.1: resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==} engines: {node: '>=18'} @@ -1845,6 +1819,9 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + 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'} @@ -2048,6 +2025,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -2314,6 +2294,9 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -2356,6 +2339,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -2368,10 +2355,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - luxon@3.5.0: - resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} - engines: {node: '>=12'} - magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} @@ -2456,6 +2439,9 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -2516,6 +2502,10 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -2527,6 +2517,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -2791,6 +2784,10 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2867,6 +2864,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2898,6 +2898,9 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -3016,6 +3019,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -3052,6 +3058,10 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -3204,10 +3214,6 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - uuid@11.0.3: - resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -3269,6 +3275,20 @@ packages: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} + winston-daily-rotate-file@4.7.1: + resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==} + engines: {node: '>=8'} + peerDependencies: + winston: ^3 + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.17.0: + resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==} + engines: {node: '>= 12.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3571,10 +3591,18 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@colors/colors@1.6.0': {} + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -3927,13 +3955,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/schedule@4.1.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))': - dependencies: - '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cron: 3.2.1 - uuid: 11.0.3 - '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.7.2)': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) @@ -4145,10 +4166,6 @@ snapshots: dependencies: '@types/node': 20.19.11 - '@types/cron@2.4.3': - dependencies: - cron: 3.5.0 - '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -4198,8 +4215,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/luxon@3.4.2': {} - '@types/mime@1.3.5': {} '@types/node@20.19.11': @@ -4223,6 +4238,8 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/triple-beam@1.3.5': {} + '@types/validator@13.15.2': {} '@types/yargs-parser@21.0.3': {} @@ -4511,6 +4528,8 @@ snapshots: asap@2.0.6: {} + async@3.2.6: {} + asynckit@0.4.0: {} axios@1.11.0: @@ -4756,12 +4775,33 @@ snapshots: collect-v8-coverage@1.0.2: {} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -4850,16 +4890,6 @@ snapshots: create-require@1.1.1: {} - cron@3.2.1: - dependencies: - '@types/luxon': 3.4.2 - luxon: 3.5.0 - - cron@3.5.0: - dependencies: - '@types/luxon': 3.4.2 - luxon: 3.5.0 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4937,6 +4967,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -5166,6 +5198,8 @@ snapshots: dependencies: bser: 2.1.1 + fecha@4.2.3: {} + fetch-cookie@3.0.1: dependencies: set-cookie-parser: 2.7.1 @@ -5181,6 +5215,10 @@ snapshots: dependencies: flat-cache: 3.2.0 + file-stream-rotator@0.6.1: + dependencies: + moment: 2.30.1 + file-type@20.4.1: dependencies: '@tokenizer/inflate': 0.2.7 @@ -5224,6 +5262,8 @@ snapshots: flatted@3.3.3: {} + fn.name@1.1.0: {} + follow-redirects@1.15.11: {} foreground-child@3.3.1: @@ -5455,6 +5495,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -5897,6 +5939,8 @@ snapshots: kleur@3.0.3: {} + kuler@2.0.0: {} + leven@3.1.0: {} levn@0.4.1: @@ -5931,6 +5975,15 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + 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 + long@5.3.2: {} lossless-json@4.1.1: {} @@ -5941,8 +5994,6 @@ snapshots: dependencies: yallist: 3.1.1 - luxon@3.5.0: {} - magic-string@0.30.8: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6006,6 +6057,8 @@ snapshots: dependencies: minimist: 1.2.8 + moment@2.30.1: {} + ms@2.0.0: {} ms@2.1.3: {} @@ -6052,6 +6105,8 @@ snapshots: object-assign@4.1.1: {} + object-hash@2.2.0: {} + object-inspect@1.13.4: {} on-finished@2.4.1: @@ -6062,6 +6117,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -6308,6 +6367,8 @@ snapshots: safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} schema-utils@3.3.0: @@ -6411,6 +6472,10 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -6435,6 +6500,8 @@ snapshots: sprintf-js@1.0.3: {} + stack-trace@0.0.10: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -6563,6 +6630,8 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + text-hex@1.0.0: {} + text-table@0.2.0: {} through@2.3.8: {} @@ -6596,6 +6665,8 @@ snapshots: tree-kill@1.2.2: {} + triple-beam@1.4.1: {} + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: typescript: 5.9.2 @@ -6724,8 +6795,6 @@ snapshots: utils-merge@1.0.1: {} - uuid@11.0.3: {} - v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: @@ -6802,6 +6871,34 @@ snapshots: dependencies: string-width: 4.2.3 + winston-daily-rotate-file@4.7.1(winston@3.17.0): + dependencies: + file-stream-rotator: 0.6.1 + object-hash: 2.2.0 + triple-beam: 1.4.1 + winston: 3.17.0 + winston-transport: 4.9.0 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.17.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + 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 + word-wrap@1.2.5: {} wordwrap@1.0.0: {} diff --git a/backend/tsconfig.base.json b/backend/tsconfig.base.json index d69d88c5..f01996b6 100644 --- a/backend/tsconfig.base.json +++ b/backend/tsconfig.base.json @@ -6,6 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, "target": "ES2021", "sourceMap": true, "incremental": true, @@ -22,9 +23,7 @@ "@forge/config": ["libs/config/src"], "@forge/config/*": ["libs/config/src/*"], "@forge/logger": ["libs/logger/src"], - "@forge/logger/*": ["libs/logger/src/*"], - "@forge/core": ["libs/core/src"], - "@forge/core/*": ["libs/core/src/*"] + "@forge/logger/*": ["libs/logger/src/*"] } }, "exclude": ["node_modules", "dist"] diff --git a/packages/vault/src/vault/interface.cairo b/packages/vault/src/vault/interface.cairo index ebbc85e8..5e7d9295 100644 --- a/packages/vault/src/vault/interface.cairo +++ b/packages/vault/src/vault/interface.cairo @@ -4,6 +4,7 @@ // Standard library imports use starknet::ContractAddress; +use vault::redeem_request::interface::RedeemRequestInfo; #[starknet::interface] pub trait IVault { @@ -44,5 +45,6 @@ pub trait IVault { fn vault_allocator(self: @TContractState) -> ContractAddress; fn last_report_timestamp(self: @TContractState) -> u64; fn max_delta(self: @TContractState) -> u256; + fn due_assets_from_id(self: @TContractState, id: u256) -> u256; } diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index 2e4fbc80..af0030e1 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -239,6 +239,18 @@ pub mod Vault { // Initialize timestamp for fee calculations self.last_report_timestamp.write(get_block_timestamp()); + + self + .emit( + Report { + new_epoch: 0, + new_handled_epoch_len: 0, + total_supply: 0, + total_assets: 0, + management_fee_shares: 0, + performance_fee_shares: 0, + }, + ); } // --- Contract Upgradeability --- @@ -847,6 +859,15 @@ pub mod Vault { fn max_delta(self: @ContractState) -> u256 { self.max_delta.read() } + + fn due_assets_from_id(self: @ContractState, id: u256) -> u256 { + let redeem_request_info = self.redeem_request.read().id_to_info(id); + let redeem_request_nominal = redeem_request_info + .nominal; // Original asset amount requested + let redeem_assets = self.redeem_assets.read(redeem_request_info.epoch); + let redeem_nominal = self.redeem_nominal.read(redeem_request_info.epoch); + (redeem_request_nominal * redeem_assets) / redeem_nominal + } } // --- Internal Helper Functions --- From e354ea97f57e506e525f89d1a44da6629f59e1b4 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Sat, 23 Aug 2025 21:54:46 +0100 Subject: [PATCH 07/54] Refactor Vesu decoder and sanitizer components - Remove unused vault_allocator field from storage in multiple decoder components - Add new ModifyPositionParamsV2 and ModifyLever types for V2 integration - Clean up transfer_position functionality in Vesu decoder - Add multiply and vesu_v2 decoder sanitizer modules to lib.cairo - Reorganize test structure and rename vesu integration test to vesu_v1 - Remove deprecated test creator module --- .../avnu_exchange_decoder_and_sanitizer.cairo | 4 +- .../decoder_custom_types.cairo | 89 +++++++++++++++++ .../erc4626_decoder_and_sanitizer.cairo | 4 +- .../interface.cairo | 11 +++ .../multiply_decoder_and_sanitizer.cairo | 45 +++++++++ .../interface.cairo | 5 +- .../vesu_decoder_and_sanitizer.cairo | 36 +------ .../interface.cairo | 11 +++ .../vesu_v2_decoder_and_sanitizer.cairo | 35 +++++++ .../src/integration_interfaces/vesu.cairo | 1 - packages/vault_allocator/src/lib.cairo | 14 ++- .../{vesu.cairo => vesu_v1.cairo} | 96 ++----------------- .../leveraged_loop_staked_ether.cairo | 4 +- .../test/scenarios/stable_carry_loop.cairo | 8 +- packages/vault_allocator/src/test/utils.cairo | 86 ----------------- 15 files changed, 224 insertions(+), 225 deletions(-) create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/interface.cairo create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/interface.cairo create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo rename packages/vault_allocator/src/test/integrations/{vesu.cairo => vesu_v1.cairo} (81%) diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo index d891bb0e..da60c7b4 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo @@ -9,9 +9,7 @@ pub mod AvnuExchangeDecoderAndSanitizerComponent { use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; #[storage] - pub struct Storage { - pub vault_allocator: ContractAddress, - } + pub struct Storage {} #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo index 1a4b60a6..f26e5494 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo @@ -71,3 +71,92 @@ pub struct ModifyPositionParams { pub debt: Amount, pub data: Span, } + +#[derive(PartialEq, Copy, Drop, Serde)] +pub struct ModifyPositionParamsV2 { + pub collateral_asset: ContractAddress, + pub debt_asset: ContractAddress, + pub user: ContractAddress, + pub collateral: Amount, + pub debt: Amount, + pub data: Span, +} + + +#[derive(Serde, Drop, Clone)] +pub enum ModifyLeverAction { + IncreaseLever: IncreaseLeverParams, + DecreaseLever: DecreaseLeverParams, +} + +#[derive(Serde, Drop, Clone)] +pub struct ModifyLeverParams { + pub action: ModifyLeverAction, +} + + +#[derive(Serde, Drop, Clone)] +pub struct IncreaseLeverParams { + pub pool_id: felt252, + pub collateral_asset: ContractAddress, + pub debt_asset: ContractAddress, + pub user: ContractAddress, + pub add_margin: u128, + pub margin_swap: Array, + pub margin_swap_limit_amount: u128, + pub lever_swap: Array, + pub lever_swap_limit_amount: u128, +} + +#[derive(Serde, Drop, Clone)] +pub struct DecreaseLeverParams { + pub pool_id: felt252, + pub collateral_asset: ContractAddress, + pub debt_asset: ContractAddress, + pub user: ContractAddress, + pub sub_margin: u128, + pub recipient: ContractAddress, + pub lever_swap: Array, + pub lever_swap_limit_amount: u128, + pub lever_swap_weights: Array, + pub withdraw_swap: Array, + pub withdraw_swap_limit_amount: u128, + pub withdraw_swap_weights: Array, + pub close_position: bool, +} + +#[derive(Serde, Drop, Clone)] +pub struct Swap { + pub route: Array, + pub token_amount: TokenAmount, +} + + +#[derive(Serde, Copy, Drop)] +pub struct RouteNode { + pub pool_key: PoolKey, + pub sqrt_ratio_limit: u256, + pub skip_ahead: u128, +} + + +#[derive(Serde, Copy, Drop)] +pub struct TokenAmount { + pub token: ContractAddress, + pub amount: i129, +} + +#[derive(Copy, Drop, Serde, PartialEq, Hash)] +pub struct PoolKey { + pub token0: ContractAddress, + pub token1: ContractAddress, + pub fee: u128, + pub tick_spacing: u128, + pub extension: ContractAddress, +} + +#[derive(Copy, Drop, Serde, Debug)] +pub struct i129 { + pub mag: u128, + pub sign: bool, +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo index 0bafff05..3b4f812f 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo @@ -8,9 +8,7 @@ pub mod Erc4626DecoderAndSanitizerComponent { use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::interface::IERC4626DecoderAndSanitizer; #[storage] - pub struct Storage { - pub vault_allocator: ContractAddress, - } + pub struct Storage {} #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..4110e379 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyLeverParams; + +#[starknet::interface] +pub trait IMultiplyDecoderAndSanitizer { + fn modify_lever(self: @T, modify_lever_params: ModifyLeverParams) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..6d4a8ff6 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod MultiplyDecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ + ModifyLeverAction, ModifyLeverParams, + }; + use vault_allocator::decoders_and_sanitizers::multiply_decoder_and_sanitizer::interface::IMultiplyDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(VesuDecoderAndSanitizerImpl)] + impl VesuDecoderAndSanitizer< + TContractState, +HasComponent, + > of IMultiplyDecoderAndSanitizer> { + fn modify_lever( + self: @ComponentState, modify_lever_params: ModifyLeverParams, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + match modify_lever_params.action { + ModifyLeverAction::IncreaseLever(params) => { + params.pool_id.serialize(ref serialized_struct); + params.collateral_asset.serialize(ref serialized_struct); + params.debt_asset.serialize(ref serialized_struct); + params.user.serialize(ref serialized_struct); + }, + ModifyLeverAction::DecreaseLever(params) => { + params.pool_id.serialize(ref serialized_struct); + params.collateral_asset.serialize(ref serialized_struct); + params.debt_asset.serialize(ref serialized_struct); + params.user.serialize(ref serialized_struct); + }, + } + + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo index 9dd2c4a5..e8a25500 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo @@ -2,13 +2,10 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - ModifyPositionParams, TransferPositionParams, -}; +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParams; #[starknet::interface] pub trait IVesuDecoderAndSanitizer { - fn transfer_position(self: @T, params: TransferPositionParams) -> Span; fn modify_position(self: @T, params: ModifyPositionParams) -> Span; } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo index 56c1abd8..91747a65 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo @@ -4,18 +4,13 @@ #[starknet::component] pub mod VesuDecoderAndSanitizerComponent { - use starknet::ContractAddress; - use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - ModifyPositionParams, TransferPositionParams, - }; + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParams; use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; use vault_allocator::decoders_and_sanitizers::vesu_decoder_and_sanitizer::interface::IVesuDecoderAndSanitizer; #[storage] - pub struct Storage { - pub vault_allocator: ContractAddress, - } + pub struct Storage {} #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] @@ -27,29 +22,6 @@ pub mod VesuDecoderAndSanitizerComponent { +HasComponent, +Erc4626DecoderAndSanitizerComponent::HasComponent, > of IVesuDecoderAndSanitizer> { - fn transfer_position( - self: @ComponentState, params: TransferPositionParams, - ) -> Span { - let mut serialized_struct: Array = ArrayTrait::new(); - params.pool_id.serialize(ref serialized_struct); - params.from_collateral_asset.serialize(ref serialized_struct); - - // From debt asset, isn't it always 0 ? to confirm - params.from_debt_asset.serialize(ref serialized_struct); - params.to_collateral_asset.serialize(ref serialized_struct); - params.to_debt_asset.serialize(ref serialized_struct); - - //From user is in fact the extensin : to confirm - params.from_user.serialize(ref serialized_struct); - params.to_user.serialize(ref serialized_struct); - - //not sure to include those 2 fields : to confirm - params.from_data.serialize(ref serialized_struct); - params.to_data.serialize(ref serialized_struct); - serialized_struct.span() - } - - fn modify_position( self: @ComponentState, params: ModifyPositionParams, ) -> Span { @@ -58,10 +30,6 @@ pub mod VesuDecoderAndSanitizerComponent { params.collateral_asset.serialize(ref serialized_struct); params.debt_asset.serialize(ref serialized_struct); params.user.serialize(ref serialized_struct); - - // not sure to include this fields : to confirm - params.data.serialize(ref serialized_struct); - serialized_struct.span() } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..0bf999c5 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParamsV2; + +#[starknet::interface] +pub trait IVesuV2DecoderAndSanitizer { + fn modify_position(self: @T, params: ModifyPositionParamsV2) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..996c50a1 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod VesuV2DecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParamsV2; + use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; + use vault_allocator::decoders_and_sanitizers::vesu_v2_decoder_and_sanitizer::interface::IVesuV2DecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(VesuDecoderAndSanitizerImpl)] + impl VesuDecoderAndSanitizer< + TContractState, + +HasComponent, + +Erc4626DecoderAndSanitizerComponent::HasComponent, + > of IVesuV2DecoderAndSanitizer> { + fn modify_position( + self: @ComponentState, params: ModifyPositionParamsV2, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + params.collateral_asset.serialize(ref serialized_struct); + params.debt_asset.serialize(ref serialized_struct); + params.user.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/integration_interfaces/vesu.cairo b/packages/vault_allocator/src/integration_interfaces/vesu.cairo index 1f7d13b4..73aaccd4 100644 --- a/packages/vault_allocator/src/integration_interfaces/vesu.cairo +++ b/packages/vault_allocator/src/integration_interfaces/vesu.cairo @@ -140,7 +140,6 @@ pub trait ISingletonV2< // fn modify_position( // ref self: TContractState, params: ModifyPositionParams, // ) -> UpdatePositionResponse; - // fn transfer_position(ref self: TContractState, params: TransferPositionParams); // fn liquidate_position( // ref self: TContractState, params: LiquidatePositionParams, // ) -> UpdatePositionResponse; diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 0d3b192b..00dfd9ae 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -53,6 +53,16 @@ pub mod decoders_and_sanitizers { pub mod interface; pub mod vesu_decoder_and_sanitizer; } + + pub mod vesu_v2_decoder_and_sanitizer { + pub mod interface; + pub mod vesu_v2_decoder_and_sanitizer; + } + + pub mod multiply_decoder_and_sanitizer { + pub mod interface; + pub mod multiply_decoder_and_sanitizer; + } } pub mod mocks { @@ -64,7 +74,7 @@ pub mod mocks { #[cfg(test)] pub mod test { - pub mod creator; + // pub mod creator; pub mod register; pub mod utils; pub mod units { @@ -73,7 +83,7 @@ pub mod test { } pub mod integrations { pub mod avnu; - pub mod vesu; + pub mod vesu_v1; } pub mod scenarios { diff --git a/packages/vault_allocator/src/test/integrations/vesu.cairo b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo similarity index 81% rename from packages/vault_allocator/src/test/integrations/vesu.cairo rename to packages/vault_allocator/src/test/integrations/vesu_v1.cairo index fab5582c..b58c05cf 100644 --- a/packages/vault_allocator/src/test/integrations/vesu.cairo +++ b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo @@ -3,13 +3,11 @@ // Licensed under the MIT License. See LICENSE file for details. use alexandria_math::i257::{I257Impl, I257Trait}; -use core::num::traits::Zero; use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use snforge_std::{map_entry_address, store}; -use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - Amount, AmountDenomination, AmountType, UnsignedAmount, + Amount, AmountDenomination, AmountType, }; use vault_allocator::integration_interfaces::vesu::{ IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, @@ -328,93 +326,26 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { .extension(GENESIS_POOL_ID); let v_token = IDefaultExtensionPOV2Dispatcher { contract_address: extension } .v_token_for_collateral_asset(GENESIS_POOL_ID, wstETH()); - let v_token_erc4626_disp = IERC4626Dispatcher { contract_address: v_token }; - let expected_shares_obtained = v_token_erc4626_disp.preview_deposit(deposit_amount); - let debt_amount: u256 = WAD / 4; // 25% of the deposit + let debt_amount: u256 = WAD / 40; // 2.5% of the deposit let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); let mut array_of_targets = ArrayTrait::new(); array_of_targets.append(wstETH()); - array_of_targets.append(v_token); - array_of_targets.append(v_token); - array_of_targets.append(VESU_SINGLETON()); array_of_targets.append(VESU_SINGLETON()); let mut array_of_selectors = ArrayTrait::new(); array_of_selectors.append(selector!("approve")); - array_of_selectors.append(selector!("deposit")); - array_of_selectors.append(selector!("approve")); - array_of_selectors.append(selector!("transfer_position")); array_of_selectors.append(selector!("modify_position")); let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldata_approve: Array = ArrayTrait::new(); - v_token.serialize(ref array_of_calldata_approve); - deposit_amount.serialize(ref array_of_calldata_approve); - array_of_calldatas.append(array_of_calldata_approve.span()); - - let mut array_of_calldata_deposit: Array = ArrayTrait::new(); - deposit_amount.serialize(ref array_of_calldata_deposit); - vault_allocator.contract_address.serialize(ref array_of_calldata_deposit); - array_of_calldatas.append(array_of_calldata_deposit.span()); - - let mut array_of_calldata_approve_vtoken: Array = ArrayTrait::new(); - extension.serialize(ref array_of_calldata_approve_vtoken); - expected_shares_obtained.serialize(ref array_of_calldata_approve_vtoken); - array_of_calldatas.append(array_of_calldata_approve_vtoken.span()); - - let mut array_of_calldata_transfer_position: Array = ArrayTrait::new(); - // pool_id - GENESIS_POOL_ID.serialize(ref array_of_calldata_transfer_position); - - // from_collateral_asset - wstETH().serialize(ref array_of_calldata_transfer_position); - // from_debt_asset - let from_debt_asset: ContractAddress = Zero::zero(); - from_debt_asset.serialize(ref array_of_calldata_transfer_position); - - // to_collateral_asset - wstETH().serialize(ref array_of_calldata_transfer_position); - - // to_debt_asset - ETH().serialize(ref array_of_calldata_transfer_position); - - // from_user - extension.serialize(ref array_of_calldata_transfer_position); - - // to_user - vault_allocator.contract_address.serialize(ref array_of_calldata_transfer_position); - - // collateral - let collateral: UnsignedAmount = UnsignedAmount { - amount_type: AmountType::Delta, - denomination: AmountDenomination::Native, - value: expected_shares_obtained, - }; - collateral.serialize(ref array_of_calldata_transfer_position); - - // debt - let debt: UnsignedAmount = UnsignedAmount { - amount_type: AmountType::Delta, denomination: AmountDenomination::Native, value: 0, - }; - debt.serialize(ref array_of_calldata_transfer_position); - - // from_data - let from_data: Span = array![].span(); - from_data.serialize(ref array_of_calldata_transfer_position); - - // to_data - let to_data: Span = array![].span(); - to_data.serialize(ref array_of_calldata_transfer_position); - - array_of_calldatas.append(array_of_calldata_transfer_position.span()); + let mut array_of_calldata_approve_token: Array = ArrayTrait::new(); + VESU_SINGLETON().serialize(ref array_of_calldata_approve_token); + deposit_amount.serialize(ref array_of_calldata_approve_token); + array_of_calldatas.append(array_of_calldata_approve_token.span()); let mut array_of_calldata_modify_position: Array = ArrayTrait::new(); // pool_id @@ -430,10 +361,10 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { vault_allocator.contract_address.serialize(ref array_of_calldata_modify_position); // collateral - let value_for_collateral_modify_position = I257Trait::new(0, false); + let value_for_collateral_modify_position = I257Trait::new((deposit_amount), false); let collateral_modify_position: Amount = Amount { amount_type: AmountType::Delta, - denomination: AmountDenomination::Native, + denomination: AmountDenomination::Assets, value: value_for_collateral_modify_position, }; collateral_modify_position.serialize(ref array_of_calldata_modify_position); @@ -454,11 +385,8 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { array_of_calldatas.append(array_of_calldata_modify_position.span()); let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - manage_leafs.append(leafs.at(1).clone()); + manage_leafs.append(leafs.at(5).clone()); manage_leafs.append(leafs.at(6).clone()); - manage_leafs.append(leafs.at(7).clone()); - manage_leafs.append(leafs.at(8).clone()); let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); cheat_caller_address_once(manager.contract_address, STRATEGIST()); @@ -470,15 +398,11 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { array_of_selectors.span(), array_of_calldatas.span(), ); - let new_underlying_balance = underlying_disp.balance_of(vault_allocator.contract_address); assert(new_underlying_balance == initial_wsteth_balance - deposit_amount, 'incorrect'); - let v_token_erc20_disp = ERC20ABIDispatcher { contract_address: v_token }; - let vault_shares_balance = v_token_erc20_disp.balance_of(vault_allocator.contract_address); - assert(vault_shares_balance == 0, 'incorrect'); - let debt_asset_disp = ERC20ABIDispatcher { contract_address: ETH() }; let debt_asset_balance = debt_asset_disp.balance_of(vault_allocator.contract_address); assert(debt_asset_balance == debt_amount, 'incorrect'); } + diff --git a/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo b/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo index 76e31018..5fef2aa9 100644 --- a/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo +++ b/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo @@ -236,9 +236,9 @@ fn test_leveraged_loop_staked_ether() { let mut flash_loan_manager_leafs: Array = ArrayTrait::new(); flash_loan_manager_leafs.append(leafs.at(6).clone()); + flash_loan_manager_leafs.append(leafs.at(7).clone()); + flash_loan_manager_leafs.append(leafs.at(8).clone()); flash_loan_manager_leafs.append(leafs.at(9).clone()); - flash_loan_manager_leafs.append(leafs.at(10).clone()); - flash_loan_manager_leafs.append(leafs.at(11).clone()); let mut flash_loan_proofs = _get_proofs_using_tree(flash_loan_manager_leafs, tree.clone()); diff --git a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo index 6915fd9a..89924332 100644 --- a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo +++ b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo @@ -226,11 +226,11 @@ fn test_stable_carry_loop() { let mut manage_leafs: Array = ArrayTrait::new(); manage_leafs.append(leafs.at(5).clone()); + manage_leafs.append(leafs.at(6).clone()); + manage_leafs.append(leafs.at(12).clone()); + manage_leafs.append(leafs.at(13).clone()); + manage_leafs.append(leafs.at(7).clone()); manage_leafs.append(leafs.at(8).clone()); - manage_leafs.append(leafs.at(14).clone()); - manage_leafs.append(leafs.at(15).clone()); - manage_leafs.append(leafs.at(9).clone()); - manage_leafs.append(leafs.at(10).clone()); cheat_caller_address_once(avnu_middleware, OWNER()); IAvnuMiddlewareDispatcher { contract_address: avnu_middleware } diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index 857226cb..d000d071 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -483,9 +483,6 @@ pub fn _add_vesu_leafs( @pool_id, 16, ); - // can be performed via transfering the v-token with transfer_position - // or can be performed via modify_position directly - // APPROVAL of collateral asset to the singleton leafs .append( @@ -507,87 +504,8 @@ pub fn _add_vesu_leafs( ); leaf_index += 1; - // APPROVAL of v-token to the extension - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: v_token, - selector: selector!("approve"), - argument_addresses: array![pool_extension.contract_address.into()] - .span(), - description: "Approve" - + " " - + "extension_pid" - + "_" - + pool_id_str.clone() - + " " - + "to spend" - + " " - + get_symbol(v_token), - }, - ); - leaf_index += 1; - let debt_asset = *debt_assets.at(j); - // TRANSFER POSITION to create the pair collateral/debt - let mut argument_addresses_transfer_position = ArrayTrait::new(); - - // pool_id - pool_id.serialize(ref argument_addresses_transfer_position); - - // from_collateral_asset - collateral_asset.serialize(ref argument_addresses_transfer_position); - - // from_debt_asset - let from_debt_asset: ContractAddress = Zero::zero(); - from_debt_asset.serialize(ref argument_addresses_transfer_position); - - // to_collateral_asset - collateral_asset.serialize(ref argument_addresses_transfer_position); - - // to_debt_asset - debt_asset.serialize(ref argument_addresses_transfer_position); - - // from_user - pool_extension.contract_address.serialize(ref argument_addresses_transfer_position); - - // to_user - vault.serialize(ref argument_addresses_transfer_position); - - // from_data - let from_data: Span = array![].span(); - from_data.serialize(ref argument_addresses_transfer_position); - - // to_data - let to_data: Span = array![].span(); - to_data.serialize(ref argument_addresses_transfer_position); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: singleton.contract_address, - selector: selector!("transfer_position"), - argument_addresses: argument_addresses_transfer_position.span(), - description: "Transfer position" - + " " - + "extension_pid" - + "_" - + pool_id_str.clone() - + " " - + "with collateral" - + " " - + get_symbol(collateral_asset) - + " " - + "and debt" - + " " - + get_symbol(debt_asset), - }, - ); - leaf_index += 1; - // MODIFY POSITION let mut argument_addresses_modify_position = ArrayTrait::new(); @@ -603,10 +521,6 @@ pub fn _add_vesu_leafs( // user vault.serialize(ref argument_addresses_modify_position); - // data - let data: Span = array![].span(); - data.serialize(ref argument_addresses_modify_position); - leafs .append( ManageLeaf { From 77f126510bee1e2d96cabc991175e6afd9c115b4 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Sat, 23 Aug 2025 21:59:28 +0100 Subject: [PATCH 08/54] Add event logging for vault allocator contract calls - Add CallPerformed event to track all contract calls made through manage functions - Refactor manage and manage_multi to use new call_contract helper - Emit detailed call information including contract address, selector, calldata, and results --- .../src/vault_allocator/vault_allocator.cairo | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo index cdfdff2c..54be9608 100644 --- a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo +++ b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo @@ -31,6 +31,16 @@ pub mod VaultAllocator { pub enum Event { OwnableEvent: OwnableComponent::Event, UpgradeableEvent: UpgradeableComponent::Event, + CallPerformed: CallPerformed, + } + + /// Event emitted when a user requests a redemption + #[derive(Drop, starknet::Event)] + pub struct CallPerformed { + pub to: ContractAddress, + pub selector: felt252, + pub calldata: Span, + pub result: Span, } @@ -67,7 +77,7 @@ pub mod VaultAllocator { fn manage(ref self: ContractState, call: Call) -> Span { self._only_manager(); - call_contract_syscall(call.to, call.selector, call.calldata).unwrap_syscall() + self.call_contract(call.to, call.selector, call.calldata) } fn manage_multi(ref self: ContractState, calls: Array) -> Array> { @@ -76,11 +86,7 @@ pub mod VaultAllocator { let calls_len = calls.len(); for i in 0..calls_len { let call = *calls.at(i); - results - .append( - call_contract_syscall(call.to, call.selector, call.calldata) - .unwrap_syscall(), - ); + results.append(self.call_contract(call.to, call.selector, call.calldata)); } results } @@ -94,5 +100,16 @@ pub mod VaultAllocator { Errors::only_manager(); } } + + fn call_contract( + ref self: ContractState, + to: ContractAddress, + selector: felt252, + calldata: Span, + ) -> Span { + let result = call_contract_syscall(to, selector, calldata).unwrap_syscall(); + self.emit(CallPerformed { to, selector, calldata, result }); + result + } } } From 515fb28ad140f20211c8f9f3e9e15f986ca62865 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:54:56 +0100 Subject: [PATCH 09/54] Enhance AVNU middleware with rate limiting and improved swap logic - Add configurable rate limiting with period-based call counting - Improve swap logic with proper zero amount handling and math utilities - Update decoder types to support AmountV2 with denomination field - Add comprehensive error handling for rate limits and validation - Refactor slippage calculation using math library for better precision --- .../decoder_custom_types.cairo | 26 ++---- .../avnu_middleware/avnu_middleware.cairo | 91 ++++++++++++++++--- .../middlewares/avnu_middleware/errors.cairo | 12 +++ .../avnu_middleware/interface.cairo | 4 +- 4 files changed, 99 insertions(+), 34 deletions(-) diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo index f26e5494..8cb7edf8 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo @@ -39,21 +39,6 @@ pub struct UnsignedAmount { pub value: u256, } -#[derive(PartialEq, Copy, Drop, Serde)] -pub struct TransferPositionParams { - pub pool_id: felt252, - pub from_collateral_asset: ContractAddress, - pub from_debt_asset: ContractAddress, - pub to_collateral_asset: ContractAddress, - pub to_debt_asset: ContractAddress, - pub from_user: ContractAddress, - pub to_user: ContractAddress, - pub collateral: UnsignedAmount, - pub debt: UnsignedAmount, - pub from_data: Span, - pub to_data: Span, -} - #[derive(PartialEq, Copy, Drop, Serde, Default)] pub struct Amount { pub amount_type: AmountType, @@ -61,6 +46,12 @@ pub struct Amount { pub value: i257, } +#[derive(PartialEq, Copy, Drop, Serde, Default)] +pub struct AmountV2 { + pub denomination: AmountDenomination, + pub value: i257, +} + #[derive(PartialEq, Copy, Drop, Serde)] pub struct ModifyPositionParams { pub pool_id: felt252, @@ -77,9 +68,8 @@ pub struct ModifyPositionParamsV2 { pub collateral_asset: ContractAddress, pub debt_asset: ContractAddress, pub user: ContractAddress, - pub collateral: Amount, - pub debt: Amount, - pub data: Span, + pub collateral: AmountV2, + pub debt: AmountV2, } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo index 50585e57..6272c225 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -4,10 +4,16 @@ #[starknet::contract] pub mod AvnuMiddleware { + const BPS_SCALE: u256 = 10_000_u256; + use core::num::traits::Zero; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use openzeppelin::utils::math; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, + }; + use starknet::{ContractAddress, get_block_timestamp, get_caller_address, get_contract_address}; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; use vault_allocator::integration_interfaces::avnu::{ IAvnuExchangeDispatcher, IAvnuExchangeDispatcherTrait, @@ -18,12 +24,19 @@ pub mod AvnuMiddleware { IPriceRouterDispatcher, IPriceRouterDispatcherTrait, }; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; impl InternalImpl = OwnableComponent::InternalImpl; + #[derive(Copy, Drop, Serde, starknet::Store)] + struct Config { + period: u64, + allowed_calls_per_period: u64, + } + #[storage] struct Storage { @@ -32,6 +45,8 @@ pub mod AvnuMiddleware { avnu_router: IAvnuExchangeDispatcher, price_router: IPriceRouterDispatcher, slippage_tolerance_bps: u256, + config: Option, + call_count: Map<(ContractAddress, u64), u64>, } #[event] @@ -40,12 +55,18 @@ pub mod AvnuMiddleware { #[flat] OwnableEvent: OwnableComponent::Event, SlippageUpdated: SlippageUpdated, + ConfigUpdated: ConfigUpdated, } #[derive(Drop, starknet::Event)] pub struct SlippageUpdated { - pub old_slippage: u256, - pub new_slippage: u256, + pub slippage: u256, + } + + #[derive(Drop, starknet::Event)] + struct ConfigUpdated { + period: u64, + allowed_calls_per_period: u64, } @@ -78,18 +99,29 @@ pub mod AvnuMiddleware { self.slippage_tolerance_bps.read() } + fn set_config(ref self: ContractState, period: u64, allowed_calls_per_period: u64) { + self.ownable.assert_only_owner(); + if (period.is_zero()) { + Errors::period_zero(); + } + if (allowed_calls_per_period.is_zero()) { + Errors::allowed_calls_per_period_zero(); + } + self.config.write(Option::Some(Config { period, allowed_calls_per_period })); + self.emit(ConfigUpdated { period, allowed_calls_per_period }); + } + fn set_slippage_tolerance_bps(ref self: ContractState, new_slippage_bps: u256) { self.ownable.assert_only_owner(); - if (new_slippage_bps > 10_000) { + if (new_slippage_bps >= BPS_SCALE) { Errors::slippage_exceeds_max(new_slippage_bps); } - let old_slippage = self.slippage_tolerance_bps.read(); self.slippage_tolerance_bps.write(new_slippage_bps); - self.emit(SlippageUpdated { old_slippage, new_slippage: new_slippage_bps }); + self.emit(SlippageUpdated { slippage: new_slippage_bps }); } fn multi_route_swap( - self: @ContractState, + ref self: ContractState, sell_token_address: ContractAddress, sell_token_amount: u256, buy_token_address: ContractAddress, @@ -101,7 +133,13 @@ pub mod AvnuMiddleware { routes: Array, ) -> u256 { let caller = get_caller_address(); + self.enforce_rate_limit(caller); let this = get_contract_address(); + + if (sell_token_amount == Zero::zero()) { + return Zero::zero(); + } + if sell_token_address == buy_token_address { ERC20ABIDispatcher { contract_address: sell_token_address } .transfer_from(caller, beneficiary, sell_token_amount); @@ -117,9 +155,13 @@ pub mod AvnuMiddleware { .read() .get_value(sell_token_address, sell_token_amount, buy_token_address); - let computed_min = quote_out - * (10_000_u256 - self.slippage_tolerance_bps.read()) - / 10_000_u256; + let computed_min = math::u256_mul_div( + quote_out, + BPS_SCALE - self.slippage_tolerance_bps.read(), + BPS_SCALE, + math::Rounding::Ceil, + ); + let min_out = if buy_token_min_amount < computed_min { computed_min } else { @@ -132,11 +174,11 @@ pub mod AvnuMiddleware { sell_token_address, sell_token_amount, buy_token_address, - buy_token_amount, + Zero::zero(), min_out, this, - integrator_fee_amount_bps, - integrator_fee_recipient, + Zero::zero(), + Zero::zero(), routes, ); let buy_bal_1 = buy.balance_of(this); @@ -145,7 +187,26 @@ pub mod AvnuMiddleware { Errors::insufficient_output(out, min_out); } buy.transfer(beneficiary, out); - 0 + out + } + } + + + #[generate_trait] + pub impl InternalFunctions of InternalFunctionsTrait { + fn enforce_rate_limit(ref self: ContractState, vault: ContractAddress) { + if (self.config.read().is_some()) { + let cfg = self.config.read().unwrap(); + let ts: u64 = get_block_timestamp(); + let slot = ts % cfg.period; + let key = (vault, slot); + let current = self.call_count.read(key); + let next = current + 1; + if (next > cfg.allowed_calls_per_period) { + Errors::rate_limit_exceeded(next, cfg.allowed_calls_per_period); + } + self.call_count.write(key, next); + } } } } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo index 0e62477c..1c90966a 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo @@ -10,4 +10,16 @@ pub mod Errors { pub fn slippage_exceeds_max(slippage: u256) { panic!("Slippage exceeds max: {}", slippage); } + + pub fn rate_limit_exceeded(next: u64, allowed: u64) { + panic!("Rate limit exceeded: {} > {}", next, allowed); + } + + pub fn period_zero() { + panic!("Period is zero"); + } + + pub fn allowed_calls_per_period_zero() { + panic!("Allowed calls per period is zero"); + } } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo index bca4ad9d..9fbb8bb5 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo @@ -11,9 +11,11 @@ pub trait IAvnuMiddleware { fn price_router(self: @T) -> ContractAddress; fn slippage_tolerance_bps(self: @T) -> u256; + fn set_config(ref self: T, period: u64, allowed_calls_per_period: u64); + fn multi_route_swap( - self: @T, + ref self: T, sell_token_address: ContractAddress, sell_token_amount: u256, buy_token_address: ContractAddress, From fb4c9b54b0e729f1a5632f878f410d22b1ac1b41 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:36:04 +0100 Subject: [PATCH 10/54] Remove unused import and fix price calculation in vault packages - Remove unused RedeemRequestInfo import from vault interface - Fix price calculation using u256_mul_div with ceiling rounding in price router --- packages/vault/src/vault/interface.cairo | 1 - .../src/periphery/price_router/price_router.cairo | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vault/src/vault/interface.cairo b/packages/vault/src/vault/interface.cairo index 5e7d9295..b2287f74 100644 --- a/packages/vault/src/vault/interface.cairo +++ b/packages/vault/src/vault/interface.cairo @@ -4,7 +4,6 @@ // Standard library imports use starknet::ContractAddress; -use vault::redeem_request::interface::RedeemRequestInfo; #[starknet::interface] pub trait IVault { diff --git a/packages/vault_allocator/src/periphery/price_router/price_router.cairo b/packages/vault_allocator/src/periphery/price_router/price_router.cairo index 6acff3e3..46928f55 100644 --- a/packages/vault_allocator/src/periphery/price_router/price_router.cairo +++ b/packages/vault_allocator/src/periphery/price_router/price_router.cairo @@ -7,6 +7,7 @@ pub mod PriceRouter { use core::num::traits::{Pow, Zero}; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::utils::math; use starknet::ContractAddress; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, @@ -89,7 +90,7 @@ pub mod PriceRouter { let num: u256 = amount * base_price * scale_quote; let den: u256 = quote_price * scale_base; - num / den + math::u256_mul_div(num, 1, den, math::Rounding::Ceil) } fn asset_to_id(self: @ContractState, asset: ContractAddress) -> felt252 { self.asset_to_id.read(asset) From 1f1b04c1f19c8322140af9161b2cd44244516ca9 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:36:59 +0100 Subject: [PATCH 11/54] Add scripts directory and update gitignore - Add deployment files exclusion to .gitignore - Include .env in gitignore - Add scripts directory with deployment utilities --- .gitignore | 7 +- scripts/.env.example | 7 + scripts/.gitignore | 48 +++ scripts/README.md | 108 ++++++ scripts/configs/config.json | 22 ++ scripts/configs/utils.ts | 48 +++ scripts/constants.ts | 0 scripts/declareContract.ts | 100 +++++ scripts/deployContract.ts | 237 ++++++++++++ scripts/deployVault.ts | 648 ++++++++++++++++++++++++++++++++ scripts/managerConfig.ts | 144 ++++++++ scripts/package.json | 38 ++ scripts/pnpm-lock.yaml | 717 ++++++++++++++++++++++++++++++++++++ scripts/utils.ts | 28 ++ scripts/utils/deployment.ts | 96 +++++ scripts/vaultConfig.ts | 297 +++++++++++++++ 16 files changed, 2544 insertions(+), 1 deletion(-) create mode 100644 scripts/.env.example create mode 100644 scripts/.gitignore create mode 100644 scripts/README.md create mode 100644 scripts/configs/config.json create mode 100644 scripts/configs/utils.ts create mode 100644 scripts/constants.ts create mode 100644 scripts/declareContract.ts create mode 100644 scripts/deployContract.ts create mode 100644 scripts/deployVault.ts create mode 100644 scripts/managerConfig.ts create mode 100644 scripts/package.json create mode 100644 scripts/pnpm-lock.yaml create mode 100644 scripts/utils.ts create mode 100644 scripts/utils/deployment.ts create mode 100644 scripts/vaultConfig.ts diff --git a/.gitignore b/.gitignore index ef21a7cc..801ede80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ target -.snfoundry_cache \ No newline at end of file +.snfoundry_cache + +# Deployment files (user-specific) +scripts/deployments.json + +.env \ No newline at end of file diff --git a/scripts/.env.example b/scripts/.env.example new file mode 100644 index 00000000..13e97ed2 --- /dev/null +++ b/scripts/.env.example @@ -0,0 +1,7 @@ +# StarkNet RPC URLs + +RPC=https://starknet-sepolia.public.blastapi.io + +# Account configuration +ACCOUNT_ADDRESS=0x... +ACCOUNT_PK=0x... \ No newline at end of file diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 00000000..561b07d1 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,48 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build outputs +dist/ +build/ +*.tsbuildinfo + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +*.temp +*.log + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ + +# Cairo/Starknet +target/ +*.sierra +*.json.gz \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..371b377d --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,108 @@ +# StarkNet Vault Kit Scripts + +Collection of deployment and management scripts for the StarkNet Vault Kit. + +## Prerequisites + +- Node.js and pnpm installed +- StarkNet account with sufficient balance +- Environment variables configured in `.env` + +## Environment Setup + +Create a `.env` file in the project root with: + +```env +RPC= +ACCOUNT_ADDRESS= +ACCOUNT_PK= +``` + +## Available Scripts + +### Contract Declaration + +```bash +# Declare any contract +pnpm declare --contract + +# Declare specific contracts +pnpm declare:vault +pnpm declare:vault-allocator +pnpm declare:redeem-request +pnpm declare:avnu-middleware +pnpm declare:manager +pnpm declare:price-router +pnpm declare:decoder-sanitizer +``` + +### Contract Deployment + +```bash +# Deploy any contract +pnpm deploy --contract + +# Deploy vault with interactive setup +pnpm deploy:vault +``` + +### Configuration Management + +```bash +# Configure vault settings +pnpm vault:config + +# Configure manager settings +pnpm manager:config +``` + +## Script Details + +### `declareContract.ts` + +Declares Cairo contracts on StarkNet. Supports all vault ecosystem contracts. + +### `deployContract.ts` + +Deploys declared contracts with constructor parameters. + +### `deployVault.ts` + +Interactive vault deployment with automatic dependency setup. + +### `vaultConfig.ts` + +Interactive vault configuration management: + +- Set fees configuration (redeem, management, performance) +- Configure report delays +- Set max delta parameters + +### `managerConfig.ts` + +Interactive manager configuration: + +- Set manage root (merkle tree root for strategist targets) + +## Usage Examples + +```bash +# Full deployment workflow +pnpm declare:vault +pnpm declare:vault-allocator +pnpm deploy:vault + +# Configure deployed vault +pnpm vault:config + +# Set up manager permissions +pnpm manager:config +``` + +## Notes + +- All scripts use interactive prompts for safe parameter input +- Transaction hashes are displayed for verification +- Deployment addresses are saved to `deployments.json` +- Scripts validate inputs before execution +- Network detection is automatic based on RPC endpoint diff --git a/scripts/configs/config.json b/scripts/configs/config.json new file mode 100644 index 00000000..359462ee --- /dev/null +++ b/scripts/configs/config.json @@ -0,0 +1,22 @@ +{ + "sepolia": { + "hash": {}, + "periphery": {} + }, + "mainnet": { + "hash": { + "Vault": "0x5058442b8c9d18dc0fdfa4beea4c2d5dc843bc1d121dc6f8acba9f9252b4072", + "VaultAllocator": "0x7f2faf80d197cbfd7286fa0b8328e9d15eff219138611573beb8280a92712e5", + "RedeemRequest": "0x213cf96be799ccd7d4775e3798c880c093197ac19313d6032f43c8d22a6346a", + "AvnuMiddleware": "0x142dff46d17eb94e9c77a771968ccd8257d8eed5d6a84c3e37f50e15dc39a9f", + "Manager": "0x67a612fc0c9f0d9a84e425cd7e531e408fa3234fa5e1050eef1905b4ac35a61", + "PriceRouter": "0x677bd52be1ce091b133c5bd637cec20df7f1390adfa64de98791e2928d5d3c1", + "SimpleDecoderAndSanitizer": "0x5e9c41a7fd764459fc91f3712a7bb3e31a02ed97cf4954b6dd84feb5034e6bc" + }, + "periphery": { + "vesuSingleton": "0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160", + "avnuRouter": "0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f", + "pragma": "0x02a85bd616f912537c50a49a4076db02c00b29b2cdc8a197ce92ed1837fa875b" + } + } +} \ No newline at end of file diff --git a/scripts/configs/utils.ts b/scripts/configs/utils.ts new file mode 100644 index 00000000..3f34b10b --- /dev/null +++ b/scripts/configs/utils.ts @@ -0,0 +1,48 @@ +import fs from "fs"; +import path from "path"; + +const CONFIG_FILE = path.join(__dirname, "config.json"); + +export interface NetworkConfig { + [key: string]: any; +} + +export interface Config { + [networkName: string]: NetworkConfig; +} + +export function readConfigs(): Config { + try { + if (!fs.existsSync(CONFIG_FILE)) { + const defaultConfig: Config = { + sepolia: { + hash: {}, + periphery: {} + }, + mainnet: { + hash: {}, + periphery: {} + } + }; + writeConfigs(defaultConfig); + return defaultConfig; + } + + const configData = fs.readFileSync(CONFIG_FILE, "utf8"); + return JSON.parse(configData) as Config; + } catch (error) { + console.error("Error reading config file:", error); + throw new Error("Failed to read configuration"); + } +} + +export function writeConfigs(config: Config): void { + try { + const configData = JSON.stringify(config, null, 2); + fs.writeFileSync(CONFIG_FILE, configData, "utf8"); + console.log("Configuration saved successfully"); + } catch (error) { + console.error("Error writing config file:", error); + throw new Error("Failed to write configuration"); + } +} \ No newline at end of file diff --git a/scripts/constants.ts b/scripts/constants.ts new file mode 100644 index 00000000..e69de29b diff --git a/scripts/declareContract.ts b/scripts/declareContract.ts new file mode 100644 index 00000000..af36d14f --- /dev/null +++ b/scripts/declareContract.ts @@ -0,0 +1,100 @@ +import { Account, json, RpcProvider, hash } from "starknet"; +import fs from "fs"; +import dotenv from "dotenv"; +import { readConfigs, writeConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +export async function declareContract( + envNetwork: string, + packageName: string, + name: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const compiledContract = await json.parse( + fs + .readFileSync(`../target/dev/${packageName}_${name}.contract_class.json`) + .toString("ascii") + ); + const compiledSierraCasm = await json.parse( + fs + .readFileSync( + `../target/dev/${packageName}_${name}.compiled_contract_class.json` + ) + .toString("ascii") + ); + + try { + const declareResponse = await owner.declare({ + contract: compiledContract, + casm: compiledSierraCasm, + }); + + let classHash = declareResponse.class_hash; + console.log( + `Class Hash ${name}: ${classHash} deployed for network: ${envNetwork}` + ); + if (!networkConfig.hash) { + networkConfig.hash = {}; + } + networkConfig.hash[name] = classHash; + config[envNetwork] = networkConfig; + writeConfigs(config); + } catch (error) { + console.error(error); + } +} + +async function main() { + if (!process.argv[2] || !process.argv[3]) { + throw new Error("Missing --contract "); + } + + let envNetwork = await getNetworkEnv(provider); + switch (process.argv[3]) { + case "Vault": + await declareContract(envNetwork, "vault", "Vault"); + break; + case "VaultAllocator": + await declareContract(envNetwork, "vault_allocator", "VaultAllocator"); + break; + case "RedeemRequest": + await declareContract(envNetwork, "vault", "RedeemRequest"); + break; + case "AvnuMiddleware": + await declareContract(envNetwork, "vault_allocator", "AvnuMiddleware"); + break; + case "Manager": + await declareContract(envNetwork, "vault_allocator", "Manager"); + break; + case "PriceRouter": + await declareContract(envNetwork, "vault_allocator", "PriceRouter"); + break; + case "SimpleDecoderAndSanitizer": + await declareContract( + envNetwork, + "vault_allocator", + "SimpleDecoderAndSanitizer" + ); + break; + default: + throw new Error("Error: Unknown contract"); + } +} + +main(); diff --git a/scripts/deployContract.ts b/scripts/deployContract.ts new file mode 100644 index 00000000..36814fda --- /dev/null +++ b/scripts/deployContract.ts @@ -0,0 +1,237 @@ +import { + Account, + RpcProvider, + CallData, + CairoUint256, +} from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; +import { saveContractDeployment } from "./utils/deployment"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +export async function deployContract( + envNetwork: string, + contractName: string, + constructorParams?: any[] +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.[contractName]; + if (!classHash) { + throw new Error( + `${contractName} class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + try { + const constructorCalldata = constructorParams + ? CallData.compile(constructorParams) + : []; + + console.log(`Deploying ${contractName} with constructor params:`); + if (constructorParams) { + constructorParams.forEach((param, index) => { + console.log(` Param ${index}: ${param}`); + }); + } else { + console.log(` No constructor parameters`); + } + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`${contractName} deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + saveContractDeployment( + envNetwork, + contractName, + deployResponse.contract_address, + deployResponse.transaction_hash + ); + + return deployResponse.contract_address; + } catch (error) { + console.error(`Error deploying ${contractName}:`, error); + throw error; + } +} + +export async function deploySimpleDecoderAndSanitizer(envNetwork: string) { + return await deployContract(envNetwork, "SimpleDecoderAndSanitizer", []); +} + +export async function deployPriceRouter( + envNetwork: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const pragmaAddress = networkConfig.periphery?.pragma; + if (!pragmaAddress) { + throw new Error( + `pragma address not found for network: ${envNetwork}. Please add it to the config.` + ); + } + + return await deployContract(envNetwork, "PriceRouter", [ + owner.address, + pragmaAddress, + ]); +} + +export async function deployAvnuMiddleware( + envNetwork: string, + slippage_tolerance_bps: number +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const avnuRouter = networkConfig.periphery?.avnuRouter; + if (!avnuRouter) { + throw new Error( + `avnuRouter address not found for network: ${envNetwork}. Please add it to the config.` + ); + } + + const priceRouter = networkConfig.pricerouter; + if (!priceRouter) { + throw new Error( + `priceRouter address not found for network: ${envNetwork}. Please add it to the config.` + ); + } + + const slippage_tolerance_bps_uint256 = new CairoUint256( + slippage_tolerance_bps + ); + + return await deployContract(envNetwork, "AvnuMiddleware", [ + owner.address, + avnuRouter, + priceRouter, + slippage_tolerance_bps_uint256, + ]); +} + +function validateSlippageTolerancePercentage(slippage: string): number { + const num = parseFloat(slippage); + if (isNaN(num) || num < 0 || num > 100) { + throw new Error( + `Invalid slippage tolerance: ${slippage}%. Must be between 0 and 100%` + ); + } + return Math.floor(num * 100); +} + +function parseArguments(contractName: string, args: string[]) { + switch (contractName) { + case "SimpleDecoderAndSanitizer": + return {}; + + case "PriceRouter": + return {}; + + case "AvnuMiddleware": + if (args.length < 1) { + throw new Error( + "AvnuMiddleware requires: " + ); + } + return { + slippageToleranceBps: validateSlippageTolerancePercentage(args[0]), + }; + + default: + throw new Error(`Unknown contract: ${contractName}`); + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + if (!process.argv[2] || !process.argv[3]) { + console.log( + "Usage: npm run deploy:contract -- --contract [args...]" + ); + console.log("\nAvailable contracts:"); + console.log(" - SimpleDecoderAndSanitizer"); + console.log(" Usage: --contract SimpleDecoderAndSanitizer"); + console.log(" - PriceRouter"); + console.log(" Usage: --contract PriceRouter"); + console.log( + " - AvnuMiddleware " + ); + console.log( + " Usage: --contract AvnuMiddleware " + ); + console.log( + " Note: slippage_tolerance_percentage should be between 0-100% (e.g., 2.5 for 2.5%)" + ); + return; + } + + const contractName = process.argv[3]; + const contractArgs = process.argv.slice(4); + + console.log( + `\n🚀 Starting ${contractName} deployment on ${envNetwork}...\n` + ); + + const parsedArgs = parseArguments(contractName, contractArgs); + let deployedAddress: string; + + switch (contractName) { + case "SimpleDecoderAndSanitizer": + deployedAddress = await deploySimpleDecoderAndSanitizer(envNetwork); + break; + + case "PriceRouter": + deployedAddress = await deployPriceRouter(envNetwork); + break; + + case "AvnuMiddleware": + deployedAddress = await deployAvnuMiddleware( + envNetwork, + parsedArgs.slippageToleranceBps as number + ); + break; + + default: + throw new Error(`Unknown contract: ${contractName}`); + } + + console.log(`\n✅ ${contractName} deployment completed successfully!`); + console.log(`📍 Contract Address: ${deployedAddress}`); + console.log(`🌐 Network: ${envNetwork}`); + } catch (error) { + console.error(`\n❌ Deployment failed:`, error); + process.exit(1); + } +} + +main().catch(console.error); diff --git a/scripts/deployVault.ts b/scripts/deployVault.ts new file mode 100644 index 00000000..e93318b3 --- /dev/null +++ b/scripts/deployVault.ts @@ -0,0 +1,648 @@ +import { + Account, + byteArray, + CairoUint256, + CallData, + RpcProvider, + validateAndParseAddress, +} from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv, WAD } from "./utils"; +import { saveVaultDeployment } from "./utils/deployment"; +import { Decimal } from "decimal.js"; +import readline from "readline"; + +interface VaultDeploymentConfig { + name: string; + symbol: string; + underlyingAsset: string; + ownerAddress: string; + feesRecipient: string; + redeemFeesPercentage: string; + performanceFeePercentage: string; + managementFeePercentage: string; + reportDelay: string; + maxDeltaPercentage: string; +} + +interface VaultAllocatorDeploymentConfig { + vault: string; + manager: string; + paymentToken: string; +} + +interface DeploymentConfig { + vault?: VaultDeploymentConfig; + vaultAllocator?: VaultAllocatorDeploymentConfig; +} + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function askQuestion(question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()); + }); + }); +} + +const MAX_REDEEM_FEE_PERCENT = 0.1; +const MAX_MANAGEMENT_FEE_PERCENT = 2.0; +const MAX_PERFORMANCE_FEE_PERCENT = 20.0; +const MIN_REPORT_DELAY_SECONDS = 3600; + +function validatePercentage(value: string, fieldName: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0) { + throw new Error(`${fieldName} must be a valid non-negative percentage`); + } +} + +function validateRedeemFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Redeem fees percentage"); + if (num > MAX_REDEEM_FEE_PERCENT) { + throw new Error( + `Redeem fees percentage must not exceed ${MAX_REDEEM_FEE_PERCENT}% (vault contract limit)` + ); + } +} + +function validateManagementFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Management fees percentage"); + if (num > MAX_MANAGEMENT_FEE_PERCENT) { + throw new Error( + `Management fees percentage must not exceed ${MAX_MANAGEMENT_FEE_PERCENT}% (vault contract limit)` + ); + } +} + +function validatePerformanceFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Performance fees percentage"); + if (num > MAX_PERFORMANCE_FEE_PERCENT) { + throw new Error( + `Performance fees percentage must not exceed ${MAX_PERFORMANCE_FEE_PERCENT}% (vault contract limit)` + ); + } +} + +function validateReportDelay(value: string): void { + const num = parseInt(value); + if (isNaN(num) || num < MIN_REPORT_DELAY_SECONDS) { + throw new Error( + `Report delay must be at least ${MIN_REPORT_DELAY_SECONDS} seconds (1 hour minimum from vault contract)` + ); + } +} + +function validateMaxDeltaPercentage(value: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0 || num > 100) { + throw new Error("Max delta percentage must be between 0 and 100"); + } +} + +async function collectVaultParameters(): Promise { + console.log("🚀 Initializing vault deployment process...\n"); + console.log("Please provide the following parameters:\n"); + + const name = await askQuestion("Vault name: "); + const symbol = await askQuestion("Vault symbol: "); + + const underlyingAsset = await askQuestion("Underlying asset address: "); + try { + validateAndParseAddress(underlyingAsset); + } catch (error) { + throw new Error(`Invalid underlying asset address: ${underlyingAsset}`); + } + + const ownerAddress = owner.address; + + const feesRecipient = await askQuestion("Fees recipient address: "); + try { + validateAndParseAddress(feesRecipient); + } catch (error) { + throw new Error(`Invalid fees recipient address: ${feesRecipient}`); + } + + const redeemFeesPercentage = await askQuestion("Redeem fees percentage: "); + validateRedeemFeePercentage(redeemFeesPercentage); + + const performanceFeePercentage = await askQuestion( + "Performance fee percentage: " + ); + validatePerformanceFeePercentage(performanceFeePercentage); + + const managementFeePercentage = await askQuestion( + "Management fee percentage: " + ); + validateManagementFeePercentage(managementFeePercentage); + + const reportDelay = await askQuestion("Report delay (seconds): "); + validateReportDelay(reportDelay); + + const maxDeltaPercentage = await askQuestion("Max delta percentage: "); + validateMaxDeltaPercentage(maxDeltaPercentage); + + return { + name, + symbol, + underlyingAsset, + ownerAddress, + feesRecipient, + redeemFeesPercentage, + performanceFeePercentage, + managementFeePercentage, + reportDelay, + maxDeltaPercentage, + }; +} + +async function askCustodialType(): Promise<{ + isCustodial: boolean; + vaultAllocatorAddress?: string; +}> { + const custodialAnswer = await askQuestion( + "\nIs this vault custodial? (y/n): " + ); + + const normalizedAnswer = custodialAnswer.toLowerCase().trim(); + if (!["y", "yes", "n", "no"].includes(normalizedAnswer)) { + throw new Error( + `Invalid custodial type answer: '${custodialAnswer}'. Please enter 'y', 'yes', 'n', or 'no'.` + ); + } + + const isCustodial = normalizedAnswer === "y" || normalizedAnswer === "yes"; + + if (isCustodial) { + const vaultAllocatorAddress = await askQuestion( + "Vault allocator address: " + ); + + try { + validateAndParseAddress(vaultAllocatorAddress); + } catch (error) { + throw new Error( + `Invalid vault allocator address: ${vaultAllocatorAddress}` + ); + } + + return { isCustodial, vaultAllocatorAddress }; + } + + return { isCustodial }; +} + +export async function deployVault( + envNetwork: string, + nameString: string, + symbolString: string, + underlyingAsset: string, + ownerAddress: string, + feesRecipient: string, + redeemFeesPercentage: string, + performanceFeePercentage: string, + managementFeePercentage: string, + reportDelay: string, + maxDeltaPercentage: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.Vault; + if (!classHash) { + throw new Error( + `Vault class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + const redeemFees = new CairoUint256( + new Decimal(redeemFeesPercentage).mul(WAD).div(100).toString() + ); + + const managementFees = new CairoUint256( + new Decimal(managementFeePercentage).mul(WAD).div(100).toString() + ); + + const performanceFees = new CairoUint256( + new Decimal(performanceFeePercentage).mul(WAD).div(100).toString() + ); + + const maxDelta = new CairoUint256( + new Decimal(maxDeltaPercentage).mul(WAD).div(100).toString() + ); + + const nameBytes = byteArray.byteArrayFromString(nameString); + const symbolBytes = byteArray.byteArrayFromString(symbolString); + + try { + let constructorCalldata = { + name: nameBytes, + symbol: symbolBytes, + underlying_asset: underlyingAsset, + owner: ownerAddress, + fees_recipient: feesRecipient, + redeem_fees: redeemFees, + management_fees: managementFees, + performance_fees: performanceFees, + report_delay: reportDelay, + max_delta: maxDelta, + }; + + console.log(constructorCalldata); + + console.log(`Deploying Vault with constructor params:`); + console.log(` Name: ${nameString}`); + console.log(` Symbol: ${symbolString}`); + console.log(` Asset: ${underlyingAsset}`); + console.log(` Owner: ${ownerAddress}`); + console.log(` Fees Recipient: ${feesRecipient}`); + console.log(` Redeem Fees: ${redeemFeesPercentage}`); + console.log(` Performance Fee: ${performanceFeePercentage}`); + console.log(` Management Fee: ${managementFeePercentage}`); + console.log(` Report Delay: ${reportDelay}`); + console.log(` Max Delta: ${maxDeltaPercentage}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`Vault deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + saveVaultDeployment( + envNetwork, + symbolString, + "vault", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying Vault:", error); + throw error; + } +} + +export async function deployRedeemRequest( + envNetwork: string, + ownerAddress: string, + vaultAddress: string, + vaultSymbol?: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.RedeemRequest; + if (!classHash) { + throw new Error( + `RedeemRequest class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + try { + console.log(`Deploying RedeemRequest with constructor params:`); + console.log(` Owner: ${owner.address}`); + console.log(` Vault: ${vaultAddress}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: [owner.address, vaultAddress], + }); + + console.log(`RedeemRequest deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + if (vaultSymbol) { + saveVaultDeployment( + envNetwork, + vaultSymbol, + "redeemRequest", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + } + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying RedeemRequest:", error); + throw error; + } +} + +export async function linkRedeemRequestToVault( + vaultAddress: string, + redeemRequestAddress: string +) { + try { + console.log(`Linking RedeemRequest to Vault...`); + console.log(` Vault: ${vaultAddress}`); + console.log(` RedeemRequest: ${redeemRequestAddress}`); + + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "register_redeem_request", + calldata: [redeemRequestAddress], + }); + + console.log(`RedeemRequest linked to Vault successfully!`); + console.log(`Transaction Hash: ${response.transaction_hash}`); + + return response.transaction_hash; + } catch (error) { + console.error("Error linking RedeemRequest to Vault:", error); + throw error; + } +} + +export async function deployVaultAllocator( + envNetwork: string, + vaultSymbol?: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.VaultAllocator; + if (!classHash) { + throw new Error( + `VaultAllocator class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + try { + console.log(`Deploying VaultAllocator with constructor params:`); + console.log(` Owner: ${owner.address}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: [owner.address], + }); + + console.log(`VaultAllocator deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + if (vaultSymbol) { + saveVaultDeployment( + envNetwork, + vaultSymbol, + "vaultAllocator", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + } + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying VaultAllocator:", error); + throw error; + } +} + +export async function deployManager( + envNetwork: string, + vaultAllocatorAddress: string, + vaultSymbol?: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.Manager; + if (!classHash) { + throw new Error( + `Manager class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + const vesuSingleton = networkConfig.periphery?.vesuSingleton; + if (!vesuSingleton) { + throw new Error( + `Vesu Singleton address not found for network: ${envNetwork}.` + ); + } + + try { + const constructorCalldata = CallData.compile([ + owner.address, + vaultAllocatorAddress, + vesuSingleton, + ]); + + console.log(`Deploying Manager with constructor params:`); + console.log(` Owner: ${owner.address}`); + console.log(` Vault Allocator: ${vaultAllocatorAddress}`); + console.log(` Vesu Singleton: ${vesuSingleton}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`Manager deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + if (vaultSymbol) { + saveVaultDeployment( + envNetwork, + vaultSymbol, + "manager", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + } + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying Manager:", error); + throw error; + } +} + +export async function attachVaultAllocatorToVault( + vaultAddress: string, + vaultAllocatorAddress: string +) { + try { + console.log(`Attaching VaultAllocator to Vault...`); + console.log(` Vault: ${vaultAddress}`); + console.log(` VaultAllocator: ${vaultAllocatorAddress}`); + + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "register_vault_allocator", + calldata: [vaultAllocatorAddress], + }); + + console.log(`VaultAllocator attached to Vault successfully!`); + console.log(`Transaction Hash: ${response.transaction_hash}`); + + return response.transaction_hash; + } catch (error) { + console.error("Error attaching VaultAllocator to Vault:", error); + throw error; + } +} + +export async function setManagerInVaultAllocator( + vaultAllocatorAddress: string, + managerAddress: string +) { + try { + console.log(`Setting Manager in VaultAllocator...`); + console.log(` VaultAllocator: ${vaultAllocatorAddress}`); + console.log(` Manager: ${managerAddress}`); + + const response = await owner.execute({ + contractAddress: vaultAllocatorAddress, + entrypoint: "set_manager", + calldata: [managerAddress], + }); + + console.log(`Manager set in VaultAllocator successfully!`); + console.log(`Transaction Hash: ${response.transaction_hash}`); + + return response.transaction_hash; + } catch (error) { + console.error("Error setting Manager in VaultAllocator:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + const vaultConfig = await collectVaultParameters(); + + const { isCustodial, vaultAllocatorAddress } = await askCustodialType(); + + console.log("\n📋 Deployment Summary:"); + console.log(`Network: ${envNetwork}`); + console.log(`Vault Type: ${isCustodial ? "Custodial" : "Non-custodial"}`); + if (isCustodial) { + console.log(`Vault Allocator: ${vaultAllocatorAddress}`); + } + console.log("\n🚀 Starting deployment process...\n"); + + console.log("📦 Deploying Vault..."); + const vaultAddress = await deployVault( + envNetwork, + vaultConfig.name, + vaultConfig.symbol, + vaultConfig.underlyingAsset, + vaultConfig.ownerAddress, + vaultConfig.feesRecipient, + vaultConfig.redeemFeesPercentage, + vaultConfig.performanceFeePercentage, + vaultConfig.managementFeePercentage, + vaultConfig.reportDelay, + vaultConfig.maxDeltaPercentage + ); + console.log("\n⏳ Waiting 3 seconds before deploying RedeemRequest..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n📦 Deploying RedeemRequest..."); + const redeemRequestAddress = await deployRedeemRequest( + envNetwork, + vaultConfig.ownerAddress, + vaultAddress, + vaultConfig.symbol + ); + + console.log("\n⏳ Waiting 3 seconds before linking RedeemRequest to Vault..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n🔗 Linking RedeemRequest to Vault..."); + await linkRedeemRequestToVault(vaultAddress, redeemRequestAddress); + + console.log("\n⏳ Waiting 3 seconds before vault allocator operations..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + if (isCustodial) { + console.log("\n🔗 Attaching existing VaultAllocator to Vault..."); + await attachVaultAllocatorToVault(vaultAddress, vaultAllocatorAddress!); + } else { + console.log("\n📦 Deploying new VaultAllocator..."); + + const newVaultAllocatorAddress = await deployVaultAllocator( + envNetwork, + vaultConfig.symbol + ); + + console.log("\n⏳ Waiting 3 seconds before attaching VaultAllocator to Vault..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n🔗 Attaching new VaultAllocator to Vault..."); + await attachVaultAllocatorToVault(vaultAddress, newVaultAllocatorAddress); + + console.log("\n⏳ Waiting 3 seconds before deploying Manager..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n📦 Deploying Manager..."); + const managerAddress = await deployManager( + envNetwork, + newVaultAllocatorAddress, + vaultConfig.symbol + ); + + console.log("\n⏳ Waiting 3 seconds before setting Manager in VaultAllocator..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n🔗 Setting Manager in VaultAllocator..."); + await setManagerInVaultAllocator( + newVaultAllocatorAddress, + managerAddress + ); + } + + console.log("\n✅ Deployment completed successfully!"); + console.log(`📍 Vault Address: ${vaultAddress}`); + console.log(`📍 RedeemRequest Address: ${redeemRequestAddress}`); + } catch (error) { + console.error("\n❌ Deployment failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); diff --git a/scripts/managerConfig.ts b/scripts/managerConfig.ts new file mode 100644 index 00000000..5b8a5b65 --- /dev/null +++ b/scripts/managerConfig.ts @@ -0,0 +1,144 @@ +import { + Account, + RpcProvider, + validateAndParseAddress, +} from "starknet"; +import dotenv from "dotenv"; +import { getNetworkEnv } from "./utils"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function askQuestion(question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()); + }); + }); +} + +function validateMerkleRoot(value: string): void { + if (!value || value.length === 0) { + throw new Error("Merkle root cannot be empty"); + } + + if (!value.startsWith("0x")) { + throw new Error("Merkle root must start with '0x'"); + } + + const hexValue = value.slice(2); + if (!/^[0-9a-fA-F]+$/.test(hexValue)) { + throw new Error("Merkle root must be a valid hexadecimal value"); + } +} + +async function getManagerAddress(): Promise { + const managerAddress = await askQuestion("Enter manager contract address: "); + try { + validateAndParseAddress(managerAddress); + return managerAddress; + } catch (error) { + throw new Error(`Invalid manager address: ${managerAddress}`); + } +} + +async function showConfigMenu(): Promise { + console.log("\n📋 Manager Configuration Options:"); + console.log("1. Set Manage Root"); + console.log("2. Exit"); + + const choice = await askQuestion("\nSelect an option (1-2): "); + return choice.trim(); +} + +async function setManageRoot(managerAddress: string): Promise { + console.log("\n🌳 Set Manage Root"); + console.log("Configure merkle tree root for a strategist target"); + console.log(); + + const strategistAddress = await askQuestion("Strategist address (target): "); + try { + validateAndParseAddress(strategistAddress); + } catch (error) { + throw new Error(`Invalid strategist address: ${strategistAddress}`); + } + + const merkleRoot = await askQuestion("Merkle tree root (felt252): "); + validateMerkleRoot(merkleRoot); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Manager: ${managerAddress}`); + console.log(` Strategist: ${strategistAddress}`); + console.log(` Merkle Root: ${merkleRoot}`); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: managerAddress, + entrypoint: "set_manage_root", + calldata: [strategistAddress, merkleRoot], + }); + + console.log("✅ Manage root updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting manage root:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + console.log(`🌐 Connected to network: ${envNetwork}`); + console.log(`👤 Using account: ${owner.address}`); + + const managerAddress = await getManagerAddress(); + + while (true) { + const choice = await showConfigMenu(); + + switch (choice) { + case "1": + await setManageRoot(managerAddress); + break; + + case "2": + console.log("👋 Goodbye!"); + return; + + default: + console.log("❌ Invalid choice. Please select 1-2."); + break; + } + + console.log("\n" + "=".repeat(50)); + } + } catch (error) { + console.error("\n❌ Configuration failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 00000000..18a11992 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,38 @@ +{ + "name": "starknet-vault-kit-scripts", + "version": "1.0.0", + "description": "StarkNet Vault Kit deployment and management scripts", + "main": "index.js", + "packageManager": "pnpm@9.0.0", + "scripts": { + "declare": "tsx declareContract.ts --contract", + "declare:vault": "tsx declareContract.ts --contract Vault", + "declare:vault-allocator": "tsx declareContract.ts --contract VaultAllocator", + "declare:redeem-request": "tsx declareContract.ts --contract RedeemRequest", + "declare:avnu-middleware": "tsx declareContract.ts --contract AvnuMiddleware", + "declare:manager": "tsx declareContract.ts --contract Manager", + "declare:price-router": "tsx declareContract.ts --contract PriceRouter", + "declare:decoder-sanitizer": "tsx declareContract.ts --contract SimpleDecoderAndSanitizer", + "deploy": "tsx deployContract.ts --contract", + "deploy:vault": "tsx deployVault.ts", + "vault:config": "tsx vaultConfig.ts", + "manager:config": "tsx managerConfig.ts" + }, + "dependencies": { + "starknet": "^6.11.0", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "@types/node": "^22.5.4", + "tsx": "^4.19.1", + "typescript": "^5.5.4" + }, + "keywords": [ + "starknet", + "cairo", + "vault", + "defi" + ], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/scripts/pnpm-lock.yaml b/scripts/pnpm-lock.yaml new file mode 100644 index 00000000..4d0f7eff --- /dev/null +++ b/scripts/pnpm-lock.yaml @@ -0,0 +1,717 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + dotenv: + specifier: ^16.4.5 + version: 16.6.1 + starknet: + specifier: ^6.11.0 + version: 6.24.1 + devDependencies: + '@types/node': + specifier: ^22.5.4 + version: 22.18.0 + tsx: + specifier: ^4.19.1 + version: 4.20.5 + typescript: + specifier: ^5.5.4 + version: 5.9.2 + +packages: + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@noble/curves@1.7.0': + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.6.0': + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + + '@scure/base@1.2.1': + resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} + + '@scure/starknet@1.1.0': + resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} + + '@starknet-io/types-js@0.7.10': + resolution: {integrity: sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==} + + '@types/node@22.18.0': + resolution: {integrity: sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==} + + abi-wan-kanabi@2.2.4: + resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} + hasBin: true + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + + cardinal@2.1.1: + resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} + hasBin: true + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + fetch-cookie@3.0.1: + resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + lossless-json@4.1.1: + resolution: {integrity: sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==} + + 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 + + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + redeyed@2.1.1: + resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + starknet@6.24.1: + resolution: {integrity: sha512-g7tiCt73berhcNi41otlN3T3kxZnIvZhMi8WdC21Y6GC6zoQgbI2z1t7JAZF9c4xZiomlanwVnurcpyfEdyMpg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + + tsx@4.20.5: + resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + +snapshots: + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + + '@noble/hashes@1.6.0': {} + + '@scure/base@1.2.1': {} + + '@scure/starknet@1.1.0': + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + + '@starknet-io/types-js@0.7.10': {} + + '@types/node@22.18.0': + dependencies: + undici-types: 6.21.0 + + abi-wan-kanabi@2.2.4: + dependencies: + ansicolors: 0.3.2 + cardinal: 2.1.1 + fs-extra: 10.1.0 + yargs: 17.7.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansicolors@0.3.2: {} + + cardinal@2.1.1: + dependencies: + ansicolors: 0.3.2 + redeyed: 2.1.1 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + dotenv@16.6.1: {} + + emoji-regex@8.0.0: {} + + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + escalade@3.2.0: {} + + esprima@4.0.1: {} + + fetch-cookie@3.0.1: + dependencies: + set-cookie-parser: 2.7.1 + tough-cookie: 4.1.4 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + graceful-fs@4.2.11: {} + + is-fullwidth-code-point@3.0.0: {} + + isomorphic-fetch@3.0.0: + dependencies: + node-fetch: 2.7.0 + whatwg-fetch: 3.6.20 + transitivePeerDependencies: + - encoding + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + lossless-json@4.1.1: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + pako@2.1.0: {} + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + redeyed@2.1.1: + dependencies: + esprima: 4.0.1 + + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + set-cookie-parser@2.7.1: {} + + starknet@6.24.1: + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + '@scure/base': 1.2.1 + '@scure/starknet': 1.1.0 + abi-wan-kanabi: 2.2.4 + fetch-cookie: 3.0.1 + isomorphic-fetch: 3.0.0 + lossless-json: 4.1.1 + pako: 2.1.0 + starknet-types-07: '@starknet-io/types-js@0.7.10' + ts-mixer: 6.0.4 + transitivePeerDependencies: + - encoding + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@0.0.3: {} + + ts-mixer@6.0.4: {} + + tsx@4.20.5: + dependencies: + esbuild: 0.25.9 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.2: {} + + undici-types@6.21.0: {} + + universalify@0.2.0: {} + + universalify@2.0.1: {} + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + webidl-conversions@3.0.1: {} + + whatwg-fetch@3.6.20: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 diff --git a/scripts/utils.ts b/scripts/utils.ts new file mode 100644 index 00000000..d20645f8 --- /dev/null +++ b/scripts/utils.ts @@ -0,0 +1,28 @@ +import fs from "fs"; +import dotenv from "dotenv"; +import { RpcProvider, constants } from "starknet"; + +dotenv.config({ path: __dirname + "/../.env" }); + +export async function appendToEnv(name: string, address: string) { + fs.appendFile( + `${__dirname}/../.env`, + `\n${name}_ADDRESS=${address}`, + function (err) { + if (err) throw err; + } + ); +} + +export async function getNetworkEnv(provider: RpcProvider): Promise { + const chainIdFromRpc = await provider.getChainId(); + if (chainIdFromRpc == constants.StarknetChainId.SN_SEPOLIA) { + return "sepolia"; + } + if (chainIdFromRpc == constants.StarknetChainId.SN_MAIN) { + return "mainnet"; + } + throw new Error(`Unsupported network: ${chainIdFromRpc}`); +} + +export const WAD = "1000000000000000000"; diff --git a/scripts/utils/deployment.ts b/scripts/utils/deployment.ts new file mode 100644 index 00000000..d9caacb5 --- /dev/null +++ b/scripts/utils/deployment.ts @@ -0,0 +1,96 @@ +import fs from "fs"; +import path from "path"; + +interface DeploymentInfo { + address: string; + transactionHash: string; + timestamp: number; +} + +interface NetworkDeployments { + [contractName: string]: DeploymentInfo; + [symbol: string]: { + vault?: DeploymentInfo; + redeemRequest?: DeploymentInfo; + vaultAllocator?: DeploymentInfo; + manager?: DeploymentInfo; + }; +} + +interface DeploymentsJson { + [network: string]: NetworkDeployments; +} + +const DEPLOYMENTS_FILE = path.join(__dirname, "../deployments.json"); + +export function readDeployments(): DeploymentsJson { + try { + if (!fs.existsSync(DEPLOYMENTS_FILE)) { + return { sepolia: {}, mainnet: {} }; + } + const data = fs.readFileSync(DEPLOYMENTS_FILE, "utf8"); + return JSON.parse(data); + } catch (error) { + console.warn("Warning: Could not read deployments.json, creating new one"); + return { sepolia: {}, mainnet: {} }; + } +} + +export function writeDeployments(deployments: DeploymentsJson): void { + try { + fs.writeFileSync(DEPLOYMENTS_FILE, JSON.stringify(deployments, null, 2)); + } catch (error) { + console.error("Error writing deployments.json:", error); + throw error; + } +} + +export function saveContractDeployment( + network: string, + contractName: string, + address: string, + transactionHash: string +): void { + const deployments = readDeployments(); + + if (!deployments[network]) { + deployments[network] = {}; + } + + deployments[network][contractName] = { + address, + transactionHash, + timestamp: Date.now(), + }; + + writeDeployments(deployments); +} + +export function saveVaultDeployment( + network: string, + symbol: string, + contractType: "vault" | "redeemRequest" | "vaultAllocator" | "manager", + address: string, + transactionHash: string +): void { + const deployments = readDeployments(); + + if (!deployments[network]) { + deployments[network] = {}; + } + + if ( + !deployments[network][symbol] || + typeof deployments[network][symbol] !== "object" + ) { + deployments[network][symbol] = {} as any; + } + + (deployments[network][symbol] as any)[contractType] = { + address, + transactionHash, + timestamp: Date.now(), + }; + + writeDeployments(deployments); +} diff --git a/scripts/vaultConfig.ts b/scripts/vaultConfig.ts new file mode 100644 index 00000000..05289dce --- /dev/null +++ b/scripts/vaultConfig.ts @@ -0,0 +1,297 @@ +import { + Account, + CairoUint256, + RpcProvider, + validateAndParseAddress, +} from "starknet"; +import dotenv from "dotenv"; +import { getNetworkEnv, WAD } from "./utils"; +import { Decimal } from "decimal.js"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function askQuestion(question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()); + }); + }); +} + +const MAX_REDEEM_FEE_PERCENT = 0.1; +const MAX_MANAGEMENT_FEE_PERCENT = 2.0; +const MAX_PERFORMANCE_FEE_PERCENT = 20.0; +const MIN_REPORT_DELAY_SECONDS = 3600; + +function validatePercentage(value: string, fieldName: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0) { + throw new Error(`${fieldName} must be a valid non-negative percentage`); + } +} + +function validateRedeemFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Redeem fees percentage"); + if (num > MAX_REDEEM_FEE_PERCENT) { + throw new Error( + `Redeem fees percentage must not exceed ${MAX_REDEEM_FEE_PERCENT}%` + ); + } +} + +function validateManagementFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Management fees percentage"); + if (num > MAX_MANAGEMENT_FEE_PERCENT) { + throw new Error( + `Management fees percentage must not exceed ${MAX_MANAGEMENT_FEE_PERCENT}%` + ); + } +} + +function validatePerformanceFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Performance fees percentage"); + if (num > MAX_PERFORMANCE_FEE_PERCENT) { + throw new Error( + `Performance fees percentage must not exceed ${MAX_PERFORMANCE_FEE_PERCENT}%` + ); + } +} + +function validateReportDelay(value: string): void { + const num = parseInt(value); + if (isNaN(num) || num < MIN_REPORT_DELAY_SECONDS) { + throw new Error( + `Report delay must be at least ${MIN_REPORT_DELAY_SECONDS} seconds (1 hour minimum)` + ); + } +} + +function validateMaxDeltaPercentage(value: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0 || num > 100) { + throw new Error("Max delta percentage must be between 0 and 100"); + } +} + +async function getVaultAddress(): Promise { + const vaultAddress = await askQuestion("Enter vault contract address: "); + try { + validateAndParseAddress(vaultAddress); + return vaultAddress; + } catch (error) { + throw new Error(`Invalid vault address: ${vaultAddress}`); + } +} + +async function showConfigMenu(): Promise { + console.log("\n📋 Vault Configuration Options:"); + console.log("1. Set Fees Configuration"); + console.log("2. Set Report Delay"); + console.log("3. Set Max Delta"); + console.log("4. Exit"); + + const choice = await askQuestion("\nSelect an option (1-4): "); + return choice.trim(); +} + +async function setFeesConfig(vaultAddress: string): Promise { + console.log("\n💰 Set Fees Configuration"); + console.log(` - Redeem fees: max ${MAX_REDEEM_FEE_PERCENT}%`); + console.log(` - Management fees: max ${MAX_MANAGEMENT_FEE_PERCENT}%`); + console.log(` - Performance fees: max ${MAX_PERFORMANCE_FEE_PERCENT}%`); + console.log(); + + const feesRecipient = await askQuestion("Fees recipient address: "); + try { + validateAndParseAddress(feesRecipient); + } catch (error) { + throw new Error(`Invalid fees recipient address: ${feesRecipient}`); + } + + const redeemFeesPercentage = await askQuestion("Redeem fees percentage: "); + validateRedeemFeePercentage(redeemFeesPercentage); + + const performanceFeePercentage = await askQuestion( + "Performance fee percentage: " + ); + validatePerformanceFeePercentage(performanceFeePercentage); + + const managementFeePercentage = await askQuestion( + "Management fee percentage: " + ); + validateManagementFeePercentage(managementFeePercentage); + + const redeemFees = new CairoUint256( + new Decimal(redeemFeesPercentage).mul(WAD).div(100).toString() + ); + + const managementFees = new CairoUint256( + new Decimal(managementFeePercentage).mul(WAD).div(100).toString() + ); + + const performanceFees = new CairoUint256( + new Decimal(performanceFeePercentage).mul(WAD).div(100).toString() + ); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log(` Fees Recipient: ${feesRecipient}`); + console.log(` Redeem Fees: ${redeemFeesPercentage}%`); + console.log(` Management Fees: ${managementFeePercentage}%`); + console.log(` Performance Fees: ${performanceFeePercentage}%`); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "set_fees_config", + calldata: [feesRecipient, redeemFees, managementFees, performanceFees], + }); + + console.log("✅ Fees configuration updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting fees configuration:", error); + throw error; + } +} + +async function setReportDelay(vaultAddress: string): Promise { + console.log("\n⏰ Set Report Delay"); + console.log(`Minimum delay: ${MIN_REPORT_DELAY_SECONDS} seconds (1 hour)`); + + const reportDelay = await askQuestion("Report delay (seconds): "); + validateReportDelay(reportDelay); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log( + ` Report Delay: ${reportDelay} seconds (${Math.floor( + parseInt(reportDelay) / 3600 + )} hours)` + ); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "set_report_delay", + calldata: [reportDelay], + }); + + console.log("✅ Report delay updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting report delay:", error); + throw error; + } +} + +async function setMaxDelta(vaultAddress: string): Promise { + console.log("\n📊 Set Max Delta"); + console.log("Max delta percentage range: 0-100%"); + + const maxDeltaPercentage = await askQuestion("Max delta percentage: "); + validateMaxDeltaPercentage(maxDeltaPercentage); + + const maxDelta = new CairoUint256( + new Decimal(maxDeltaPercentage).mul(WAD).div(100).toString() + ); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log(` Max Delta: ${maxDeltaPercentage}%`); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "set_max_delta", + calldata: [maxDelta], + }); + + console.log("✅ Max delta updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting max delta:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + console.log(`🌐 Connected to network: ${envNetwork}`); + console.log(`👤 Using account: ${owner.address}`); + + const vaultAddress = await getVaultAddress(); + + while (true) { + const choice = await showConfigMenu(); + + switch (choice) { + case "1": + await setFeesConfig(vaultAddress); + break; + + case "2": + await setReportDelay(vaultAddress); + break; + + case "3": + await setMaxDelta(vaultAddress); + break; + + case "4": + console.log("👋 Goodbye!"); + return; + + default: + console.log("❌ Invalid choice. Please select 1-4."); + break; + } + + console.log("\n" + "=".repeat(50)); + } + } catch (error) { + console.error("\n❌ Configuration failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); From 2c87b3907d67886015e9ffc6ca658603d29773e7 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:18:29 +0100 Subject: [PATCH 12/54] Add bring_liquidity functionality and vault allocator test utilities - Add bring_liquidity method to base decoder and sanitizer interface - Implement bring_liquidity in BaseDecoderAndSanitizerComponent - Add mock vault module and deploy utility function - Create vault bring liquidity integration test structure - Add vault allocator leaf generation utilities for approvals and bring liquidity operations - Update module exports to include new components --- .../base_decoder_and_sanitizer.cairo | 5 + .../decoders_and_sanitizers/interface.cairo | 1 + packages/vault_allocator/src/lib.cairo | 23 +-- .../vault_allocator/src/mocks/vault.cairo | 179 ++++++++++++++++++ .../integrations/vault_bring_liquidity.cairo | 137 ++++++++++++++ packages/vault_allocator/src/test/utils.cairo | 75 ++++++-- 6 files changed, 391 insertions(+), 29 deletions(-) create mode 100644 packages/vault_allocator/src/mocks/vault.cairo create mode 100644 packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo index 63304d20..020d5caa 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo @@ -42,5 +42,10 @@ pub mod BaseDecoderAndSanitizerComponent { is_legacy.serialize(ref serialized_struct); serialized_struct.span() } + + fn bring_liquidity(self: @ComponentState, amount: u256) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo index 7314c955..6f175354 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo @@ -15,4 +15,5 @@ pub trait IBaseDecoderAndSanitizer { is_legacy: bool, data: Span, ) -> Span; + fn bring_liquidity(self: @T, amount: u256) -> Span; } diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 00dfd9ae..c4c72327 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -70,6 +70,7 @@ pub mod mocks { pub mod erc20; pub mod erc4626; pub mod flashloan; + pub mod vault; } #[cfg(test)] @@ -77,18 +78,18 @@ pub mod test { // pub mod creator; pub mod register; pub mod utils; - pub mod units { - pub mod manager; - pub mod vault_allocator; - } + // pub mod units { + // pub mod manager; + // pub mod vault_allocator; + // } pub mod integrations { - pub mod avnu; - pub mod vesu_v1; - } - - pub mod scenarios { - pub mod leveraged_loop_staked_ether; - pub mod stable_carry_loop; + // pub mod avnu; + pub mod vault_bring_liquidity; + // pub mod vesu_v1; } + // pub mod scenarios { +// pub mod leveraged_loop_staked_ether; +// pub mod stable_carry_loop; +// } } diff --git a/packages/vault_allocator/src/mocks/vault.cairo b/packages/vault_allocator/src/mocks/vault.cairo new file mode 100644 index 00000000..05a5aeb8 --- /dev/null +++ b/packages/vault_allocator/src/mocks/vault.cairo @@ -0,0 +1,179 @@ +#[starknet::contract] +pub mod MockVault { + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::token::erc20::extensions::erc4626::{ + DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, ERC4626DefaultNoLimits, + }; + use openzeppelin::token::erc20::{ + DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, + }; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + + // --- OpenZeppelin Component Integrations --- + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: ERC4626Component, storage: erc4626, event: ERC4626Event); + + // --- ERC4626 Implementation --- + #[abi(embed_v0)] + impl ERC4626Impl = ERC4626Component::ERC4626Impl; + impl ERC4626InternalImpl = ERC4626Component::InternalImpl; + + // --- ERC20 Implementation --- + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // --- ERC20 Metadata Implementation --- + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + + #[storage] + pub struct Storage { + // --- Component Storage --- + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + erc4626: ERC4626Component::Storage, + // --- Vault State --- + buffer: u256, // Assets available in vault for instant redemption + aum: u256 // Assets under management (deployed to allocators) + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + ERC20Event: ERC20Component::Event, + ERC4626Event: ERC4626Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, underlying_asset: ContractAddress) { + self.erc20.initializer("MockVault", "MVAULT"); + self.erc4626.initializer(underlying_asset); + } + + + /// Implementation of asset management for ERC4626 compatibility + impl MockVaultAssetsManagementImpl of ERC4626Component::AssetsManagementTrait { + fn get_total_assets(self: @ERC4626Component::ComponentState) -> u256 { + let contract_state = self.get_contract(); + contract_state.buffer.read() + contract_state.aum.read() + } + + fn transfer_assets_in( + ref self: ERC4626Component::ComponentState, + from: ContractAddress, + assets: u256, + ) { + let this = starknet::get_contract_address(); + let asset_dispatcher = ERC20ABIDispatcher { + contract_address: self.ERC4626_asset.read(), + }; + assert( + asset_dispatcher.transfer_from(from, this, assets), + ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, + ); + } + + fn transfer_assets_out( + ref self: ERC4626Component::ComponentState, + to: ContractAddress, + assets: u256, + ) { + let asset_dispatcher = ERC20ABIDispatcher { + contract_address: self.ERC4626_asset.read(), + }; + assert( + asset_dispatcher.transfer(to, assets), + ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, + ); + } + } + + pub impl MockVaultHooksImpl of ERC4626Component::ERC4626HooksTrait { + fn before_withdraw( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) {} + + fn after_withdraw( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.buffer.write(contract_state.buffer.read() - assets); + } + + fn before_deposit( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) {} + + fn after_deposit( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.buffer.write(contract_state.buffer.read() + assets); + } + } + + #[abi(embed_v0)] + impl MockVaultImpl of MockVaultTrait { + /// Bring assets back from allocators to vault buffer + fn bring_liquidity(ref self: ContractState, amount: u256) { + ERC20ABIDispatcher { contract_address: self.erc4626.asset() } + .transfer_from(get_caller_address(), starknet::get_contract_address(), amount); + self.buffer.write(self.buffer.read() + amount); + self.aum.write(self.aum.read() - amount); + } + + /// Get current buffer amount + fn buffer(self: @ContractState) -> u256 { + self.buffer.read() + } + + /// Get current assets under management + fn aum(self: @ContractState) -> u256 { + self.aum.read() + } + + /// Set buffer for testing purposes + fn set_buffer(ref self: ContractState, amount: u256) { + self.buffer.write(amount); + } + + /// Set AUM for testing purposes + fn set_aum(ref self: ContractState, amount: u256) { + self.aum.write(amount); + } + } + + #[starknet::interface] + pub trait MockVaultTrait { + fn bring_liquidity(ref self: TContractState, amount: u256); + fn buffer(self: @TContractState) -> u256; + fn aum(self: @TContractState) -> u256; + fn set_buffer(ref self: TContractState, amount: u256); + fn set_aum(ref self: TContractState, amount: u256); + } +} diff --git a/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo new file mode 100644 index 00000000..2d652ffd --- /dev/null +++ b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; +use snforge_std::{map_entry_address, store}; +use vault_allocator::manager::interface::IManagerDispatcherTrait; +use vault_allocator::mocks::vault::MockVault::MockVaultTraitDispatcherTrait; +use vault_allocator::test::register::VESU_SINGLETON; +use vault_allocator::test::utils::{ + ManageLeaf, OWNER, STRATEGIST, WAD, _add_vault_allocator_leafs, _get_proofs_using_tree, + _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_erc20_mock, deploy_manager, + deploy_mock_vault, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, + generate_merkle_tree, +}; +use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; + +#[test] +fn test_manage_vault_with_merkle_verification_bring_liquidity() { + let vault_allocator = deploy_vault_allocator(); + let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); + + let underlying_token = deploy_erc20_mock(); + let mock_vault = deploy_mock_vault(underlying_token); + + let mock_vault_erc4626 = IERC4626Dispatcher { contract_address: mock_vault.contract_address }; + + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + _add_vault_allocator_leafs( + ref leafs, + ref leaf_index, + vault_allocator.contract_address, + simple_decoder_and_sanitizer, + mock_vault_erc4626, + ); + + _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); + + let tree = generate_merkle_tree(leafs.span()); + let root = *tree.at(tree.len() - 1).at(0); + + cheat_caller_address_once(vault_allocator.contract_address, OWNER()); + vault_allocator.set_manager(manager.contract_address); + + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(STRATEGIST(), root); + + // Set initial balance and liquidity for mock vault + let initial_liquidity: u256 = 10 * WAD; + let initial_buffer: u256 = 5 * WAD; + + // Add underlying token balance to vault allocator + let mut cheat_calldata = ArrayTrait::new(); + initial_liquidity.serialize(ref cheat_calldata); + store( + underlying_token, + map_entry_address( + selector!("ERC20_balances"), array![vault_allocator.contract_address.into()].span(), + ), + cheat_calldata.span(), + ); + + // Set initial buffer and liquidity in mock vault + cheat_caller_address_once(mock_vault.contract_address, OWNER()); + mock_vault.set_buffer(initial_buffer); + + cheat_caller_address_once(mock_vault.contract_address, OWNER()); + mock_vault.set_aum(initial_liquidity); + + let underlying_disp = ERC20ABIDispatcher { contract_address: underlying_token }; + assert( + underlying_disp.balance_of(vault_allocator.contract_address) == initial_liquidity, + 'underlying balance incorrect', + ); + + // Prepare to call bring_liquidity + let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); + array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); + + let mut array_of_targets = ArrayTrait::new(); + array_of_targets.append(underlying_token); + array_of_targets.append(mock_vault.contract_address); + + let mut array_of_selectors = ArrayTrait::new(); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("bring_liquidity")); + + let mut array_of_calldatas = ArrayTrait::new(); + + let bring_liquidity_amount: u256 = 2 * WAD; + + // Approval calldata + let mut array_of_calldata_approve: Array = ArrayTrait::new(); + mock_vault.contract_address.serialize(ref array_of_calldata_approve); + bring_liquidity_amount.serialize(ref array_of_calldata_approve); + array_of_calldatas.append(array_of_calldata_approve.span()); + + // Bring liquidity calldata (empty for this function) + let mut array_of_calldata_bring_liquidity: Array = ArrayTrait::new(); + bring_liquidity_amount.serialize(ref array_of_calldata_bring_liquidity); + array_of_calldatas.append(array_of_calldata_bring_liquidity.span()); + + let mut manage_leafs: Array = ArrayTrait::new(); + manage_leafs.append(leafs.at(0).clone()); + manage_leafs.append(leafs.at(1).clone()); + + let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); + + cheat_caller_address_once(manager.contract_address, STRATEGIST()); + manager + .manage_vault_with_merkle_verification( + manage_proofs.span(), + array_of_decoders_and_sanitizers.span(), + array_of_targets.span(), + array_of_selectors.span(), + array_of_calldatas.span(), + ); + + // Verify that bring_liquidity was called successfully + // The mock vault should have received some underlying tokens and updated its state + let new_liquidity = underlying_disp.balance_of(vault_allocator.contract_address); + assert( + new_liquidity == initial_liquidity - bring_liquidity_amount, 'tokens should be transferred', + ); + + // Check that the vault has increased liquidity + let new_buffer = mock_vault.buffer(); + let new_aum = mock_vault.aum(); + + assert(new_buffer == initial_buffer + bring_liquidity_amount, 'buffer should increase'); + assert(new_aum == initial_liquidity - bring_liquidity_amount, 'aum should decrease'); +} diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index d000d071..6d650813 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -16,6 +16,7 @@ use vault_allocator::integration_interfaces::vesu::{ }; use vault_allocator::manager::interface::IManagerDispatcher; use vault_allocator::mocks::counter::ICounterDispatcher; +use vault_allocator::mocks::vault::MockVault::MockVaultTraitDispatcher; use vault_allocator::periphery::price_router::interface::{ IPriceRouterDispatcher, IPriceRouterDispatcherTrait, }; @@ -101,6 +102,14 @@ pub fn deploy_erc20_mock() -> ContractAddress { erc20_address } +pub fn deploy_mock_vault(underlying: ContractAddress) -> MockVaultTraitDispatcher { + let mock_vault = declare("MockVault").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + underlying.serialize(ref calldata); + let (mock_vault_address, _) = mock_vault.deploy(@calldata).unwrap(); + MockVaultTraitDispatcher { contract_address: mock_vault_address } +} + pub fn deploy_simple_decoder_and_sanitizer() -> ContractAddress { let simple_decoder_and_sanitizer = declare("SimpleDecoderAndSanitizer") .unwrap() @@ -241,24 +250,7 @@ fn _next_power_of_two(x: u256) -> u256 { } power } -// pub fn _pad_leafs_to_power_of_two(ref leafs: Array, ref leaf_index: u256) { -// let next_power = _next_power_of_two(leaf_index); -// let padding_needed = next_power - leaf_index; - -// let default_leaf = ManageLeaf { -// decoder_and_sanitizer: Zero::zero(), -// target: Zero::zero(), -// selector: Zero::zero(), -// argument_addresses: ArrayTrait::new().span(), -// }; - -// let mut i = 0; -// while i < padding_needed { -// leafs.append(default_leaf); -// leaf_index += 1; -// i += 1; -// } -// } + pub fn _pad_leafs_to_power_of_two(ref leafs: Array, ref leaf_index: u256) { let target_len = if leaf_index < 4_u256 { @@ -335,6 +327,53 @@ pub fn _generate_proof(mut leaf: felt252, tree: Array>) -> Span, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + vault: IERC4626Dispatcher, +) { + let underlying_asset = vault.asset(); + + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: underlying_asset, + selector: selector!("approve"), + argument_addresses: array![vault.contract_address.into()].span(), + description: "Approve" + + " " + + get_symbol(vault.contract_address) + + " " + + "to spend" + + " " + + get_symbol(underlying_asset), + }, + ); + leaf_index += 1; + + // Bring liquidity + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: vault.contract_address, + selector: selector!("bring_liquidity"), + argument_addresses: array![].span(), + description: "Bring liquidity" + " " + get_symbol(vault.contract_address), + }, + ); + leaf_index += 1; +} + + // ========================================= ERC4626 ========================================= pub fn _add_erc4626_leafs( From 5016478cdd39f3ba554af95dc508ba8404e1ca1e Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:19:40 +0100 Subject: [PATCH 13/54] Add uncap decoder and sanitizer --- .../interface.cairo | 12 ++++++ .../uncap_decoder_and_sanitizer.cairo | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/interface.cairo create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/uncap_decoder_and_sanitizer.cairo diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..b86d7bac --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IUnCapDecoderAndSanitizer { + fn provide_to_sp(self: @T, top_up: u256, do_claim: bool) -> Span; + fn withdraw_from_sp(self: @T, amount: u256, do_claim: bool) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/uncap_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/uncap_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..581fa73e --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/uncap_decoder_and_sanitizer.cairo @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod UnCapDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::uncap_decoder_and_sanitizer::interface::IUnCapDecoderAndSanitizer; + + #[storage] + pub struct Storage { + pub vault_allocator: ContractAddress, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(UnCapDecoderAndSanitizerImpl)] + impl UnCapDecoderAndSanitizer< + TContractState, +HasComponent, + > of IUnCapDecoderAndSanitizer> { + fn provide_to_sp( + self: @ComponentState, top_up: u256, do_claim: bool, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + + fn withdraw_from_sp( + self: @ComponentState, amount: u256, do_claim: bool, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + } +} From 9579b456454964cc6ccd4def85f37fd0cd85c7b2 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:25:00 +0100 Subject: [PATCH 14/54] Delete constants.ts --- scripts/constants.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 scripts/constants.ts diff --git a/scripts/constants.ts b/scripts/constants.ts deleted file mode 100644 index e69de29b..00000000 From e583f6f0ffa56f118d3466ab7a0c6d22e9c30767 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:27:17 +0100 Subject: [PATCH 15/54] Add backend development infrastructure and improve configuration - Add development Docker setup with dedicated dev compose file - Configure nodemon for hot reload in API and indexer services - Add FORCE_START_BLOCK option for flexible indexer restart - Improve error handling in Prisma service for missing tables - Add decimal.js dependency for precise financial calculations - Update environment validation with proper types - Enhance logging consistency across services - Add comprehensive Docker command orchestration - Update README with backend and scripts documentation sections --- README.md | 6 + backend/.env.example | 3 + backend/Dockerfile.dev | 38 +++++ backend/apps/api/nodemon.json | 8 ++ backend/apps/api/package.json | 8 +- backend/apps/api/src/app.service.ts | 134 ++++++++++-------- backend/apps/api/src/main.ts | 58 ++++---- backend/apps/indexer/nodemon.json | 8 ++ backend/apps/indexer/package.json | 14 +- backend/apps/indexer/src/indexer.service.ts | 112 ++++++++------- backend/docker-compose.dev.yml | 72 ++++++++++ backend/docker-compose.yml | 20 ++- backend/libs/config/src/config.service.ts | 3 +- backend/libs/config/src/env.validation.ts | 18 ++- backend/libs/db/src/prisma.service.ts | 103 +++++++++----- backend/pnpm-lock.yaml | 112 +++++++++++++-- .../20250826190954_init/migration.sql | 67 +++++++++ backend/prisma/migrations/migration_lock.toml | 3 + backend/prisma/schema.prisma | 3 +- 19 files changed, 587 insertions(+), 203 deletions(-) create mode 100644 backend/Dockerfile.dev create mode 100644 backend/apps/api/nodemon.json create mode 100644 backend/apps/indexer/nodemon.json create mode 100644 backend/docker-compose.dev.yml create mode 100644 backend/prisma/migrations/20250826190954_init/migration.sql create mode 100644 backend/prisma/migrations/migration_lock.toml diff --git a/README.md b/README.md index 5d2c7c76..238a983e 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,12 @@ scarb build snforge test ``` +### Scripts & Backend + +For deployment scripts and configuration utilities, see [scripts/README.md](scripts/README.md). + +For backend services (API and indexer), see [backend/README.md](backend/README.md). + ## Testing The project includes comprehensive test suites: diff --git a/backend/.env.example b/backend/.env.example index f23418c6..b26d5b2a 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -13,5 +13,8 @@ VAULT_ADDRESS="0x000000000000000000000000000000000000000000000000000000000000000 # Indexing start block START_BLOCK=12993 +# Force start from START_BLOCK (ignores database state) +FORCE_START_BLOCK=false + # Apibara token for indexing (required for indexer) APIBARA_TOKEN="your_apibara_token_here" \ No newline at end of file diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev new file mode 100644 index 00000000..73282c54 --- /dev/null +++ b/backend/Dockerfile.dev @@ -0,0 +1,38 @@ +FROM node:18-slim as base + +# Install OpenSSL 1.1 and other necessary packages +RUN apt-get update && apt-get install -y \ + openssl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Install pnpm +RUN npm install -g pnpm + +# Copy package files for dependency installation +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/api/package.json ./apps/api/ +COPY apps/indexer/package.json ./apps/indexer/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ + +# Install all dependencies (including dev dependencies for development) +RUN pnpm install + +# Copy prisma schema for client generation +COPY prisma ./prisma/ + +# Generate Prisma client +RUN pnpm run prisma:generate + +# API target for development +FROM base as api +EXPOSE 3000 +CMD ["pnpm", "run", "dev:api"] + +# Indexer target for development +FROM base as indexer +CMD ["pnpm", "run", "dev:indexer"] \ No newline at end of file diff --git a/backend/apps/api/nodemon.json b/backend/apps/api/nodemon.json new file mode 100644 index 00000000..ec1b3153 --- /dev/null +++ b/backend/apps/api/nodemon.json @@ -0,0 +1,8 @@ +{ + "watch": ["src", "../../../libs"], + "ext": "ts,js,json", + "ignore": ["node_modules", "dist"], + "env": { + "NODE_ENV": "development" + } +} \ No newline at end of file diff --git a/backend/apps/api/package.json b/backend/apps/api/package.json index dd6dd02f..4f53d2bf 100644 --- a/backend/apps/api/package.json +++ b/backend/apps/api/package.json @@ -5,7 +5,7 @@ "main": "dist/main.js", "scripts": { "build": "nest build", - "dev": "nest start --watch", + "dev": "nodemon --exec ts-node src/main.ts", "start": "node dist/main.js", "lint": "eslint \"src/**/*.ts\" --fix", "test": "jest" @@ -23,6 +23,7 @@ "@nestjs/terminus": "^11.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "decimal.js": "^10.6.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "starknet": "^6.0.0" @@ -31,7 +32,10 @@ "@nestjs/cli": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", "jest": "^29.5.0", - "ts-jest": "^29.1.0" + "ts-jest": "^29.1.0", + "ts-node": "^10.9.1", + "nodemon": "^3.0.0" } } \ No newline at end of file diff --git a/backend/apps/api/src/app.service.ts b/backend/apps/api/src/app.service.ts index e3c682e1..2032a86f 100644 --- a/backend/apps/api/src/app.service.ts +++ b/backend/apps/api/src/app.service.ts @@ -10,7 +10,7 @@ import { Logger } from "@forge/logger"; @Injectable() export class AppService implements OnModuleInit { strategyDecimals: number; - private readonly logger = Logger.create('AppService'); + private readonly logger = Logger.create("AppService"); constructor( private readonly prismaService: PrismaService, @@ -20,18 +20,18 @@ export class AppService implements OnModuleInit { async onModuleInit() { try { - const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; - this.logger.info('Initializing AppService', { vaultAddress }); - + const vaultAddress = this.configService.get("VAULT_ADDRESS") as string; + this.logger.info("Initializing AppService", { vaultAddress }); + this.strategyDecimals = Number( await this.starknetService.vault_decimals(vaultAddress) ); - - this.logger.info('AppService initialized successfully', { - strategyDecimals: this.strategyDecimals + + this.logger.info("AppService initialized successfully", { + strategyDecimals: this.strategyDecimals, }); } catch (error) { - this.logger.error('Failed to initialize AppService', error); + this.logger.error("Failed to initialize AppService", error); throw error; } } @@ -58,10 +58,10 @@ export class AppService implements OnModuleInit { ): Promise { try { const addressToUse = validateAndParseAddress(address); - this.logger.info('Fetching pending redeems', { + this.logger.info("Fetching pending redeems", { address: addressToUse, limit, - offset + offset, }); // Fetch pending redeems directly from PrismaService @@ -72,19 +72,22 @@ export class AppService implements OnModuleInit { offset ); - this.logger.debug('Found pending redeems from database', { - count: pendingRedeemsForStrategy.length + this.logger.debug("Found pending redeems from database", { + count: pendingRedeemsForStrategy.length, }); // Map database results to PendingRedeem type with parallel processing const pendingRedeemPromises = pendingRedeemsForStrategy.map( async (redeem) => { try { - const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; - const dueAssets = await this.starknetService.vault_due_assets_from_id( - vaultAddress, - Number(redeem.redeemId) - ); + const vaultAddress = this.configService.get( + "VAULT_ADDRESS" + ) as string; + const dueAssets = + await this.starknetService.vault_due_assets_from_id( + vaultAddress, + Number(redeem.redeemId) + ); return { epoch: Number(redeem.epoch), @@ -96,8 +99,8 @@ export class AppService implements OnModuleInit { transactionHash: redeem.transactionHash, }; } catch (error) { - this.logger.error('Failed to process pending redeem', error, { - redeemId: redeem.redeemId.toString() + this.logger.error("Failed to process pending redeem", error, { + redeemId: redeem.redeemId.toString(), }); throw error; } @@ -105,29 +108,29 @@ export class AppService implements OnModuleInit { ); const resolvedPendingRedeems = await Promise.all(pendingRedeemPromises); - - this.logger.info('Successfully processed pending redeems', { + + this.logger.info("Successfully processed pending redeems", { address: addressToUse, - count: resolvedPendingRedeems.length + count: resolvedPendingRedeems.length, }); - + return resolvedPendingRedeems; } catch (error) { - this.logger.error('Failed to get pending redeems', error, { address }); + this.logger.error("Failed to get pending redeems", error, { address }); throw error; } } public async getLastReport() { try { - this.logger.debug('Fetching last report'); - + this.logger.debug("Fetching last report"); + const lastReport = await this.prismaService.fetchLastReport(); if (!lastReport) { - this.logger.info('No reports found in database'); + this.logger.info("No reports found in database"); return null; } - + const result = { id: lastReport.id, blockNumber: lastReport.blockNumber, @@ -137,55 +140,66 @@ export class AppService implements OnModuleInit { newHandledEpochLen: lastReport.newHandledEpochLen.toString(), totalSupply: this.formatBigIntToDecimal(lastReport.totalSupply), totalAssets: this.formatBigIntToDecimal(lastReport.totalAssets), - managementFeeShares: this.formatBigIntToDecimal(lastReport.managementFeeShares), - performanceFeeShares: this.formatBigIntToDecimal(lastReport.performanceFeeShares), + managementFeeShares: this.formatBigIntToDecimal( + lastReport.managementFeeShares + ), + performanceFeeShares: this.formatBigIntToDecimal( + lastReport.performanceFeeShares + ), }; - - this.logger.info('Successfully fetched last report', { + + this.logger.info("Successfully fetched last report", { reportId: result.id, blockNumber: result.blockNumber, - epoch: result.newEpoch + epoch: result.newEpoch, }); - + return result; } catch (error) { - this.logger.error('Failed to get last report', error); + this.logger.error("Failed to get last report", error); throw error; } } public async getRedeemById(redeemId: string) { try { - this.logger.info('Fetching redeem by ID', { redeemId }); - - const redeemRequested = await this.prismaService.redeemRequested.findUnique({ - where: { redeemId: BigInt(redeemId) } - }); - + this.logger.info("Fetching redeem by ID", { redeemId }); + + const redeemRequested = + await this.prismaService.redeemRequested.findUnique({ + where: { redeemId: BigInt(redeemId) }, + }); + if (!redeemRequested) { - this.logger.info('Redeem request not found', { redeemId }); + this.logger.info("Redeem request not found", { redeemId }); return null; } const redeemClaimed = await this.prismaService.redeemClaimed.findUnique({ - where: { redeemId: BigInt(redeemId) } + where: { redeemId: BigInt(redeemId) }, }); // Get current due assets from the contract (only if not claimed yet) let currentDueAssets: bigint | null = null; if (!redeemClaimed) { try { - const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; - currentDueAssets = await this.starknetService.vault_due_assets_from_id( - vaultAddress, - Number(redeemId) - ); - this.logger.debug('Fetched current due assets from contract', { + const vaultAddress = this.configService.get( + "VAULT_ADDRESS" + ) as string; + currentDueAssets = + await this.starknetService.vault_due_assets_from_id( + vaultAddress, + Number(redeemId) + ); + this.logger.debug("Fetched current due assets from contract", { redeemId, - dueAssets: currentDueAssets?.toString() + dueAssets: currentDueAssets?.toString(), }); } catch (error) { - this.logger.warn('Failed to fetch due assets for redeem ID', { redeemId, error: error.message }); + this.logger.warn("Failed to fetch due assets for redeem ID", { + redeemId, + error: error.message, + }); } } @@ -196,23 +210,27 @@ export class AppService implements OnModuleInit { epoch: redeemRequested.epoch.toString(), sharesBurn: this.formatBigIntToDecimal(redeemRequested.shares), nominal: this.formatBigIntToDecimal(redeemRequested.assets), - assets: currentDueAssets ? this.formatBigIntToDecimal(currentDueAssets) : null, + assets: currentDueAssets + ? this.formatBigIntToDecimal(currentDueAssets) + : null, requestTimestamp: redeemRequested.timestamp, requestTransactionHash: redeemRequested.transactionHash, claimedTimestamp: redeemClaimed?.timestamp, claimedTransactionHash: redeemClaimed?.transactionHash, - claimedAssets: redeemClaimed ? this.formatBigIntToDecimal(redeemClaimed.assets) : null, + claimedAssets: redeemClaimed + ? this.formatBigIntToDecimal(redeemClaimed.assets) + : null, }; - - this.logger.info('Successfully fetched redeem details', { + + this.logger.info("Successfully fetched redeem details", { redeemId, isClaimed: !!redeemClaimed, - owner: result.owner + owner: result.owner, }); - + return result; } catch (error) { - this.logger.error('Failed to get redeem by ID', error, { redeemId }); + this.logger.error("Failed to get redeem by ID", error, { redeemId }); throw error; } } diff --git a/backend/apps/api/src/main.ts b/backend/apps/api/src/main.ts index 9f45c7f3..a05a891f 100644 --- a/backend/apps/api/src/main.ts +++ b/backend/apps/api/src/main.ts @@ -1,29 +1,29 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; -import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; -import { validateApiConfig } from '@forge/config'; -import { Logger, LogLevel, LoggerConfig } from '@forge/logger'; -import { AppModule } from './app.module'; +import { NestFactory } from "@nestjs/core"; +import { ValidationPipe } from "@nestjs/common"; +import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; +import { validateApiConfig } from "@forge/config"; +import { Logger, LogLevel, LoggerConfig } from "@forge/logger"; +import { AppModule } from "./app.module"; async function bootstrap() { // Configure global logger const loggerConfig: LoggerConfig = { level: (process.env.LOG_LEVEL as LogLevel) || LogLevel.INFO, - service: 'vault-api', + service: "vault-api", enableConsole: true, - enableFile: process.env.NODE_ENV === 'production', - logDir: process.env.LOG_DIR || 'logs', - format: process.env.NODE_ENV === 'production' ? 'json' : 'simple', + enableFile: process.env.NODE_ENV === "production", + logDir: process.env.LOG_DIR || "logs", + format: process.env.NODE_ENV === "production" ? "json" : "simple", }; - + Logger.configure(loggerConfig); - const logger = Logger.create('Bootstrap'); + const logger = Logger.create("Bootstrap"); try { // Validate environment variables const envConfig = validateApiConfig(process.env); - logger.info('Environment configuration validated successfully'); - + logger.info("Environment configuration validated successfully"); + const app = await NestFactory.create(AppModule); // Configure global validation pipes @@ -32,40 +32,38 @@ async function bootstrap() { whitelist: true, forbidNonWhitelisted: true, transform: true, - }), + }) ); - logger.info('Global validation pipes configured'); + logger.info("Global validation pipes configured"); // Enable CORS app.enableCors(); - logger.info('CORS enabled'); + logger.info("CORS enabled"); // Setup Swagger documentation const config = new DocumentBuilder() - .setTitle('StarkNet Vault Kit API') - .setDescription('API for StarkNet Vault Kit indexer and services') - .setVersion('1.0') - .addTag('indexer') - .addTag('starknet') + .setTitle("StarkNet Vault Kit API") + .setDescription("API for StarkNet Vault Kit indexer and services") + .setVersion("1.0") .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('api', app, document); - logger.info('Swagger documentation configured at /api'); + SwaggerModule.setup("api", app, document); + logger.info("Swagger documentation configured at /api"); const port = process.env.PORT || 3000; await app.listen(port); - - logger.info('🚀 StarkNet Vault Kit API started successfully', { + + logger.info("🚀 StarkNet Vault Kit API started successfully", { port, - environment: process.env.NODE_ENV || 'development', + environment: process.env.NODE_ENV || "development", apiUrl: `http://localhost:${port}`, - docsUrl: `http://localhost:${port}/api` + docsUrl: `http://localhost:${port}/api`, }); } catch (error) { - logger.error('❌ Failed to start API server', error); + logger.error("❌ Failed to start API server", error); process.exit(1); } } -bootstrap(); \ No newline at end of file +bootstrap(); diff --git a/backend/apps/indexer/nodemon.json b/backend/apps/indexer/nodemon.json new file mode 100644 index 00000000..ec1b3153 --- /dev/null +++ b/backend/apps/indexer/nodemon.json @@ -0,0 +1,8 @@ +{ + "watch": ["src", "../../../libs"], + "ext": "ts,js,json", + "ignore": ["node_modules", "dist"], + "env": { + "NODE_ENV": "development" + } +} \ No newline at end of file diff --git a/backend/apps/indexer/package.json b/backend/apps/indexer/package.json index 13e5e06a..81e68cfe 100644 --- a/backend/apps/indexer/package.json +++ b/backend/apps/indexer/package.json @@ -5,22 +5,24 @@ "main": "dist/main.js", "scripts": { "build": "tsc", - "dev": "ts-node src/main.ts", + "dev": "nodemon --exec ts-node src/main.ts", "start": "node dist/main.js", "lint": "eslint \"src/**/*.ts\" --fix" }, "dependencies": { + "@apibara/protocol": "^0.4.9", + "@apibara/starknet": "^0.5.0", "@forge/config": "workspace:*", "@forge/db": "workspace:*", "@forge/logger": "workspace:*", - "@apibara/protocol": "^0.4.9", - "@apibara/starknet": "^0.5.0", - "starknet": "^6.0.0", "class-transformer": "^0.5.1", - "class-validator": "^0.14.0" + "class-validator": "^0.14.0", + "reflect-metadata": "^0.2.0", + "starknet": "^6.0.0" }, "devDependencies": { + "@types/node": "^20.3.1", "ts-node": "^10.9.1", - "@types/node": "^20.3.1" + "nodemon": "^3.0.0" } } \ No newline at end of file diff --git a/backend/apps/indexer/src/indexer.service.ts b/backend/apps/indexer/src/indexer.service.ts index 8fae34b5..a3fb7b94 100644 --- a/backend/apps/indexer/src/indexer.service.ts +++ b/backend/apps/indexer/src/indexer.service.ts @@ -56,7 +56,7 @@ export class IndexerService { private readonly configService: ConfigService, private readonly prismaService: PrismaService ) { - this.logger = Logger.create('IndexerService'); + this.logger = Logger.create("IndexerService"); this.url = "mainnet.starknet.a5a.ch"; this.apibaraToken = this.configService.get("APIBARA_TOKEN") as string; @@ -80,7 +80,10 @@ export class IndexerService { url: this.url, token: this.apibaraToken, onReconnect: async (err, retryCount) => { - this.logger.error('Connection lost', err, { retryCount, errorCode: err.code }); + this.logger.error("Connection lost", err, { + retryCount, + errorCode: err.code, + }); if (err.code !== 13 && err.code !== 14) { // Code 13 = internal error, 14 = unavailable return { reconnect: false }; @@ -99,13 +102,13 @@ export class IndexerService { const filterBuilder = Filter.create().withHeader({ weak: false }); - const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + const vaultAddress = this.configService.get("VAULT_ADDRESS") as string; try { this.vaultFe = FieldElement.fromBigInt( validateAndParseAddress(vaultAddress) ); } catch (e) { - this.logger.error('Invalid vault address', e, { vaultAddress }); + this.logger.error("Invalid vault address", e, { vaultAddress }); throw e; } @@ -124,30 +127,38 @@ export class IndexerService { ); }); - let startBlock = Number(this.configService.get('START_BLOCK')); - - const [lastRedeemRequested, lastRedeemClaimed, lastReport] = - await Promise.all([ - this.prismaService.fetchLastRedeemRequested(), - this.prismaService.fetchLastRedeemClaimed(), - this.prismaService.fetchLastReport(), - ]); - - const maxBlock = Math.max( - lastRedeemRequested?.blockNumber || 0, - lastRedeemClaimed?.blockNumber || 0, - lastReport?.blockNumber || 0, - startBlock - ); - - startBlock = maxBlock + 1; - - this.logger.info(`📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`, { - startBlock, - lastRedeemRequestedBlock: lastRedeemRequested?.blockNumber || 0, - lastRedeemClaimedBlock: lastRedeemClaimed?.blockNumber || 0, - lastReportBlock: lastReport?.blockNumber || 0, - }); + let startBlock = Number(this.configService.get("START_BLOCK")) || 0; + const forceStartBlock = this.configService.get("FORCE_START_BLOCK"); + + if (forceStartBlock == "true") { + this.logger.info( + `🔥 Force starting from block: ${startBlock} (FORCE_START_BLOCK=true, using START_BLOCK)` + ); + } else { + const [lastRedeemRequested, lastRedeemClaimed, lastReport] = + await Promise.all([ + this.prismaService.fetchLastRedeemRequested(), + this.prismaService.fetchLastRedeemClaimed(), + this.prismaService.fetchLastReport(), + ]); + + startBlock = Math.max( + lastRedeemRequested?.blockNumber || 0, + lastRedeemClaimed?.blockNumber || 0, + lastReport?.blockNumber || 0, + startBlock + ); + + this.logger.info( + `📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`, + { + startBlock, + lastRedeemRequestedBlock: lastRedeemRequested?.blockNumber || 0, + lastRedeemClaimedBlock: lastRedeemClaimed?.blockNumber || 0, + lastReportBlock: lastReport?.blockNumber || 0, + } + ); + } const cursor = StarkNetCursor.createWithBlockNumber(startBlock); @@ -173,14 +184,15 @@ export class IndexerService { throw new Error("No timestamp in block header"); } - this.logger.debug('Processing block', { + this.logger.debug("Processing block", { blockNumber: blockNum, lastBlockIndexed: this.lastBlockIndexedVault, - timestamp: Number(timestamp) + timestamp: Number(timestamp), }); for (let event of block.events) { const hash = event.transaction?.meta?.hash; + if (!hash) { throw new Error("No hash"); } @@ -204,11 +216,11 @@ export class IndexerService { } }); - this.logger.debug('Processing event', { + this.logger.debug("Processing event", { eventType: this.getEventTypeName(eventTypeHex), blockNumber: blockNum, transactionHash: hashHex, - eventTypeHex + eventTypeHex, }); try { @@ -220,10 +232,10 @@ export class IndexerService { eventTypeHex, }); } catch (error) { - this.logger.error('Failed to process event', error, { + this.logger.error("Failed to process event", error, { blockNumber: blockNum, transactionHash: hashHex, - eventType: this.getEventTypeName(eventTypeHex) + eventType: this.getEventTypeName(eventTypeHex), }); throw error; } @@ -275,7 +287,7 @@ export class IndexerService { transactionHash ); } else { - this.logger.warn('Unknown event type', { eventTypeHex }); + this.logger.warn("Unknown event type", { eventTypeHex }); } } @@ -309,9 +321,9 @@ export class IndexerService { await this.flushRedeemRequestedBuffer(); } } catch (error) { - this.logger.error('Failed to process RedeemRequested event', error, { + this.logger.error("Failed to process RedeemRequested event", error, { blockNumber, - transactionHash + transactionHash, }); } } @@ -343,9 +355,9 @@ export class IndexerService { await this.flushRedeemClaimedBuffer(); } } catch (error) { - this.logger.error('Failed to process RedeemClaimed event', error, { + this.logger.error("Failed to process RedeemClaimed event", error, { blockNumber, - transactionHash + transactionHash, }); } } @@ -376,9 +388,9 @@ export class IndexerService { await this.flushReportBuffer(); } } catch (error) { - this.logger.error('Failed to process Report event', error, { + this.logger.error("Failed to process Report event", error, { blockNumber, - transactionHash + transactionHash, }); } } @@ -391,12 +403,12 @@ export class IndexerService { data: this.redeemRequestedBuffer, skipDuplicates: true, }); - this.logger.info('Flushed RedeemRequested events', { - count: this.redeemRequestedBuffer.length + this.logger.info("Flushed RedeemRequested events", { + count: this.redeemRequestedBuffer.length, }); this.redeemRequestedBuffer = []; } catch (err) { - this.logger.error('Failed to flush RedeemRequested buffer', err); + this.logger.error("Failed to flush RedeemRequested buffer", err); throw err; } } @@ -409,12 +421,12 @@ export class IndexerService { data: this.redeemClaimedBuffer, skipDuplicates: true, }); - this.logger.info('Flushed RedeemClaimed events', { - count: this.redeemClaimedBuffer.length + this.logger.info("Flushed RedeemClaimed events", { + count: this.redeemClaimedBuffer.length, }); this.redeemClaimedBuffer = []; } catch (err) { - this.logger.error('Failed to flush RedeemClaimed buffer', err); + this.logger.error("Failed to flush RedeemClaimed buffer", err); throw err; } } @@ -427,12 +439,12 @@ export class IndexerService { data: this.reportBuffer, skipDuplicates: true, }); - this.logger.info('Flushed Report events', { - count: this.reportBuffer.length + this.logger.info("Flushed Report events", { + count: this.reportBuffer.length, }); this.reportBuffer = []; } catch (err) { - this.logger.error('Failed to flush Report buffer', err); + this.logger.error("Failed to flush Report buffer", err); throw err; } } diff --git a/backend/docker-compose.dev.yml b/backend/docker-compose.dev.yml new file mode 100644 index 00000000..662696b7 --- /dev/null +++ b/backend/docker-compose.dev.yml @@ -0,0 +1,72 @@ +# version: '3.8' # obsolete attribute + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: starknet_vault_kit + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + indexer: + build: + context: . + dockerfile: Dockerfile.dev + target: indexer + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - APIBARA_TOKEN=${APIBARA_TOKEN} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - START_BLOCK=${START_BLOCK} + - FORCE_START_BLOCK=${FORCE_START_BLOCK} + volumes: + - .:/app + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + command: > + sh -c " + echo '✅ Starting migration and indexer...'; + npx prisma migrate deploy; + echo '✅ Migration done! Starting Indexer...'; + pnpm run dev:indexer + " + + api: + build: + context: . + dockerfile: Dockerfile.dev + target: api + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - VAULT_ADDRESS=${VAULT_ADDRESS} + volumes: + - .:/app + ports: + - "3000:3000" + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + command: > + sh -c " + echo '✅ Starting migration and API...'; + npx prisma migrate deploy; + echo '✅ Migration done! Starting API...'; + pnpm run dev:api + " + +volumes: + postgres_data: \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index dfc80bc9..99b4a60c 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +# version: '3.8' # obsolete attribute services: postgres: @@ -25,10 +25,20 @@ services: - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} - APIBARA_TOKEN=${APIBARA_TOKEN} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - START_BLOCK=${START_BLOCK} + - FORCE_START_BLOCK=${FORCE_START_BLOCK} depends_on: postgres: condition: service_healthy restart: unless-stopped + command: > + sh -c " + echo '✅ Starting migration and indexer...'; + npx prisma migrate deploy; + echo '✅ Migration done! Starting Indexer...'; + node dist/apps/indexer/main + " api: build: @@ -37,12 +47,20 @@ services: environment: - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - VAULT_ADDRESS=${VAULT_ADDRESS} ports: - "3000:3000" depends_on: postgres: condition: service_healthy restart: unless-stopped + command: > + sh -c " + echo '✅ Starting migration and API...'; + npx prisma migrate deploy; + echo '✅ Migration done! Starting API...'; + node dist/apps/api/main + " volumes: postgres_data: \ No newline at end of file diff --git a/backend/libs/config/src/config.service.ts b/backend/libs/config/src/config.service.ts index e87e27d3..9ef6f44a 100644 --- a/backend/libs/config/src/config.service.ts +++ b/backend/libs/config/src/config.service.ts @@ -1,8 +1,7 @@ import { ConfigService as ConfigServiceSource } from "@nestjs/config"; -import { BaseEnvironmentVariables } from "./env.validation"; export class ConfigService extends ConfigServiceSource { public get(key: string): string | number | undefined { return super.get(key, { infer: true }); } -} \ No newline at end of file +} diff --git a/backend/libs/config/src/env.validation.ts b/backend/libs/config/src/env.validation.ts index 1f98ac8b..49055d62 100644 --- a/backend/libs/config/src/env.validation.ts +++ b/backend/libs/config/src/env.validation.ts @@ -1,5 +1,5 @@ import { plainToClass, Transform } from "class-transformer"; -import { IsNumber, IsOptional, validateSync, IsString } from "class-validator"; +import { IsNumber, IsOptional, validateSync, IsString, IsBoolean } from "class-validator"; // Base environment variables needed by all services export class BaseEnvironmentVariables { @@ -16,12 +16,28 @@ export class ApiEnvironmentVariables extends BaseEnvironmentVariables { @IsString() RPC_URL: string; + + @IsString() + VAULT_ADDRESS: string; } // Indexer-specific environment variables export class IndexerEnvironmentVariables extends BaseEnvironmentVariables { @IsString() APIBARA_TOKEN: string; + + @IsString() + VAULT_ADDRESS: string; + + @IsOptional() + @IsNumber() + @Transform(({ value }) => Number(value)) + START_BLOCK?: number; + + @IsOptional() + @IsBoolean() + @Transform(({ value }) => value === 'true' || value === true) + FORCE_START_BLOCK?: boolean; } // Generic validation function diff --git a/backend/libs/db/src/prisma.service.ts b/backend/libs/db/src/prisma.service.ts index f1365f95..2349cc9c 100644 --- a/backend/libs/db/src/prisma.service.ts +++ b/backend/libs/db/src/prisma.service.ts @@ -16,33 +16,53 @@ export class PrismaService } async fetchLastRedeemRequested(): Promise { - const latest = await this.redeemRequested.findFirst({ - orderBy: { blockNumber: "desc" }, - }); - return latest as RedeemRequested | undefined; + try { + const latest = await this.redeemRequested.findFirst({ + orderBy: { blockNumber: "desc" }, + }); + return latest as RedeemRequested | undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } } async fetchLastReport(): Promise { - const latest = await this.report.findFirst({ - orderBy: { blockNumber: "desc" }, - }); - return latest as Report | undefined; + try { + const latest = await this.report.findFirst({ + orderBy: { blockNumber: "desc" }, + }); + return latest as Report | undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } } async fetchLastReports(limit: number, offset?: number): Promise { - const reports = await this.report.findMany({ - orderBy: { blockNumber: "desc" }, - take: limit, - ...(offset && { skip: offset }), - }); - return reports as Report[]; + try { + const reports = await this.report.findMany({ + orderBy: { blockNumber: "desc" }, + take: limit, + ...(offset && { skip: offset }), + }); + return reports as Report[]; + } catch (error) { + // Table doesn't exist yet, return empty array + return []; + } } async fetchLastRedeemClaimed(): Promise { - const latest = await this.redeemClaimed.findFirst({ - orderBy: { blockNumber: "desc" }, - }); - return latest as RedeemClaimed | undefined; + try { + const latest = await this.redeemClaimed.findFirst({ + orderBy: { blockNumber: "desc" }, + }); + return latest as RedeemClaimed | undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } } @@ -51,27 +71,32 @@ export class PrismaService limit?: number, offset?: number ): Promise { - // Get all claimed redeem IDs for this address - const claimedRedeemIds = await this.redeemClaimed.findMany({ - where: { receiver }, - select: { redeemId: true }, - }); - - const claimedIds = claimedRedeemIds.map(r => r.redeemId); - - // Find all requested redeems that are NOT in the claimed list - return this.redeemRequested.findMany({ - where: { - receiver, - redeemId: { - notIn: claimedIds, + try { + // Get all claimed redeem IDs for this address + const claimedRedeemIds = await this.redeemClaimed.findMany({ + where: { receiver }, + select: { redeemId: true }, + }); + + const claimedIds = claimedRedeemIds.map(r => r.redeemId); + + // Find all requested redeems that are NOT in the claimed list + return this.redeemRequested.findMany({ + where: { + receiver, + redeemId: { + notIn: claimedIds, + }, }, - }, - orderBy: { - redeemId: "desc", - }, - ...(limit && { take: limit }), - ...(offset && { skip: offset }), - }); + orderBy: { + redeemId: "desc", + }, + ...(limit && { take: limit }), + ...(offset && { skip: offset }), + }); + } catch (error) { + // Tables don't exist yet, return empty array + return []; + } } } diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 492a69ca..d3c19102 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -117,6 +117,9 @@ importers: class-validator: specifier: ^0.14.0 version: 0.14.2 + decimal.js: + specifier: ^10.6.0 + version: 10.6.0 reflect-metadata: specifier: ^0.2.0 version: 0.2.2 @@ -136,12 +139,21 @@ importers: '@types/jest': specifier: ^29.5.2 version: 29.5.14 + '@types/node': + specifier: ^20.3.1 + version: 20.19.11 jest: specifier: ^29.5.0 version: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + nodemon: + specifier: ^3.0.0 + version: 3.1.10 ts-jest: specifier: ^29.1.0 version: 29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)))(typescript@5.9.2) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) apps/indexer: dependencies: @@ -166,6 +178,9 @@ importers: class-validator: specifier: ^0.14.0 version: 0.14.2 + reflect-metadata: + specifier: ^0.2.0 + version: 0.2.2 starknet: specifier: ^6.0.0 version: 6.24.1 @@ -173,6 +188,9 @@ importers: '@types/node': specifier: ^20.3.1 version: 20.19.11 + nodemon: + specifier: ^3.0.0 + version: 3.1.10 ts-node: specifier: ^10.9.1 version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) @@ -1505,6 +1523,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + dedent@1.6.0: resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} peerDependencies: @@ -1941,6 +1962,10 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1982,6 +2007,9 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2490,6 +2518,11 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + nodemon@3.1.10: + resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} + engines: {node: '>=10'} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2666,6 +2699,9 @@ packages: psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2867,6 +2903,10 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2967,6 +3007,10 @@ packages: resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==} engines: {node: '>=14.18.0'} + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3047,6 +3091,10 @@ packages: resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} engines: {node: '>=14.16'} + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -3180,6 +3228,9 @@ packages: resolution: {integrity: sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==} engines: {node: '>=18'} + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3416,7 +3467,7 @@ snapshots: '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3575,7 +3626,7 @@ snapshots: '@babel/parser': 7.28.3 '@babel/template': 7.27.2 '@babel/types': 7.28.2 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -3613,7 +3664,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -3641,7 +3692,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -4120,7 +4171,7 @@ snapshots: '@tokenizer/inflate@0.2.7': dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) fflate: 0.8.2 token-types: 6.1.1 transitivePeerDependencies: @@ -4271,7 +4322,7 @@ snapshots: '@typescript-eslint/types': 8.40.0 '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.40.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 typescript: 5.9.2 transitivePeerDependencies: @@ -4281,7 +4332,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) '@typescript-eslint/types': 8.40.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -4300,7 +4351,7 @@ snapshots: '@typescript-eslint/types': 8.40.0 '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) '@typescript-eslint/utils': 8.40.0(eslint@8.57.1)(typescript@5.9.2) - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) eslint: 8.57.1 ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 @@ -4315,7 +4366,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) '@typescript-eslint/types': 8.40.0 '@typescript-eslint/visitor-keys': 8.40.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -4904,9 +4955,13 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.1: + debug@4.4.1(supports-color@5.5.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + decimal.js@10.6.0: {} dedent@1.6.0: {} @@ -5050,7 +5105,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -5396,6 +5451,8 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + has-flag@3.0.0: {} + has-flag@4.0.0: {} has-own-prop@2.0.0: {} @@ -5432,6 +5489,8 @@ snapshots: ieee754@1.2.1: {} + ignore-by-default@1.0.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -5564,7 +5623,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -6097,6 +6156,19 @@ snapshots: node-releases@2.0.19: {} + nodemon@3.1.10: + dependencies: + chokidar: 3.6.0 + debug: 4.4.1(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.7.2 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + normalize-path@3.0.0: {} npm-run-path@4.0.1: @@ -6267,6 +6339,8 @@ snapshots: dependencies: punycode: 2.3.1 + pstree.remy@1.1.8: {} + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -6476,6 +6550,10 @@ snapshots: dependencies: is-arrayish: 0.3.2 + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.2 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -6571,7 +6649,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 3.5.4 @@ -6588,6 +6666,10 @@ snapshots: transitivePeerDependencies: - supports-color + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -6654,6 +6736,8 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + touch@3.1.1: {} + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -6768,6 +6852,8 @@ snapshots: uint8array-extras@1.4.1: {} + undefsafe@2.0.5: {} + undici-types@6.21.0: {} universalify@0.2.0: {} diff --git a/backend/prisma/migrations/20250826190954_init/migration.sql b/backend/prisma/migrations/20250826190954_init/migration.sql new file mode 100644 index 00000000..2ddc1093 --- /dev/null +++ b/backend/prisma/migrations/20250826190954_init/migration.sql @@ -0,0 +1,67 @@ +-- CreateTable +CREATE TABLE "Report" ( + "id" TEXT NOT NULL, + "blockNumber" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "transactionHash" TEXT NOT NULL, + "newEpoch" BIGINT NOT NULL, + "newHandledEpochLen" BIGINT NOT NULL, + "totalSupply" BIGINT NOT NULL, + "totalAssets" BIGINT NOT NULL, + "managementFeeShares" BIGINT NOT NULL, + "performanceFeeShares" BIGINT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Report_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RedeemRequested" ( + "id" TEXT NOT NULL, + "blockNumber" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "transactionHash" TEXT NOT NULL, + "owner" TEXT NOT NULL, + "receiver" TEXT NOT NULL, + "shares" BIGINT NOT NULL, + "assets" BIGINT NOT NULL, + "redeemId" BIGINT NOT NULL, + "epoch" BIGINT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "RedeemRequested_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RedeemClaimed" ( + "id" TEXT NOT NULL, + "blockNumber" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "transactionHash" TEXT NOT NULL, + "receiver" TEXT NOT NULL, + "redeemRequestNominal" BIGINT NOT NULL, + "assets" BIGINT NOT NULL, + "redeemId" BIGINT NOT NULL, + "epoch" BIGINT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "RedeemClaimed_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IndexerStatus" ( + "id" INTEGER NOT NULL DEFAULT 1, + "currentBlock" INTEGER NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "IndexerStatus_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Report_newEpoch_key" ON "Report"("newEpoch"); + +-- CreateIndex +CREATE UNIQUE INDEX "RedeemRequested_redeemId_key" ON "RedeemRequested"("redeemId"); + +-- CreateIndex +CREATE UNIQUE INDEX "RedeemClaimed_redeemId_key" ON "RedeemClaimed"("redeemId"); diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 42d09619..55ac3fdb 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -1,5 +1,6 @@ generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" + binaryTargets = ["native", "linux-arm64-openssl-3.0.x"] } datasource db { From 2e48672b4b35511f77548f0ec0a0fb3248f5115e Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:18:31 +0200 Subject: [PATCH 16/54] Add aum component --- .../aum_provider_4626/aum_provider_4626.cairo | 74 ++++++++++ .../aum_provider_4626/errors.cairo | 9 ++ .../aum_provider_4626/interface.cairo | 10 ++ .../src/aum_provider/base_aum_provider.cairo | 52 +++++++ packages/vault/src/aum_provider/errors.cairo | 9 ++ .../vault/src/aum_provider/interface.cairo | 9 ++ packages/vault/src/lib.cairo | 11 ++ scripts/configs/config.json | 3 +- scripts/declareContract.ts | 4 + scripts/deployContract.ts | 47 +++++-- scripts/package.json | 6 +- scripts/pnpm-lock.yaml | 130 ++---------------- scripts/vaultConfig.ts | 48 ++++++- 13 files changed, 277 insertions(+), 135 deletions(-) create mode 100644 packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo create mode 100644 packages/vault/src/aum_provider/aum_provider_4626/errors.cairo create mode 100644 packages/vault/src/aum_provider/aum_provider_4626/interface.cairo create mode 100644 packages/vault/src/aum_provider/base_aum_provider.cairo create mode 100644 packages/vault/src/aum_provider/errors.cairo create mode 100644 packages/vault/src/aum_provider/interface.cairo diff --git a/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo b/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo new file mode 100644 index 00000000..f47012a5 --- /dev/null +++ b/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod AumProvider4626 { + use core::num::traits::Zero; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use vault::aum_provider::aum_provider_4626::errors::Errors; + use vault::aum_provider::aum_provider_4626::interface::IAumProvider4626; + use vault::aum_provider::base_aum_provider::BaseAumProviderComponent; + component!( + path: BaseAumProviderComponent, storage: base_aum_provider, event: BaseAumProviderEvent, + ); + + #[abi(embed_v0)] + impl BaseAumProviderImpl = + BaseAumProviderComponent::BaseAumProviderImpl; + impl BaseAumProviderInternalImpl = BaseAumProviderComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + base_aum_provider: BaseAumProviderComponent::Storage, + strategy4626: IERC4626Dispatcher, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + BaseAumProviderEvent: BaseAumProviderComponent::Event, + } + + #[abi(embed_v0)] + impl AumProvider4626Impl of IAumProvider4626 { + fn get_strategy_4626(self: @ContractState) -> ContractAddress { + self.strategy4626.read().contract_address + } + } + + #[constructor] + fn constructor(ref self: ContractState, vault: ContractAddress, strategy4626: ContractAddress) { + self.base_aum_provider.initializer(vault); + if (strategy4626.is_zero()) { + Errors::invalid_strategy4626_address(); + } + + self.strategy4626.write(IERC4626Dispatcher { contract_address: strategy4626 }); + } + + // --- AUM Trait Implementation --- + impl AumTrait of BaseAumProviderComponent::AumTrait { + fn get_aum(self: @BaseAumProviderComponent::ComponentState) -> u256 { + let contract_state = self.get_contract(); + let strategy = contract_state.strategy4626.read(); + let vault_address = self.vault.read().contract_address; + let underlying_asset = strategy.asset(); + let underlying_dispatcher = ERC20ABIDispatcher { contract_address: underlying_asset }; + let underlying_balance = underlying_dispatcher.balance_of(vault_address); + let strategy_shares = ERC20ABIDispatcher { contract_address: strategy.contract_address } + .balance_of(vault_address); + let strategy_assets = if strategy_shares > 0 { + strategy.convert_to_assets(strategy_shares) + } else { + 0 + }; + + underlying_balance + strategy_assets + } + } +} diff --git a/packages/vault/src/aum_provider/aum_provider_4626/errors.cairo b/packages/vault/src/aum_provider/aum_provider_4626/errors.cairo new file mode 100644 index 00000000..916972c5 --- /dev/null +++ b/packages/vault/src/aum_provider/aum_provider_4626/errors.cairo @@ -0,0 +1,9 @@ +// 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 invalid_strategy4626_address() { + panic!("Invalid strategy4626 address"); + } +} diff --git a/packages/vault/src/aum_provider/aum_provider_4626/interface.cairo b/packages/vault/src/aum_provider/aum_provider_4626/interface.cairo new file mode 100644 index 00000000..c20e957a --- /dev/null +++ b/packages/vault/src/aum_provider/aum_provider_4626/interface.cairo @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IAumProvider4626 { + fn get_strategy_4626(self: @TContractState) -> ContractAddress; +} \ No newline at end of file diff --git a/packages/vault/src/aum_provider/base_aum_provider.cairo b/packages/vault/src/aum_provider/base_aum_provider.cairo new file mode 100644 index 00000000..6079553e --- /dev/null +++ b/packages/vault/src/aum_provider/base_aum_provider.cairo @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod BaseAumProviderComponent { + use core::num::traits::Zero; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use vault::aum_provider::errors::Errors; + use vault::aum_provider::interface::IBaseAumProvider; + use vault::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; + #[storage] + pub struct Storage { + pub vault: IVaultDispatcher, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + + pub trait AumTrait> { + fn get_aum(self: @ComponentState) -> u256; + } + + + #[embeddable_as(BaseAumProviderImpl)] + impl BaseAumProvider< + TContractState, +HasComponent, impl Aum: AumTrait, + > of IBaseAumProvider> { + fn aum(self: @ComponentState) -> u256 { + Aum::get_aum(self) + } + + fn report_aum(self: @ComponentState) { + self.vault.read().report(Aum::get_aum(self)); + } + } + + #[generate_trait] + pub impl InternalImpl< + TContractState, +HasComponent, + > of InternalTrait { + fn initializer(ref self: ComponentState, vault: ContractAddress) { + if (vault.is_zero()) { + Errors::invalid_vault_address(); + } + self.vault.write(IVaultDispatcher { contract_address: vault }); + } + } +} diff --git a/packages/vault/src/aum_provider/errors.cairo b/packages/vault/src/aum_provider/errors.cairo new file mode 100644 index 00000000..a7c84ac7 --- /dev/null +++ b/packages/vault/src/aum_provider/errors.cairo @@ -0,0 +1,9 @@ +// 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 invalid_vault_address() { + panic!("Invalid vault address"); + } +} diff --git a/packages/vault/src/aum_provider/interface.cairo b/packages/vault/src/aum_provider/interface.cairo new file mode 100644 index 00000000..06df30d5 --- /dev/null +++ b/packages/vault/src/aum_provider/interface.cairo @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::interface] +pub trait IBaseAumProvider { + fn aum(self: @T) -> u256; + fn report_aum(self: @T); +} diff --git a/packages/vault/src/lib.cairo b/packages/vault/src/lib.cairo index 9b6d63a5..12aa3603 100644 --- a/packages/vault/src/lib.cairo +++ b/packages/vault/src/lib.cairo @@ -8,6 +8,17 @@ pub mod vault { pub mod vault; } +pub mod aum_provider { + pub mod base_aum_provider; + pub mod errors; + pub mod interface; + pub mod aum_provider_4626 { + pub mod aum_provider_4626; + pub mod errors; + pub mod interface; + } +} + pub mod redeem_request { pub mod errors; diff --git a/scripts/configs/config.json b/scripts/configs/config.json index 359462ee..90e0e7f1 100644 --- a/scripts/configs/config.json +++ b/scripts/configs/config.json @@ -11,7 +11,8 @@ "AvnuMiddleware": "0x142dff46d17eb94e9c77a771968ccd8257d8eed5d6a84c3e37f50e15dc39a9f", "Manager": "0x67a612fc0c9f0d9a84e425cd7e531e408fa3234fa5e1050eef1905b4ac35a61", "PriceRouter": "0x677bd52be1ce091b133c5bd637cec20df7f1390adfa64de98791e2928d5d3c1", - "SimpleDecoderAndSanitizer": "0x5e9c41a7fd764459fc91f3712a7bb3e31a02ed97cf4954b6dd84feb5034e6bc" + "SimpleDecoderAndSanitizer": "0x5e9c41a7fd764459fc91f3712a7bb3e31a02ed97cf4954b6dd84feb5034e6bc", + "AumProvider4626": "0x45852151d30200834f14c523037ae354bdf7e794af2859fe8ac325320c54be5" }, "periphery": { "vesuSingleton": "0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160", diff --git a/scripts/declareContract.ts b/scripts/declareContract.ts index af36d14f..09ed82fc 100644 --- a/scripts/declareContract.ts +++ b/scripts/declareContract.ts @@ -7,6 +7,7 @@ import { getNetworkEnv } from "./utils"; dotenv.config({ path: __dirname + "/../.env" }); const provider = new RpcProvider({ nodeUrl: process.env.RPC }); + const owner = new Account( provider, process.env.ACCOUNT_ADDRESS as string, @@ -92,6 +93,9 @@ async function main() { "SimpleDecoderAndSanitizer" ); break; + case "AumProvider4626": + await declareContract(envNetwork, "vault", "AumProvider4626"); + break; default: throw new Error("Error: Unknown contract"); } diff --git a/scripts/deployContract.ts b/scripts/deployContract.ts index 36814fda..9897ad9c 100644 --- a/scripts/deployContract.ts +++ b/scripts/deployContract.ts @@ -1,9 +1,4 @@ -import { - Account, - RpcProvider, - CallData, - CairoUint256, -} from "starknet"; +import { Account, RpcProvider, CallData, CairoUint256 } from "starknet"; import dotenv from "dotenv"; import { readConfigs } from "./configs/utils"; import { getNetworkEnv } from "./utils"; @@ -79,9 +74,7 @@ export async function deploySimpleDecoderAndSanitizer(envNetwork: string) { return await deployContract(envNetwork, "SimpleDecoderAndSanitizer", []); } -export async function deployPriceRouter( - envNetwork: string -) { +export async function deployPriceRouter(envNetwork: string) { const config = readConfigs(); const networkConfig = config[envNetwork]; if (!networkConfig) { @@ -137,6 +130,17 @@ export async function deployAvnuMiddleware( ]); } +export async function deployAumProvider4626( + envNetwork: string, + vaultAddress: string, + strategy4626Address: string +) { + return await deployContract(envNetwork, "AumProvider4626", [ + vaultAddress, + strategy4626Address, + ]); +} + function validateSlippageTolerancePercentage(slippage: string): number { const num = parseFloat(slippage); if (isNaN(num) || num < 0 || num > 100) { @@ -165,6 +169,15 @@ function parseArguments(contractName: string, args: string[]) { slippageToleranceBps: validateSlippageTolerancePercentage(args[0]), }; + case "AumProvider4626": + if (args.length < 2) { + throw new Error("AumProvider4626 requires: "); + } + return { + vaultAddress: args[0], + strategy4626Address: args[1], + }; + default: throw new Error(`Unknown contract: ${contractName}`); } @@ -183,15 +196,17 @@ async function main() { console.log(" Usage: --contract SimpleDecoderAndSanitizer"); console.log(" - PriceRouter"); console.log(" Usage: --contract PriceRouter"); - console.log( - " - AvnuMiddleware " - ); + console.log(" - AvnuMiddleware "); console.log( " Usage: --contract AvnuMiddleware " ); console.log( " Note: slippage_tolerance_percentage should be between 0-100% (e.g., 2.5 for 2.5%)" ); + console.log(" - AumProvider4626 "); + console.log( + " Usage: --contract AumProvider4626 " + ); return; } @@ -221,6 +236,14 @@ async function main() { ); break; + case "AumProvider4626": + deployedAddress = await deployAumProvider4626( + envNetwork, + parsedArgs.vaultAddress as string, + parsedArgs.strategy4626Address as string + ); + break; + default: throw new Error(`Unknown contract: ${contractName}`); } diff --git a/scripts/package.json b/scripts/package.json index 18a11992..16154627 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -13,14 +13,16 @@ "declare:manager": "tsx declareContract.ts --contract Manager", "declare:price-router": "tsx declareContract.ts --contract PriceRouter", "declare:decoder-sanitizer": "tsx declareContract.ts --contract SimpleDecoderAndSanitizer", + "declare:aum-provider-4626": "tsx declareContract.ts --contract AumProvider4626", "deploy": "tsx deployContract.ts --contract", + "deploy:contract": "tsx deployContract.ts", "deploy:vault": "tsx deployVault.ts", "vault:config": "tsx vaultConfig.ts", "manager:config": "tsx managerConfig.ts" }, "dependencies": { - "starknet": "^6.11.0", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "starknet": "7.6.4" }, "devDependencies": { "@types/node": "^22.5.4", diff --git a/scripts/pnpm-lock.yaml b/scripts/pnpm-lock.yaml index 4d0f7eff..58ba7a08 100644 --- a/scripts/pnpm-lock.yaml +++ b/scripts/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^16.4.5 version: 16.6.1 starknet: - specifier: ^6.11.0 - version: 6.24.1 + specifier: 7.6.4 + version: 7.6.4 devDependencies: '@types/node': specifier: ^22.5.4 @@ -200,6 +200,9 @@ packages: '@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==} + '@types/node@22.18.0': resolution: {integrity: sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==} @@ -254,9 +257,6 @@ packages: engines: {node: '>=4'} hasBin: true - fetch-cookie@3.0.1: - resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} - fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -280,37 +280,15 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - isomorphic-fetch@3.0.0: - resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} - jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} lossless-json@4.1.1: resolution: {integrity: sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==} - 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 - pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} @@ -318,17 +296,12 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - - starknet@6.24.1: - resolution: {integrity: sha512-g7tiCt73berhcNi41otlN3T3kxZnIvZhMi8WdC21Y6GC6zoQgbI2z1t7JAZF9c4xZiomlanwVnurcpyfEdyMpg==} + starknet@7.6.4: + resolution: {integrity: sha512-FB20IaLCDbh/XomkB+19f5jmNxG+RzNdRO7QUhm7nfH81UPIt2C/MyWAlHCYkbv2wznSEb73wpxbp9tytokTgQ==} + engines: {node: '>=22'} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -338,13 +311,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} @@ -361,26 +327,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-fetch@3.6.20: - resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -492,6 +442,8 @@ snapshots: '@starknet-io/types-js@0.7.10': {} + '@starknet-io/types-js@0.8.4': {} + '@types/node@22.18.0': dependencies: undici-types: 6.21.0 @@ -565,11 +517,6 @@ snapshots: esprima@4.0.1: {} - fetch-cookie@3.0.1: - dependencies: - set-cookie-parser: 2.7.1 - tough-cookie: 4.1.4 - fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -589,13 +536,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - isomorphic-fetch@3.0.0: - dependencies: - node-fetch: 2.7.0 - whatwg-fetch: 3.6.20 - transitivePeerDependencies: - - encoding - jsonfile@6.2.0: dependencies: universalify: 2.0.1 @@ -604,47 +544,28 @@ snapshots: lossless-json@4.1.1: {} - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - pako@2.1.0: {} - psl@1.15.0: - dependencies: - punycode: 2.3.1 - - punycode@2.3.1: {} - - querystringify@2.2.0: {} - redeyed@2.1.1: dependencies: esprima: 4.0.1 require-directory@2.1.1: {} - requires-port@1.0.0: {} - resolve-pkg-maps@1.0.0: {} - set-cookie-parser@2.7.1: {} - - starknet@6.24.1: + starknet@7.6.4: 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-07': '@starknet-io/types-js@0.7.10' + '@starknet-io/starknet-types-08': '@starknet-io/types-js@0.8.4' abi-wan-kanabi: 2.2.4 - fetch-cookie: 3.0.1 - isomorphic-fetch: 3.0.0 lossless-json: 4.1.1 pako: 2.1.0 - starknet-types-07: '@starknet-io/types-js@0.7.10' ts-mixer: 6.0.4 - transitivePeerDependencies: - - encoding string-width@4.2.3: dependencies: @@ -656,15 +577,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - tough-cookie@4.1.4: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 - - tr46@0.0.3: {} - ts-mixer@6.0.4: {} tsx@4.20.5: @@ -678,24 +590,8 @@ snapshots: undici-types@6.21.0: {} - universalify@0.2.0: {} - universalify@2.0.1: {} - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - - webidl-conversions@3.0.1: {} - - whatwg-fetch@3.6.20: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 diff --git a/scripts/vaultConfig.ts b/scripts/vaultConfig.ts index 05289dce..24833203 100644 --- a/scripts/vaultConfig.ts +++ b/scripts/vaultConfig.ts @@ -106,9 +106,10 @@ async function showConfigMenu(): Promise { console.log("1. Set Fees Configuration"); console.log("2. Set Report Delay"); console.log("3. Set Max Delta"); - console.log("4. Exit"); + console.log("4. Grant Oracle Role"); + console.log("5. Exit"); - const choice = await askQuestion("\nSelect an option (1-4): "); + const choice = await askQuestion("\nSelect an option (1-5): "); return choice.trim(); } @@ -251,6 +252,43 @@ async function setMaxDelta(vaultAddress: string): Promise { } } +async function grantOracleRole(vaultAddress: string): Promise { + console.log("\n🔮 Grant Oracle Role"); + console.log("This will grant the ORACLE_ROLE to a specified account."); + + const oracleAccount = await askQuestion("Oracle account address: "); + try { + validateAndParseAddress(oracleAccount); + } catch (error) { + throw new Error(`Invalid oracle account address: ${oracleAccount}`); + } + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log(` Oracle Account: ${oracleAccount}`); + console.log(` Role: ORACLE_ROLE`); + + const confirm = await askQuestion("\nConfirm granting oracle role? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Operation cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "grant_role", + calldata: ["ORACLE_ROLE", oracleAccount], + }); + + console.log("✅ Oracle role granted successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error granting oracle role:", error); + throw error; + } +} + async function main() { try { const envNetwork = await getNetworkEnv(provider); @@ -276,11 +314,15 @@ async function main() { break; case "4": + await grantOracleRole(vaultAddress); + break; + + case "5": console.log("👋 Goodbye!"); return; default: - console.log("❌ Invalid choice. Please select 1-4."); + console.log("❌ Invalid choice. Please select 1-5."); break; } From 1f45b2fbf929da9c79e8def12eeed1846f89c2e5 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:28:36 +0200 Subject: [PATCH 17/54] Update AUM provider interfaces and implementations - Enhance base AUM provider with improved interface definitions - Update 4626 AUM provider implementation - Refactor AUM provider interface for better compatibility --- .../aum_provider_4626/aum_provider_4626.cairo | 13 ++++++++++--- .../vault/src/aum_provider/base_aum_provider.cairo | 4 ++-- packages/vault/src/aum_provider/interface.cairo | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo b/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo index f47012a5..88bb8011 100644 --- a/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo +++ b/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo @@ -12,6 +12,7 @@ pub mod AumProvider4626 { use vault::aum_provider::aum_provider_4626::errors::Errors; use vault::aum_provider::aum_provider_4626::interface::IAumProvider4626; use vault::aum_provider::base_aum_provider::BaseAumProviderComponent; + use vault::vault::interface::IVaultDispatcherTrait; component!( path: BaseAumProviderComponent, storage: base_aum_provider, event: BaseAumProviderEvent, ); @@ -56,12 +57,18 @@ pub mod AumProvider4626 { fn get_aum(self: @BaseAumProviderComponent::ComponentState) -> u256 { let contract_state = self.get_contract(); let strategy = contract_state.strategy4626.read(); - let vault_address = self.vault.read().contract_address; + let vault_allocator_address = contract_state + .base_aum_provider + .vault + .read() + .vault_allocator(); + + // .vault.vault_allocator(); let underlying_asset = strategy.asset(); let underlying_dispatcher = ERC20ABIDispatcher { contract_address: underlying_asset }; - let underlying_balance = underlying_dispatcher.balance_of(vault_address); + let underlying_balance = underlying_dispatcher.balance_of(vault_allocator_address); let strategy_shares = ERC20ABIDispatcher { contract_address: strategy.contract_address } - .balance_of(vault_address); + .balance_of(vault_allocator_address); let strategy_assets = if strategy_shares > 0 { strategy.convert_to_assets(strategy_shares) } else { diff --git a/packages/vault/src/aum_provider/base_aum_provider.cairo b/packages/vault/src/aum_provider/base_aum_provider.cairo index 6079553e..6ebf107d 100644 --- a/packages/vault/src/aum_provider/base_aum_provider.cairo +++ b/packages/vault/src/aum_provider/base_aum_provider.cairo @@ -33,8 +33,8 @@ pub mod BaseAumProviderComponent { Aum::get_aum(self) } - fn report_aum(self: @ComponentState) { - self.vault.read().report(Aum::get_aum(self)); + fn report(ref self: ComponentState) { + self.vault.read().report(Aum::get_aum(@self)); } } diff --git a/packages/vault/src/aum_provider/interface.cairo b/packages/vault/src/aum_provider/interface.cairo index 06df30d5..6f218f13 100644 --- a/packages/vault/src/aum_provider/interface.cairo +++ b/packages/vault/src/aum_provider/interface.cairo @@ -5,5 +5,5 @@ #[starknet::interface] pub trait IBaseAumProvider { fn aum(self: @T) -> u256; - fn report_aum(self: @T); + fn report(ref self: T); } From 511fdde3efe3ed01a183fcb7161409e9f5d480ed Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:28:42 +0200 Subject: [PATCH 18/54] Update deployment scripts and configuration - Update vault configuration with new settings - Improve script dependencies and configuration management - Enhance deployment configuration structure --- scripts/configs/config.json | 2 +- scripts/package.json | 3 +-- scripts/vaultConfig.ts | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/configs/config.json b/scripts/configs/config.json index 90e0e7f1..22cfe6cf 100644 --- a/scripts/configs/config.json +++ b/scripts/configs/config.json @@ -12,7 +12,7 @@ "Manager": "0x67a612fc0c9f0d9a84e425cd7e531e408fa3234fa5e1050eef1905b4ac35a61", "PriceRouter": "0x677bd52be1ce091b133c5bd637cec20df7f1390adfa64de98791e2928d5d3c1", "SimpleDecoderAndSanitizer": "0x5e9c41a7fd764459fc91f3712a7bb3e31a02ed97cf4954b6dd84feb5034e6bc", - "AumProvider4626": "0x45852151d30200834f14c523037ae354bdf7e794af2859fe8ac325320c54be5" + "AumProvider4626": "0x21aa3fcc7be5d6078d8551838e297e5e29f7e594c2f1338e3a27dbb11e127eb" }, "periphery": { "vesuSingleton": "0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160", diff --git a/scripts/package.json b/scripts/package.json index 16154627..1bef18d2 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -14,8 +14,7 @@ "declare:price-router": "tsx declareContract.ts --contract PriceRouter", "declare:decoder-sanitizer": "tsx declareContract.ts --contract SimpleDecoderAndSanitizer", "declare:aum-provider-4626": "tsx declareContract.ts --contract AumProvider4626", - "deploy": "tsx deployContract.ts --contract", - "deploy:contract": "tsx deployContract.ts", + "deploy:contract": "tsx deployContract.ts --contract", "deploy:vault": "tsx deployVault.ts", "vault:config": "tsx vaultConfig.ts", "manager:config": "tsx managerConfig.ts" diff --git a/scripts/vaultConfig.ts b/scripts/vaultConfig.ts index 24833203..82eea705 100644 --- a/scripts/vaultConfig.ts +++ b/scripts/vaultConfig.ts @@ -3,6 +3,7 @@ import { CairoUint256, RpcProvider, validateAndParseAddress, + hash, } from "starknet"; import dotenv from "dotenv"; import { getNetworkEnv, WAD } from "./utils"; @@ -278,7 +279,7 @@ async function grantOracleRole(vaultAddress: string): Promise { const response = await owner.execute({ contractAddress: vaultAddress, entrypoint: "grant_role", - calldata: ["ORACLE_ROLE", oracleAccount], + calldata: [hash.starknetKeccak("ORACLE_ROLE").toString(), oracleAccount], }); console.log("✅ Oracle role granted successfully!"); From efab21c5125a1df227ce47e77a623f9376f2eace Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:28:47 +0200 Subject: [PATCH 19/54] Add new SDK implementation - Implement client SDK for vault interactions - Add comprehensive API client functionality - Include type definitions and utilities for external integrations --- sdk/.gitignore | 3 + sdk/README.md | 286 ++++++ sdk/examples/user-example.ts | 504 ++++++++++ sdk/package.json | 46 + sdk/src/abi/vault.json | 1581 ++++++++++++++++++++++++++++++ sdk/src/abi/vault_allocator.json | 294 ++++++ sdk/src/curator/index.ts | 1 + sdk/src/index.ts | 23 + sdk/src/types/index.ts | 63 ++ sdk/src/user/index.ts | 324 ++++++ sdk/src/utils/calldata.ts | 226 +++++ sdk/tsconfig.json | 25 + 12 files changed, 3376 insertions(+) create mode 100644 sdk/.gitignore create mode 100644 sdk/README.md create mode 100644 sdk/examples/user-example.ts create mode 100644 sdk/package.json create mode 100644 sdk/src/abi/vault.json create mode 100644 sdk/src/abi/vault_allocator.json create mode 100644 sdk/src/curator/index.ts create mode 100644 sdk/src/index.ts create mode 100644 sdk/src/types/index.ts create mode 100644 sdk/src/user/index.ts create mode 100644 sdk/src/utils/calldata.ts create mode 100644 sdk/tsconfig.json diff --git a/sdk/.gitignore b/sdk/.gitignore new file mode 100644 index 00000000..bdfa6b81 --- /dev/null +++ b/sdk/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules +dist \ No newline at end of file diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 00000000..f90f19ad --- /dev/null +++ b/sdk/README.md @@ -0,0 +1,286 @@ +# Starknet Vault Kit SDK + +TypeScript SDK for interacting with Starknet Vault Kit contracts. Provides easy-to-use interfaces for both vault users and curators to interact with ERC-4626 compatible vaults with epoched redemption systems. + +## Features + +- **User Operations**: Deposit, mint, request redemptions, and claim redemptions +- **Curator Operations**: Report AUM, manage liquidity, configure fees, and pause/unpause +- **Calldata Generation**: Generate transaction calldata for all operations +- **State Queries**: Read vault state, balances, fees, and redemption information +- **Type Safety**: Full TypeScript support with proper types + +## Installation + +```bash +npm install @starknet-vault-kit/sdk +# or +yarn add @starknet-vault-kit/sdk +``` + +## Quick Start + +### User Operations + +```typescript +import { VaultUserSDK, VaultConfig } from '@starknet-vault-kit/sdk'; +import { RpcProvider } from 'starknet'; + +// Configure vault - only vault address is required +const vaultConfig: VaultConfig = { + vaultAddress: "0x...", +}; + +// Initialize SDK with provider +const provider = new RpcProvider({ nodeUrl: "https://starknet-mainnet.public.blastapi.io" }); +const userSDK = new VaultUserSDK(vaultConfig, provider); + +// Generate deposit calldata +const depositCalldata = userSDK.buildDepositCalldata({ + assets: "1000000", // 1 USDC (6 decimals) + receiver: "0x..." +}); + +// Generate deposit calldata WITH approval (async) +const depositWithApproval = await userSDK.buildDepositCalldataWithApproval({ + assets: "1000000", + receiver: "0x...", + includeApprove: true +}); +// Returns { transactions: [approveCalldata, depositCalldata] } + +// Get vault state +const vaultState = await userSDK.getVaultState(); +console.log("Current epoch:", vaultState.epoch); + +// Preview deposit +const expectedShares = await userSDK.previewDeposit("1000000"); +console.log("Expected shares:", expectedShares); +``` + +### Curator Operations + +```typescript +import { VaultCuratorSDK } from '@starknet-vault-kit/sdk'; + +const curatorSDK = new VaultCuratorSDK(vaultConfig, provider); + +// Generate report calldata +const reportCalldata = curatorSDK.buildReportCalldata({ + newAum: "5000000000" // New AUM value +}); + +// Check if report can be made +const canReport = await curatorSDK.canReport(); +console.log("Can report:", canReport); + +// Get pending redemption requirements +const pendingRedemptions = await curatorSDK.getPendingRedemptionRequirements(); +console.log("Total pending assets:", pendingRedemptions.totalPendingAssets); +``` + +## API Reference + +### VaultUserSDK + +#### Calldata Generation + +- `buildDepositCalldata(params)` - Generate deposit transaction calldata +- `buildDepositCalldataWithApproval(params)` - Generate deposit with approval (async) +- `buildMintCalldata(params)` - Generate mint transaction calldata +- `buildMintCalldataWithApproval(params)` - Generate mint with approval (async) +- `buildRequestRedeemCalldata(params)` - Generate redeem request calldata +- `buildClaimRedeemCalldata(params)` - Generate claim redemption calldata + +#### View Methods + +- `getVaultState()` - Get current vault state (epoch, buffer, AUM, etc.) +- `getUserShareBalance(address)` - Get user's share balance +- `previewDeposit(assets)` - Preview shares received for deposit +- `previewMint(shares)` - Preview assets needed for mint +- `previewRedeem(shares)` - Preview assets received for redemption +- `getDueAssetsFromId(id)` - Get expected assets for redemption NFT +- `convertToShares(assets)` - Convert assets to shares +- `convertToAssets(shares)` - Convert shares to assets +- `getUnderlyingAssetAddress()` - Get underlying asset contract address +- `getRedeemRequestAddress()` - Get redeem request NFT contract address + +### VaultCuratorSDK + +#### Calldata Generation + +- `buildReportCalldata(params)` - Generate AUM report calldata +- `buildBringLiquidityCalldata(params)` - Generate bring liquidity calldata +- `buildPauseCalldata()` - Generate pause calldata +- `buildUnpauseCalldata()` - Generate unpause calldata +- `buildSetFeesConfigCalldata(...)` - Generate fee configuration calldata +- `buildSetReportDelayCalldata(delay)` - Generate report delay calldata +- `buildSetMaxDeltaCalldata(delta)` - Generate max delta calldata + +#### View Methods + +- `getFeesConfig()` - Get current fee configuration +- `getReportDelay()` - Get minimum report delay +- `getMaxDelta()` - Get maximum AUM delta per report +- `getLastReportTimestamp()` - Get last report timestamp +- `canReport()` - Check if report can be made +- `getTimeUntilNextReport()` - Get time until next report allowed +- `getPendingRedemptionRequirements()` - Get pending redemption info +- `isPaused()` - Check if vault is paused +- `getCurrentEpoch()` - Get current epoch +- `getBuffer()` - Get current buffer amount +- `getAum()` - Get current AUM + +## Types + +### VaultConfig +```typescript +interface VaultConfig { + vaultAddress: string; // Only vault address is required - other addresses are fetched automatically +} +``` + +### CalldataResult +```typescript +interface CalldataResult { + contractAddress: string; + entrypoint: string; + calldata: string[]; +} +``` + +### MultiCalldataResult +```typescript +interface MultiCalldataResult { + transactions: CalldataResult[]; // Array of transactions to execute in order +} +``` + +### VaultState +```typescript +interface VaultState { + epoch: bigint; + handledEpochLen: bigint; + buffer: bigint; + aum: bigint; + totalSupply: bigint; + totalAssets: bigint; +} +``` + +## Examples + +### Complete User Flow + +```typescript +import { VaultUserSDK, VaultConfig } from '@starknet-vault-kit/sdk'; +import { Account, RpcProvider } from 'starknet'; + +const vaultConfig: VaultConfig = { + vaultAddress: "0x123...", +}; + +const provider = new RpcProvider({ nodeUrl: "your-node-url" }); +const account = new Account(provider, "0x...", "your-private-key"); +const userSDK = new VaultUserSDK(vaultConfig, provider); + +// 1. Check current vault state +const vaultState = await userSDK.getVaultState(); +console.log("Vault epoch:", vaultState.epoch); + +// 2. Preview deposit +const assetsToDeposit = "1000000"; // 1 USDC +const expectedShares = await userSDK.previewDeposit(assetsToDeposit); +console.log("Expected shares:", expectedShares); + +// 3. Generate and execute deposit +const depositCalldata = userSDK.buildDepositCalldata({ + assets: assetsToDeposit, + receiver: account.address +}); + +const tx = await account.execute([{ + contractAddress: depositCalldata.contractAddress, + entrypoint: depositCalldata.entrypoint, + calldata: depositCalldata.calldata +}]); + +console.log("Deposit tx:", tx.transaction_hash); + +// Alternative: Deposit with approval in one transaction batch +const depositWithApprovalCalldata = await userSDK.buildDepositCalldataWithApproval({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true +}); + +const approvalTx = await account.execute( + depositWithApprovalCalldata.transactions.map(tx => ({ + contractAddress: tx.contractAddress, + entrypoint: tx.entrypoint, + calldata: tx.calldata + })) +); + +console.log("Approval + Deposit tx:", approvalTx.transaction_hash); + +// 4. Later, request redemption +const userShares = await userSDK.getUserShareBalance(account.address); +const redeemCalldata = userSDK.buildRequestRedeemCalldata({ + shares: userShares / 2n, // Redeem half + receiver: account.address, + owner: account.address +}); + +const redeemTx = await account.execute([{ + contractAddress: redeemCalldata.contractAddress, + entrypoint: redeemCalldata.entrypoint, + calldata: redeemCalldata.calldata +}]); + +console.log("Redeem request tx:", redeemTx.transaction_hash); +``` + +### Curator Operations + +```typescript +import { VaultCuratorSDK } from '@starknet-vault-kit/sdk'; + +const curatorSDK = new VaultCuratorSDK(vaultConfig, provider); + +// Check report status +const canReport = await curatorSDK.canReport(); +if (!canReport) { + const timeUntilReport = await curatorSDK.getTimeUntilNextReport(); + console.log(`Must wait ${timeUntilReport} seconds before next report`); + return; +} + +// Get current state for report +const [currentAum, buffer, pendingRedemptions] = await Promise.all([ + curatorSDK.getAum(), + curatorSDK.getBuffer(), + curatorSDK.getPendingRedemptionRequirements() +]); + +console.log("Current AUM:", currentAum); +console.log("Buffer:", buffer); +console.log("Pending redemptions:", pendingRedemptions.totalPendingAssets); + +// Generate report with new AUM +const newAum = "5500000000"; // Updated AUM from strategy +const reportCalldata = curatorSDK.buildReportCalldata({ newAum }); + +// Execute report +const reportTx = await curatorAccount.execute([{ + contractAddress: reportCalldata.contractAddress, + entrypoint: reportCalldata.entrypoint, + calldata: reportCalldata.calldata +}]); + +console.log("Report tx:", reportTx.transaction_hash); +``` + +## License + +MIT \ No newline at end of file diff --git a/sdk/examples/user-example.ts b/sdk/examples/user-example.ts new file mode 100644 index 00000000..2a570ab3 --- /dev/null +++ b/sdk/examples/user-example.ts @@ -0,0 +1,504 @@ +/** + * Example usage of VaultUserSDK + * Demonstrates how to interact with the vault as a regular user + */ + +import dotenv from "dotenv"; +import { VaultUserSDK, VaultConfig } from "../src"; +import { Account, RpcProvider } from "starknet"; +import * as readline from "readline"; + +dotenv.config(); + +const vaultConfig: VaultConfig = { + vaultAddress: + "0x006ec49bb6bbd9262423e5948159f2a6fcd6ffb0a5a8492d2eff0ee13d020b6c", +}; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function question(query: string): Promise { + return new Promise((resolve) => rl.question(query, resolve)); +} + +async function showMenu(): Promise { + console.log("\n=== Vault User Operations Menu ==="); + console.log("1. Get vault state"); + console.log("2. Get user balance"); + console.log("3. Preview deposit"); + console.log("4. Generate deposit calldata"); + console.log("5. Generate deposit calldata with approval"); + console.log("6. Execute deposit"); + console.log("7. Preview mint"); + console.log("8. Generate mint calldata"); + console.log("9. Generate mint calldata with approval"); + console.log("10. Execute mint"); + console.log("11. Preview redeem"); + console.log("12. Generate request redeem calldata"); + console.log("13. Execute request redeem"); + console.log("14. Generate claim redeem calldata"); + console.log("15. Execute claim redeem"); + console.log("16. Check due assets for NFT"); + console.log("17. Test conversion utilities"); + console.log("0. Exit"); + console.log("====================================="); + + const choice = await question("Enter your choice (0-17): "); + return choice.trim(); +} + +async function handleGetVaultState(userSDK: VaultUserSDK): Promise { + try { + console.log("\n--- Getting vault state ---"); + const vaultState = await userSDK.getVaultState(); + console.log(`Current epoch: ${vaultState.epoch}`); + console.log(`Total supply: ${vaultState.totalSupply}`); + console.log(`Total assets: ${vaultState.totalAssets}`); + console.log(`Buffer: ${vaultState.buffer}`); + console.log(`AUM: ${vaultState.aum}`); + } catch (error) { + console.error("Error getting vault state:", error); + } +} + +async function handleGetUserBalance( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + console.log("\n--- Getting user balance ---"); + const userBalance = await userSDK.getUserShareBalance(account.address); + console.log(`User share balance: ${userBalance}`); + } catch (error) { + console.error("Error getting user balance:", error); + } +} + +async function handlePreviewDeposit(userSDK: VaultUserSDK): Promise { + try { + const assetsInput = await question( + "Enter assets to deposit (e.g., 1000000 for 1 USDC): " + ); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Previewing deposit ---"); + console.log(`Assets to deposit: ${assetsToDeposit}`); + const expectedShares = await userSDK.previewDeposit(assetsToDeposit); + console.log(`Expected shares: ${expectedShares}`); + } catch (error) { + console.error("Error previewing deposit:", error); + } +} + +async function handleGenerateDepositCalldata( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const assetsInput = await question("Enter assets to deposit: "); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Generating deposit calldata ---"); + const depositCalldata = userSDK.buildDepositCalldata({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true, + }); + console.log(`Deposit calldata: ${depositCalldata}`); + } catch (error) { + console.error("Error generating deposit calldata:", error); + } +} + +async function handleGenerateDepositCalldataWithApproval( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const assetsInput = await question("Enter assets to deposit: "); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Generating deposit calldata with approval ---"); + const depositWithApprovalCalldata = + await userSDK.buildDepositCalldataWithApproval({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true, + }); + console.log( + `Number of transactions: ${depositWithApprovalCalldata.transactions.length}` + ); + console.log( + `Transaction 1 (Approve): ${depositWithApprovalCalldata.transactions[0].entrypoint}` + ); + console.log( + `Transaction 2 (Deposit): ${depositWithApprovalCalldata.transactions[1].entrypoint}` + ); + } catch (error) { + console.error("Error generating deposit calldata with approval:", error); + } +} + +async function handleExecuteDeposit( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const assetsInput = await question("Enter assets to deposit: "); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Executing deposit ---"); + console.log(`Assets to deposit: ${assetsToDeposit}`); + + const depositWithApprovalCalldata = + await userSDK.buildDepositCalldataWithApproval({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true, + }); + + console.log("Executing transaction..."); + const result = await account.execute( + depositWithApprovalCalldata.transactions + ); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Deposit executed successfully!"); + } catch (error) { + console.error("Error executing deposit:", error); + } +} + +async function handlePreviewMint(userSDK: VaultUserSDK): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Previewing mint ---"); + console.log(`Shares to mint: ${sharesToMint}`); + const expectedAssets = await userSDK.previewMint(sharesToMint); + console.log(`Expected assets required: ${expectedAssets}`); + } catch (error) { + console.error("Error previewing mint:", error); + } +} + +async function handleGenerateMintCalldata( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Generating mint calldata ---"); + const mintCalldata = userSDK.buildMintCalldata({ + shares: sharesToMint, + receiver: account.address, + }); + console.log(`Mint calldata: ${mintCalldata}`); + } catch (error) { + console.error("Error generating mint calldata:", error); + } +} + +async function handleGenerateMintCalldataWithApproval( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Generating mint calldata with approval ---"); + const mintWithApprovalCalldata = + await userSDK.buildMintCalldataWithApproval({ + shares: sharesToMint, + receiver: account.address, + includeApprove: true, + }); + console.log( + `Number of transactions: ${mintWithApprovalCalldata.transactions.length}` + ); + console.log( + `Transaction 1 (Approve): ${mintWithApprovalCalldata.transactions[0].entrypoint}` + ); + console.log( + `Transaction 2 (Mint): ${mintWithApprovalCalldata.transactions[1].entrypoint}` + ); + } catch (error) { + console.error("Error generating mint calldata with approval:", error); + } +} + +async function handleExecuteMint( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Executing mint ---"); + console.log(`Shares to mint: ${sharesToMint}`); + + const mintWithApprovalCalldata = + await userSDK.buildMintCalldataWithApproval({ + shares: sharesToMint, + receiver: account.address, + includeApprove: true, + }); + + console.log("Executing transaction..."); + const result = await account.execute(mintWithApprovalCalldata.transactions); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Mint executed successfully!"); + } catch (error) { + console.error("Error executing mint:", error); + } +} + +async function handlePreviewRedeem(userSDK: VaultUserSDK): Promise { + try { + const sharesInput = await question("Enter shares to redeem: "); + const sharesToRedeem = sharesInput.trim(); + + console.log("\n--- Previewing redeem ---"); + console.log(`Shares to redeem: ${sharesToRedeem}`); + const expectedAssets = await userSDK.previewRedeem(sharesToRedeem); + console.log(`Expected assets: ${expectedAssets}`); + } catch (error) { + console.error("Error previewing redeem:", error); + } +} + +async function handleGenerateRequestRedeemCalldata( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to redeem: "); + const sharesToRedeem = sharesInput.trim(); + + console.log("\n--- Generating request redeem calldata ---"); + const redeemCalldata = userSDK.buildRequestRedeemCalldata({ + shares: sharesToRedeem, + receiver: account.address, + owner: account.address, + }); + console.log(`Request redeem calldata: ${redeemCalldata}`); + } catch (error) { + console.error("Error generating request redeem calldata:", error); + } +} + +async function handleExecuteRequestRedeem( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to redeem: "); + const sharesToRedeem = sharesInput.trim(); + + console.log("\n--- Executing request redeem ---"); + console.log(`Shares to redeem: ${sharesToRedeem}`); + + const redeemCalldata = userSDK.buildRequestRedeemCalldata({ + shares: sharesToRedeem, + receiver: account.address, + owner: account.address, + }); + + console.log("Executing transaction..."); + const result = await account.execute(redeemCalldata); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Request redeem executed successfully!"); + } catch (error) { + console.error("Error executing request redeem:", error); + } +} + +async function handleGenerateClaimRedeemCalldata( + userSDK: VaultUserSDK +): Promise { + try { + const nftIdInput = await question("Enter NFT ID to claim: "); + const nftId = nftIdInput.trim(); + + console.log("\n--- Generating claim redeem calldata ---"); + const claimCalldata = userSDK.buildClaimRedeemCalldata({ + id: nftId, + }); + console.log(`NFT ID: ${nftId}`); + console.log(`Claim redeem calldata: ${claimCalldata}`); + } catch (error) { + console.error("Error generating claim redeem calldata:", error); + } +} + +async function handleExecuteClaimRedeem( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const nftIdInput = await question("Enter NFT ID to claim: "); + const nftId = nftIdInput.trim(); + + console.log("\n--- Executing claim redeem ---"); + console.log(`NFT ID to claim: ${nftId}`); + + const claimCalldata = userSDK.buildClaimRedeemCalldata({ + id: nftId, + }); + + console.log("Executing transaction..."); + const result = await account.execute(claimCalldata); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Claim redeem executed successfully!"); + } catch (error) { + console.error("Error executing claim redeem:", error); + } +} + +async function handleCheckDueAssets(userSDK: VaultUserSDK): Promise { + try { + const nftIdInput = await question("Enter NFT ID to check: "); + const nftId = nftIdInput.trim(); + + console.log("\n--- Checking due assets for NFT ---"); + const dueAssets = await userSDK.getDueAssetsFromId(nftId); + console.log(`Due assets for NFT ${nftId}: ${dueAssets}`); + } catch (error) { + console.error(`Error checking due assets: ${error}`); + } +} + +async function handleConversionUtilities(userSDK: VaultUserSDK): Promise { + try { + const assetsInput = await question("Enter assets amount to convert: "); + const testAssets = assetsInput.trim(); + + console.log("\n--- Testing conversion utilities ---"); + const convertedShares = await userSDK.convertToShares(testAssets); + const convertedBackToAssets = await userSDK.convertToAssets( + convertedShares + ); + console.log(`${testAssets} assets = ${convertedShares} shares`); + console.log(`${convertedShares} shares = ${convertedBackToAssets} assets`); + } catch (error) { + console.error("Error with conversion utilities:", error); + } +} + +async function userExample() { + const provider = new RpcProvider({ + nodeUrl: "https://rpc.starknet.lava.build:443", + }); + + // Check for required environment variables + const accountAddress = process.env.ACCOUNT_ADDRESS; + const accountPK = process.env.ACCOUNT_PK; + + if (!accountAddress || !accountPK) { + console.error("Missing required environment variables:"); + if (!accountAddress) console.error("- ACCOUNT_ADDRESS"); + if (!accountPK) console.error("- ACCOUNT_PK"); + console.error("Please set these environment variables and try again."); + return; + } + + const account = new Account( + provider, + accountAddress, + accountPK, + undefined, + "0x3" + ); + + const userSDK = new VaultUserSDK(vaultConfig, provider); + + console.log("=== Vault User Operations Interactive Example ==="); + console.log(`Connected to vault: ${vaultConfig.vaultAddress}`); + console.log(`Using account: ${account.address}`); + + try { + let choice = ""; + while (choice !== "0") { + choice = await showMenu(); + + switch (choice) { + case "1": + await handleGetVaultState(userSDK); + break; + case "2": + await handleGetUserBalance(userSDK, account); + break; + case "3": + await handlePreviewDeposit(userSDK); + break; + case "4": + await handleGenerateDepositCalldata(userSDK, account); + break; + case "5": + await handleGenerateDepositCalldataWithApproval(userSDK, account); + break; + case "6": + await handleExecuteDeposit(userSDK, account); + break; + case "7": + await handlePreviewMint(userSDK); + break; + case "8": + await handleGenerateMintCalldata(userSDK, account); + break; + case "9": + await handleGenerateMintCalldataWithApproval(userSDK, account); + break; + case "10": + await handleExecuteMint(userSDK, account); + break; + case "11": + await handlePreviewRedeem(userSDK); + break; + case "12": + await handleGenerateRequestRedeemCalldata(userSDK, account); + break; + case "13": + await handleExecuteRequestRedeem(userSDK, account); + break; + case "14": + await handleGenerateClaimRedeemCalldata(userSDK); + break; + case "15": + await handleExecuteClaimRedeem(userSDK, account); + break; + case "16": + await handleCheckDueAssets(userSDK); + break; + case "17": + await handleConversionUtilities(userSDK); + break; + case "0": + console.log("Goodbye!"); + break; + default: + console.log("Invalid choice. Please enter a number between 0-17."); + break; + } + + if (choice !== "0") { + await question("\nPress Enter to continue..."); + } + } + } catch (error) { + console.error("Error in user example:", error); + } finally { + rl.close(); + } +} + +userExample(); + +export { userExample }; diff --git a/sdk/package.json b/sdk/package.json new file mode 100644 index 00000000..02f0ac52 --- /dev/null +++ b/sdk/package.json @@ -0,0 +1,46 @@ +{ + "name": "@starknet-vault-kit/sdk", + "version": "0.1.0", + "description": "TypeScript SDK for Starknet Vault Kit - user and curator operations", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "jest", + "lint": "eslint src/**/*.ts", + "clean": "rm -rf dist" + }, + "keywords": [ + "starknet", + "vault", + "defi", + "erc4626", + "sdk" + ], + "author": "Starknet Vault Kit Team", + "license": "MIT", + "dependencies": { + "dotenv": "^17.2.2", + "starknet": "7.6.4" + }, + "devDependencies": { + "@types/jest": "^29.0.0", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.0.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/your-org/starknet-vault-kit.git", + "directory": "sdk" + } +} \ No newline at end of file diff --git a/sdk/src/abi/vault.json b/sdk/src/abi/vault.json new file mode 100644 index 00000000..131f20be --- /dev/null +++ b/sdk/src/abi/vault.json @@ -0,0 +1,1581 @@ +[ + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "openzeppelin_interfaces::upgrades::IUpgradeable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::upgrades::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "VaultMetadataImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20Metadata" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20Metadata", + "items": [ + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "VaultImpl", + "interface_name": "vault::vault::interface::IVault" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "interface", + "name": "vault::vault::interface::IVault", + "items": [ + { + "type": "function", + "name": "register_redeem_request", + "inputs": [ + { + "name": "redeem_request", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_vault_allocator", + "inputs": [ + { + "name": "vault_allocator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "request_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "claim_redeem", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_fees_config", + "inputs": [ + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_report_delay", + "inputs": [ + { + "name": "report_delay", + "type": "core::integer::u64" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_max_delta", + "inputs": [ + { + "name": "max_delta", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "report", + "inputs": [ + { + "name": "new_aum", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "bring_liquidity", + "inputs": [ + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "epoch", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "handled_epoch_len", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "buffer", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "aum", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_assets", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_nominal", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "fees_recipient", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "management_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "performance_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_request", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "report_delay", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "vault_allocator", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "last_report_timestamp", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_delta", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "due_assets_from_id", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ERC4626Impl", + "interface_name": "openzeppelin_interfaces::token::erc4626::IERC4626" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc4626::IERC4626", + "items": [ + { + "type": "function", + "name": "asset", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "total_assets", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_shares", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_assets", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_deposit", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_mint", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_withdraw", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_redeem", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20Impl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20", + "items": [ + { + "type": "function", + "name": "total_supply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balance_of", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "transfer_from", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20CamelOnlyImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly", + "items": [ + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "AccessControlImpl", + "interface_name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl", + "items": [ + { + "type": "function", + "name": "has_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_role_admin", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "renounce_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "PausableImpl", + "interface_name": "openzeppelin_interfaces::security::pausable::IPausable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::security::pausable::IPausable", + "items": [ + { + "type": "function", + "name": "is_paused", + "inputs": [], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + }, + { + "name": "symbol", + "type": "core::byte_array::ByteArray" + }, + { + "name": "underlying_asset", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + }, + { + "name": "report_delay", + "type": "core::integer::u64" + }, + { + "name": "max_delta", + "type": "core::integer::u256" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "struct", + "members": [ + { + "name": "from", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "nested" + }, + { + "name": "Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Deposit", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "nested" + }, + { + "name": "Withdraw", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "delay", + "type": "core::integer::u64", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "previous_admin_role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "new_admin_role", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "RoleGranted", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "nested" + }, + { + "name": "RoleGrantedWithDelay", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "nested" + }, + { + "name": "RoleRevoked", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "nested" + }, + { + "name": "RoleAdminChanged", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Paused", + "type": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "nested" + }, + { + "name": "Unpaused", + "type": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemRequested", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "struct", + "members": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "redeem_request_nominal", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Report", + "kind": "struct", + "members": [ + { + "name": "new_epoch", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "new_handled_epoch_len", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_supply", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "management_fee_shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "performance_fee_shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Event", + "kind": "enum", + "variants": [ + { + "name": "ERC20Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "nested" + }, + { + "name": "ERC4626Event", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "nested" + }, + { + "name": "SRC5Event", + "type": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "nested" + }, + { + "name": "AccessControlEvent", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "nested" + }, + { + "name": "UpgradeableEvent", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "nested" + }, + { + "name": "PausableEvent", + "type": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "nested" + }, + { + "name": "RedeemRequested", + "type": "vault::vault::vault::Vault::RedeemRequested", + "kind": "nested" + }, + { + "name": "RedeemClaimed", + "type": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "nested" + }, + { + "name": "Report", + "type": "vault::vault::vault::Vault::Report", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/sdk/src/abi/vault_allocator.json b/sdk/src/abi/vault_allocator.json new file mode 100644 index 00000000..4506e322 --- /dev/null +++ b/sdk/src/abi/vault_allocator.json @@ -0,0 +1,294 @@ +[ + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "openzeppelin_interfaces::upgrades::IUpgradeable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::upgrades::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "VaultAllocatorImpl", + "interface_name": "vault_allocator::vault_allocator::interface::IVaultAllocator" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::starknet::account::Call", + "members": [ + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Span::" + } + ] + }, + { + "type": "interface", + "name": "vault_allocator::vault_allocator::interface::IVaultAllocator", + "items": [ + { + "type": "function", + "name": "manager", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_manager", + "inputs": [ + { + "name": "manager", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "manage", + "inputs": [ + { + "name": "call", + "type": "core::starknet::account::Call" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "manage_multi", + "inputs": [ + { + "name": "calls", + "type": "core::array::Array::" + } + ], + "outputs": [ + { + "type": "core::array::Array::>" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "OwnableImpl", + "interface_name": "openzeppelin_interfaces::access::ownable::IOwnable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::access::ownable::IOwnable", + "items": [ + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer_ownership", + "inputs": [ + { + "name": "new_owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "renounce_ownership", + "inputs": [], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferred", + "kind": "struct", + "members": [ + { + "name": "previous_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "new_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + "kind": "struct", + "members": [ + { + "name": "previous_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "new_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::ownable::ownable::OwnableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "OwnershipTransferred", + "type": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferred", + "kind": "nested" + }, + { + "name": "OwnershipTransferStarted", + "type": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "vault_allocator::vault_allocator::vault_allocator::VaultAllocator::CallPerformed", + "kind": "struct", + "members": [ + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "selector", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "calldata", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "result", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault_allocator::vault_allocator::vault_allocator::VaultAllocator::Event", + "kind": "enum", + "variants": [ + { + "name": "OwnableEvent", + "type": "openzeppelin_access::ownable::ownable::OwnableComponent::Event", + "kind": "nested" + }, + { + "name": "UpgradeableEvent", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "nested" + }, + { + "name": "CallPerformed", + "type": "vault_allocator::vault_allocator::vault_allocator::VaultAllocator::CallPerformed", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/sdk/src/curator/index.ts b/sdk/src/curator/index.ts new file mode 100644 index 00000000..1865fe0f --- /dev/null +++ b/sdk/src/curator/index.ts @@ -0,0 +1 @@ +export class VaultCuratorSDK {} diff --git a/sdk/src/index.ts b/sdk/src/index.ts new file mode 100644 index 00000000..3c737a92 --- /dev/null +++ b/sdk/src/index.ts @@ -0,0 +1,23 @@ +// Main SDK exports +export { VaultUserSDK } from "./user"; +export { VaultCuratorSDK } from "./curator"; +export { CalldataBuilder } from "./utils/calldata"; + +// Type exports +export type { + VaultConfig, + DepositParams, + MintParams, + RequestRedeemParams, + ClaimRedeemParams, + VaultState, + FeesConfig, + ReportParams, + BringLiquidityParams, + CalldataResult, + MultiCalldataResult, + Call +} from "./types"; + +// Re-export starknet types that users might need +export type { BigNumberish, Provider, Contract } from "starknet"; \ No newline at end of file diff --git a/sdk/src/types/index.ts b/sdk/src/types/index.ts new file mode 100644 index 00000000..8733e2fe --- /dev/null +++ b/sdk/src/types/index.ts @@ -0,0 +1,63 @@ +import { BigNumberish, Call } from "starknet"; + +export interface VaultConfig { + vaultAddress: string; +} + +export interface DepositParams { + assets: BigNumberish; + receiver: string; + includeApprove?: boolean; // Include approval transaction for underlying asset +} + +export interface MintParams { + shares: BigNumberish; + receiver: string; + includeApprove?: boolean; // Include approval transaction for underlying asset +} + +export interface RequestRedeemParams { + shares: BigNumberish; + receiver: string; + owner: string; +} + +export interface ClaimRedeemParams { + id: BigNumberish; +} + +export interface VaultState { + epoch: bigint; + handledEpochLen: bigint; + buffer: bigint; + aum: bigint; + totalSupply: bigint; + totalAssets: bigint; +} + +export interface FeesConfig { + feesRecipient: string; + redeemFees: bigint; + managementFees: bigint; + performanceFees: bigint; +} + +export interface ReportParams { + newAum: BigNumberish; +} + +export interface BringLiquidityParams { + amount: BigNumberish; +} + +export interface CalldataResult { + contractAddress: string; + entrypoint: string; + calldata: string[]; +} + +export interface MultiCalldataResult { + transactions: CalldataResult[]; +} + +export { Call }; diff --git a/sdk/src/user/index.ts b/sdk/src/user/index.ts new file mode 100644 index 00000000..1b1be9f1 --- /dev/null +++ b/sdk/src/user/index.ts @@ -0,0 +1,324 @@ +import { Provider, Contract, BigNumberish, uint256 } from "starknet"; +import { + VaultConfig, + DepositParams, + MintParams, + RequestRedeemParams, + ClaimRedeemParams, + CalldataResult, + MultiCalldataResult, + VaultState, +} from "../types"; +import { CalldataBuilder } from "../utils/calldata"; +import vaultAbi from "../abi/vault.json"; + +export class VaultUserSDK { + private vaultConfig: VaultConfig; + private provider?: Provider; + private vaultContract?: Contract; + private underlyingAssetAddress?: string; + + constructor(vaultConfig: VaultConfig, provider?: Provider) { + this.vaultConfig = vaultConfig; + this.provider = provider; + } + + /** + * Set the provider for reading vault state + */ + setProvider(provider: Provider): void { + this.provider = provider; + } + + /** + * Initialize vault contract for read operations + */ + private async initContract(): Promise { + if (!this.provider) { + throw new Error("Provider is required for contract operations"); + } + + if (!this.vaultContract) { + this.vaultContract = new Contract( + vaultAbi, + this.vaultConfig.vaultAddress, + this.provider + ); + + // Fetch underlying asset address if not cached + if (!this.underlyingAssetAddress) { + this.underlyingAssetAddress = await this.vaultContract.asset(); + } + } + } + + // === CALLDATA GENERATION METHODS === + + /** + * Generate calldata for deposit operation + * If includeApprove is true, returns multiple transactions including approval + */ + buildDepositCalldata( + params: DepositParams + ): CalldataResult | MultiCalldataResult { + const depositCalldata = CalldataBuilder.buildDepositCalldata( + this.vaultConfig.vaultAddress, + params.assets, + params.receiver + ); + + if (!params.includeApprove) { + return depositCalldata; + } + + if (!this.underlyingAssetAddress) { + throw new Error( + "Cannot build approval calldata: underlying asset address not loaded. Call a view method first or set includeApprove to false." + ); + } + + const approveCalldata = CalldataBuilder.buildApproveCalldata( + this.underlyingAssetAddress, + this.vaultConfig.vaultAddress, + params.assets + ); + + return { + transactions: [approveCalldata, depositCalldata], + }; + } + + /** + * Generate calldata for mint operation + * If includeApprove is true, returns multiple transactions including approval + */ + buildMintCalldata(params: MintParams): CalldataResult | MultiCalldataResult { + const mintCalldata = CalldataBuilder.buildMintCalldata( + this.vaultConfig.vaultAddress, + params.shares, + params.receiver + ); + + if (!params.includeApprove) { + return mintCalldata; + } + + if (!this.underlyingAssetAddress) { + throw new Error( + "Cannot build approval calldata: underlying asset address not loaded. Call a view method first or set includeApprove to false." + ); + } + + // For mint, we need to calculate required assets first + throw new Error( + "Cannot build mint with approval: requires async preview call. Use buildMintCalldataWithApproval() method instead." + ); + } + + /** + * Generate calldata for deposit operation with approval (async version) + * Loads underlying asset address and includes approval transaction + */ + async buildDepositCalldataWithApproval( + params: DepositParams + ): Promise { + if (!params.includeApprove) { + throw new Error( + "Use buildDepositCalldata() for deposit without approval" + ); + } + + await this.initContract(); + + const depositCalldata = CalldataBuilder.buildDepositCalldata( + this.vaultConfig.vaultAddress, + params.assets, + params.receiver + ); + + const approveCalldata = CalldataBuilder.buildApproveCalldata( + this.underlyingAssetAddress!, + this.vaultConfig.vaultAddress, + params.assets + ); + + return { + transactions: [approveCalldata, depositCalldata], + }; + } + + /** + * Generate calldata for mint operation with approval (async version) + * Calculates required assets and includes approval transaction + */ + async buildMintCalldataWithApproval( + params: MintParams + ): Promise { + if (!params.includeApprove) { + throw new Error("Use buildMintCalldata() for mint without approval"); + } + + await this.initContract(); + + // Calculate required assets for the mint + const requiredAssets = await this.previewMint(params.shares); + + const mintCalldata = CalldataBuilder.buildMintCalldata( + this.vaultConfig.vaultAddress, + params.shares, + params.receiver + ); + + const approveCalldata = CalldataBuilder.buildApproveCalldata( + this.underlyingAssetAddress!, + this.vaultConfig.vaultAddress, + requiredAssets + ); + + return { + transactions: [approveCalldata, mintCalldata], + }; + } + + /** + * Generate calldata for request redeem operation + */ + buildRequestRedeemCalldata(params: RequestRedeemParams): CalldataResult { + return CalldataBuilder.buildRequestRedeemCalldata( + this.vaultConfig.vaultAddress, + params.shares, + params.receiver, + params.owner + ); + } + + /** + * Generate calldata for claim redeem operation + */ + buildClaimRedeemCalldata(params: ClaimRedeemParams): CalldataResult { + return CalldataBuilder.buildClaimRedeemCalldata( + this.vaultConfig.vaultAddress, + params.id + ); + } + + // === VIEW METHODS === + + /** + * Get current vault state + */ + async getVaultState(): Promise { + await this.initContract(); + + const [epoch, handledEpochLen, buffer, aum, totalSupply, totalAssets] = + await Promise.all([ + this.vaultContract!.epoch(), + this.vaultContract!.handled_epoch_len(), + this.vaultContract!.buffer(), + this.vaultContract!.aum(), + this.vaultContract!.total_supply(), + this.vaultContract!.total_assets(), + ]); + + return { + epoch: BigInt(uint256.uint256ToBN(epoch).toString()), + handledEpochLen: BigInt(uint256.uint256ToBN(handledEpochLen).toString()), + buffer: BigInt(uint256.uint256ToBN(buffer).toString()), + aum: BigInt(uint256.uint256ToBN(aum).toString()), + totalSupply: BigInt(uint256.uint256ToBN(totalSupply).toString()), + totalAssets: BigInt(uint256.uint256ToBN(totalAssets).toString()), + }; + } + + /** + * Get user's share balance + */ + async getUserShareBalance(userAddress: string): Promise { + await this.initContract(); + const balance = await this.vaultContract!.balance_of(userAddress); + return BigInt(uint256.uint256ToBN(balance).toString()); + } + + /** + * Preview how many shares will be received for a deposit + */ + async previewDeposit(assets: BigNumberish): Promise { + await this.initContract(); + const shares = await this.vaultContract!.preview_deposit( + uint256.bnToUint256(assets.toString()) + ); + return BigInt(uint256.uint256ToBN(shares).toString()); + } + + /** + * Preview how many assets are needed to mint shares + */ + async previewMint(shares: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.preview_mint( + uint256.bnToUint256(shares.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Preview how many assets will be received for redeeming shares + */ + async previewRedeem(shares: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.preview_redeem( + uint256.bnToUint256(shares.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Get expected assets for a redemption NFT ID + */ + async getDueAssetsFromId(id: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.due_assets_from_id( + uint256.bnToUint256(id.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Convert assets to shares + */ + async convertToShares(assets: BigNumberish): Promise { + await this.initContract(); + const shares = await this.vaultContract!.convert_to_shares( + uint256.bnToUint256(assets.toString()) + ); + return BigInt(uint256.uint256ToBN(shares).toString()); + } + + /** + * Convert shares to assets + */ + async convertToAssets(shares: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.convert_to_assets( + uint256.bnToUint256(shares.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Get the underlying asset contract address + */ + async getUnderlyingAssetAddress(): Promise { + await this.initContract(); + return this.underlyingAssetAddress!; + } + + /** + * Get the redeem request NFT contract address + */ + async getRedeemRequestAddress(): Promise { + await this.initContract(); + const address = await this.vaultContract!.redeem_request(); + return address.toString(); + } +} diff --git a/sdk/src/utils/calldata.ts b/sdk/src/utils/calldata.ts new file mode 100644 index 00000000..77cfac2d --- /dev/null +++ b/sdk/src/utils/calldata.ts @@ -0,0 +1,226 @@ +import { BigNumberish, CallData, uint256 } from "starknet"; +import { CalldataResult } from "../types"; + +export class CalldataBuilder { + /** + * Build calldata for ERC20 approve operation + */ + static buildApproveCalldata( + tokenAddress: string, + spender: string, + amount: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + spender: spender, + amount: uint256.bnToUint256(amount.toString()), + }); + + return { + contractAddress: tokenAddress, + entrypoint: "approve", + calldata, + }; + } + + /** + * Build calldata for deposit operation + */ + static buildDepositCalldata( + vaultAddress: string, + assets: BigNumberish, + receiver: string + ): CalldataResult { + const calldata = CallData.compile({ + assets: uint256.bnToUint256(assets.toString()), + receiver: receiver, + }); + + return { + contractAddress: vaultAddress, + entrypoint: "deposit", + calldata, + }; + } + + /** + * Build calldata for mint operation + */ + static buildMintCalldata( + vaultAddress: string, + shares: BigNumberish, + receiver: string + ): CalldataResult { + const calldata = CallData.compile({ + shares: uint256.bnToUint256(shares.toString()), + receiver: receiver, + }); + + return { + contractAddress: vaultAddress, + entrypoint: "mint", + calldata, + }; + } + + /** + * Build calldata for request_redeem operation + */ + static buildRequestRedeemCalldata( + vaultAddress: string, + shares: BigNumberish, + receiver: string, + owner: string + ): CalldataResult { + const calldata = CallData.compile({ + shares: uint256.bnToUint256(shares.toString()), + receiver: receiver, + owner: owner, + }); + + return { + contractAddress: vaultAddress, + entrypoint: "request_redeem", + calldata, + }; + } + + /** + * Build calldata for claim_redeem operation + */ + static buildClaimRedeemCalldata( + vaultAddress: string, + id: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + id: uint256.bnToUint256(id.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "claim_redeem", + calldata, + }; + } + + /** + * Build calldata for report operation (curator only) + */ + static buildReportCalldata( + vaultAddress: string, + newAum: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + new_aum: uint256.bnToUint256(newAum.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "report", + calldata, + }; + } + + /** + * Build calldata for bring_liquidity operation (curator only) + */ + static buildBringLiquidityCalldata( + vaultAddress: string, + amount: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + amount: uint256.bnToUint256(amount.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "bring_liquidity", + calldata, + }; + } + + /** + * Build calldata for pause operation (curator only) + */ + static buildPauseCalldata(vaultAddress: string): CalldataResult { + const calldata = CallData.compile({}); + + return { + contractAddress: vaultAddress, + entrypoint: "pause", + calldata, + }; + } + + /** + * Build calldata for unpause operation (curator only) + */ + static buildUnpauseCalldata(vaultAddress: string): CalldataResult { + const calldata = CallData.compile({}); + + return { + contractAddress: vaultAddress, + entrypoint: "unpause", + calldata, + }; + } + + /** + * Build calldata for set_fees_config operation (curator only) + */ + static buildSetFeesConfigCalldata( + vaultAddress: string, + feesRecipient: string, + redeemFees: BigNumberish, + managementFees: BigNumberish, + performanceFees: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + fees_recipient: feesRecipient, + redeem_fees: uint256.bnToUint256(redeemFees.toString()), + management_fees: uint256.bnToUint256(managementFees.toString()), + performance_fees: uint256.bnToUint256(performanceFees.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "set_fees_config", + calldata, + }; + } + + /** + * Build calldata for set_report_delay operation (curator only) + */ + static buildSetReportDelayCalldata( + vaultAddress: string, + reportDelay: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + report_delay: reportDelay.toString(), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "set_report_delay", + calldata, + }; + } + + /** + * Build calldata for set_max_delta operation (curator only) + */ + static buildSetMaxDeltaCalldata( + vaultAddress: string, + maxDelta: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + max_delta: uint256.bnToUint256(maxDelta.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "set_max_delta", + calldata, + }; + } +} \ No newline at end of file diff --git a/sdk/tsconfig.json b/sdk/tsconfig.json new file mode 100644 index 00000000..8702215c --- /dev/null +++ b/sdk/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts" + ] +} \ No newline at end of file From adb354e30abecb9ff61965278cbc7ec6954b268c Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:28:53 +0200 Subject: [PATCH 20/54] Refactor backend architecture and improve pending redeems logic - Restructure backend with improved microservices architecture - Add relayer services for automatic redeem and on-chain AUM - Improve pending redeems logic to handle claimed redeems properly - Update database service with better redeem filtering - Add comprehensive logging and configuration management - Remove scientific notation from decimal formatting - Add Docker development setup with service isolation --- backend/.dockerignore | 83 ++ backend/.env.example | 70 +- backend/.prettierrc | 7 + backend/Dockerfile.api | 31 - backend/Dockerfile.api.dev | 35 + backend/Dockerfile.dev | 38 - backend/Dockerfile.indexer | 29 - backend/Dockerfile.indexer.dev | 32 + backend/Dockerfile.relayerAutomaticRedeem.dev | 32 + backend/Dockerfile.relayerOnChainAum.dev | 32 + backend/LOGGING.md | 120 ++ backend/apps/api/README.md | 29 - backend/apps/api/nest-cli.json | 7 +- backend/apps/api/nodemon.json | 8 - backend/apps/api/package.json | 28 +- backend/apps/api/src/app.controller.ts | 78 +- backend/apps/api/src/app.module.ts | 2 +- backend/apps/api/src/app.service.ts | 410 ++++-- backend/apps/api/src/health/health.module.ts | 10 +- backend/apps/api/src/main.ts | 59 +- .../apps/api/src/starknet/starknet.service.ts | 113 -- backend/apps/api/src/types/strategy.ts | 15 +- backend/apps/indexer/README.md | 29 - backend/apps/indexer/nest-cli.json | 11 + backend/apps/indexer/nodemon.json | 8 - backend/apps/indexer/package.json | 15 +- backend/apps/indexer/src/indexer.module.ts | 12 + backend/apps/indexer/src/indexer.service.ts | 229 ++-- backend/apps/indexer/src/main.ts | 59 +- .../apps/relayerAutomaticRedeem/nest-cli.json | 11 + .../apps/relayerAutomaticRedeem/package.json | 25 + .../apps/relayerAutomaticRedeem/src/main.ts | 53 + .../src/relayerAutomaticRedeem.constants.ts | 4 + .../src/relayerAutomaticRedeem.module.ts | 12 + .../src/relayerAutomaticRedeem.service.ts | 234 ++++ .../apps/relayerAutomaticRedeem/tsconfig.json | 9 + backend/apps/relayerOnChainAum/nest-cli.json | 11 + backend/apps/relayerOnChainAum/package.json | 21 + .../apps/relayerOnChainAum/src/app.module.ts | 17 + backend/apps/relayerOnChainAum/src/main.ts | 24 + .../relayerOnChainAum/src/relayer.service.ts | 92 ++ backend/apps/relayerOnChainAum/tsconfig.json | 8 + backend/docker-compose.dev.yml | 133 +- backend/docker-compose.yml | 66 - backend/libs/config/src/config.module.ts | 4 +- backend/libs/config/src/config.service.ts | 10 +- backend/libs/config/src/env.validation.ts | 60 +- backend/libs/db/package.json | 11 +- .../20250826190954_init/migration.sql | 0 .../20250904105108_init/migration.sql | 10 + .../db/prisma/migrations/migration_lock.toml | 3 + backend/{ => libs/db}/prisma/schema.prisma | 7 +- backend/libs/db/src/prisma.service.ts | 90 +- backend/libs/logger/package.json | 3 - backend/libs/logger/src/index.ts | 1 + backend/libs/logger/src/logger.config.ts | 22 + backend/libs/logger/src/logger.ts | 13 +- backend/libs/starknet/package.json | 17 + .../libs/starknet/src/abis/aumProvider.json | 99 ++ .../starknet/src}/abis/vault.json | 0 .../starknet => libs/starknet/src}/index.ts | 0 .../starknet/src}/starknet.module.ts | 0 backend/libs/starknet/src/starknet.service.ts | 328 +++++ backend/libs/starknet/tsconfig.json | 9 + backend/package.json | 30 +- backend/pnpm-lock.yaml | 1105 ++++++++++------- backend/prisma/migrations/migration_lock.toml | 3 - backend/tsconfig.base.json | 4 +- 68 files changed, 2901 insertions(+), 1279 deletions(-) create mode 100644 backend/.dockerignore create mode 100644 backend/.prettierrc delete mode 100644 backend/Dockerfile.api create mode 100644 backend/Dockerfile.api.dev delete mode 100644 backend/Dockerfile.dev delete mode 100644 backend/Dockerfile.indexer create mode 100644 backend/Dockerfile.indexer.dev create mode 100644 backend/Dockerfile.relayerAutomaticRedeem.dev create mode 100644 backend/Dockerfile.relayerOnChainAum.dev create mode 100644 backend/LOGGING.md delete mode 100644 backend/apps/api/README.md delete mode 100644 backend/apps/api/nodemon.json delete mode 100644 backend/apps/api/src/starknet/starknet.service.ts delete mode 100644 backend/apps/indexer/README.md create mode 100644 backend/apps/indexer/nest-cli.json delete mode 100644 backend/apps/indexer/nodemon.json create mode 100644 backend/apps/indexer/src/indexer.module.ts create mode 100644 backend/apps/relayerAutomaticRedeem/nest-cli.json create mode 100644 backend/apps/relayerAutomaticRedeem/package.json create mode 100644 backend/apps/relayerAutomaticRedeem/src/main.ts create mode 100644 backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.constants.ts create mode 100644 backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.module.ts create mode 100644 backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.service.ts create mode 100644 backend/apps/relayerAutomaticRedeem/tsconfig.json create mode 100644 backend/apps/relayerOnChainAum/nest-cli.json create mode 100644 backend/apps/relayerOnChainAum/package.json create mode 100644 backend/apps/relayerOnChainAum/src/app.module.ts create mode 100644 backend/apps/relayerOnChainAum/src/main.ts create mode 100644 backend/apps/relayerOnChainAum/src/relayer.service.ts create mode 100644 backend/apps/relayerOnChainAum/tsconfig.json delete mode 100644 backend/docker-compose.yml rename backend/{ => libs/db}/prisma/migrations/20250826190954_init/migration.sql (100%) create mode 100644 backend/libs/db/prisma/migrations/20250904105108_init/migration.sql create mode 100644 backend/libs/db/prisma/migrations/migration_lock.toml rename backend/{ => libs/db}/prisma/schema.prisma (88%) create mode 100644 backend/libs/logger/src/logger.config.ts create mode 100644 backend/libs/starknet/package.json create mode 100644 backend/libs/starknet/src/abis/aumProvider.json rename backend/{apps/api/src/starknet => libs/starknet/src}/abis/vault.json (100%) rename backend/{apps/api/src/starknet => libs/starknet/src}/index.ts (100%) rename backend/{apps/api/src/starknet => libs/starknet/src}/starknet.module.ts (100%) create mode 100644 backend/libs/starknet/src/starknet.service.ts create mode 100644 backend/libs/starknet/tsconfig.json delete mode 100644 backend/prisma/migrations/migration_lock.toml diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..09e1868d --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,83 @@ +# Dependencies +node_modules +**/node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build outputs +dist +build +**/dist +**/build + +# Development files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and editor files +.vscode +.idea +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile* +docker-compose*.yml +.dockerignore + +# Logs +logs +*.log + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +.pnp +.pnp.js + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Prisma generated client (will be regenerated in container) +**/prisma/generated + +# Temporary files +tmp/ +temp/ + +# Documentation +*.md +docs/ + +# Test files +test/ +tests/ +**/*.test.* +**/*.spec.* \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index b26d5b2a..fe5124d7 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,20 +1,70 @@ -# Database -DATABASE_URL="postgresql://postgres:postgres@localhost:5432/starknet_vault_kit" +# ========================================== +# SHARED ENVIRONMENT VARIABLES +# ========================================== -# Server -PORT=3000 +# Database (required by all services) +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/starknet_vault_kit" -# StarkNet RPC +# StarkNet RPC (required by API, relayers) RPC_URL="https://starknet-mainnet.public.blastapi.io" -# Vault Contract Address (replace with actual address) +# Vault Contract Address (required by all services) VAULT_ADDRESS="0x0000000000000000000000000000000000000000000000000000000000000000" -# Indexing start block +# ========================================== +# API SERVICE +# ========================================== + +# Server port (optional, defaults to 3000) +PORT=3000 + +# ========================================== +# INDEXER SERVICE +# ========================================== + +# Apibara token for indexing (required) +APIBARA_TOKEN="your_apibara_token_here" + +# Indexing start block (optional) START_BLOCK=12993 -# Force start from START_BLOCK (ignores database state) +# Force start from START_BLOCK ignoring database state (optional, defaults to false) FORCE_START_BLOCK=false -# Apibara token for indexing (required for indexer) -APIBARA_TOKEN="your_apibara_token_here" \ No newline at end of file +# ========================================== +# RELAYER AUTOMATIC REDEEM SERVICE +# ========================================== + +# Relayer wallet address (required) +RELAYER_ADDRESS="0x..." + +# Relayer private key (required) +RELAYER_PRIVATE_KEY="0x..." + +# Cron schedule for automatic redeem checks (optional, defaults to "*/5 * * * *") +CRON_SCHEDULE="*/5 * * * *" + +# ========================================== +# RELAYER ON-CHAIN AUM SERVICE +# ========================================== + +# Note: RELAYER_ADDRESS and RELAYER_PRIVATE_KEY are shared with automatic redeem service + +# On-chain AUM provider contract address (required) +ON_CHAIN_AUM_PROVIDER="0x..." + +# ========================================== +# LOGGING CONFIGURATION (optional) +# ========================================== + +# Log level (optional, defaults to info) +LOG_LEVEL="info" # Options: error, warn, info, debug + +# Enable file logging (optional, defaults to false) +ENABLE_FILE_LOGGING="false" + +# Log directory (optional, defaults to logs) +LOG_DIR="logs" + +# Node environment (optional, defaults to development) +NODE_ENV="development" # Options: development, production \ No newline at end of file diff --git a/backend/.prettierrc b/backend/.prettierrc new file mode 100644 index 00000000..526aaef6 --- /dev/null +++ b/backend/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 2 +} \ No newline at end of file diff --git a/backend/Dockerfile.api b/backend/Dockerfile.api deleted file mode 100644 index 09744202..00000000 --- a/backend/Dockerfile.api +++ /dev/null @@ -1,31 +0,0 @@ -FROM node:18-alpine - -WORKDIR /app - -# Copy package files -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ -COPY apps/api/package.json ./apps/api/ -COPY libs/config/package.json ./libs/config/ -COPY libs/db/package.json ./libs/db/ -COPY libs/core/package.json ./libs/core/ -COPY libs/logger/package.json ./libs/logger/ - -# Install pnpm and dependencies -RUN npm install -g pnpm -RUN pnpm install --frozen-lockfile - -# Copy source code -COPY prisma ./prisma/ -COPY apps/api ./apps/api/ -COPY libs ./libs/ -COPY tsconfig.json ./ - -# Generate Prisma client -RUN pnpm run prisma:generate - -# Build the application -RUN pnpm run build:api - -EXPOSE 3000 - -CMD ["pnpm", "run", "start:api"] \ No newline at end of file diff --git a/backend/Dockerfile.api.dev b/backend/Dockerfile.api.dev new file mode 100644 index 00000000..7845208f --- /dev/null +++ b/backend/Dockerfile.api.dev @@ -0,0 +1,35 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/api/package.json ./apps/api/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/api ./apps/api/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries in parallel +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build && \ + pnpm run build:api + +EXPOSE 3000 + +CMD ["pnpm", "run", "start:api"] \ No newline at end of file diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev deleted file mode 100644 index 73282c54..00000000 --- a/backend/Dockerfile.dev +++ /dev/null @@ -1,38 +0,0 @@ -FROM node:18-slim as base - -# Install OpenSSL 1.1 and other necessary packages -RUN apt-get update && apt-get install -y \ - openssl \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Install pnpm -RUN npm install -g pnpm - -# Copy package files for dependency installation -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ -COPY apps/api/package.json ./apps/api/ -COPY apps/indexer/package.json ./apps/indexer/ -COPY libs/config/package.json ./libs/config/ -COPY libs/db/package.json ./libs/db/ -COPY libs/logger/package.json ./libs/logger/ - -# Install all dependencies (including dev dependencies for development) -RUN pnpm install - -# Copy prisma schema for client generation -COPY prisma ./prisma/ - -# Generate Prisma client -RUN pnpm run prisma:generate - -# API target for development -FROM base as api -EXPOSE 3000 -CMD ["pnpm", "run", "dev:api"] - -# Indexer target for development -FROM base as indexer -CMD ["pnpm", "run", "dev:indexer"] \ No newline at end of file diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer deleted file mode 100644 index 0fa81d72..00000000 --- a/backend/Dockerfile.indexer +++ /dev/null @@ -1,29 +0,0 @@ -FROM node:18-alpine - -WORKDIR /app - -# Copy package files -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ -COPY apps/indexer/package.json ./apps/indexer/ -COPY libs/config/package.json ./libs/config/ -COPY libs/db/package.json ./libs/db/ -COPY libs/core/package.json ./libs/core/ -COPY libs/logger/package.json ./libs/logger/ - -# Install pnpm and dependencies -RUN npm install -g pnpm -RUN pnpm install --frozen-lockfile - -# Copy source code -COPY prisma ./prisma/ -COPY apps/indexer ./apps/indexer/ -COPY libs ./libs/ -COPY tsconfig.json ./ - -# Generate Prisma client -RUN pnpm run prisma:generate - -# Build the application -RUN pnpm run build:indexer - -CMD ["pnpm", "run", "start:indexer"] \ No newline at end of file diff --git a/backend/Dockerfile.indexer.dev b/backend/Dockerfile.indexer.dev new file mode 100644 index 00000000..11f66c34 --- /dev/null +++ b/backend/Dockerfile.indexer.dev @@ -0,0 +1,32 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/indexer/package.json ./apps/indexer/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/indexer ./apps/indexer/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build + +CMD ["pnpm", "run", "dev:indexer"] \ No newline at end of file diff --git a/backend/Dockerfile.relayerAutomaticRedeem.dev b/backend/Dockerfile.relayerAutomaticRedeem.dev new file mode 100644 index 00000000..6ffeef94 --- /dev/null +++ b/backend/Dockerfile.relayerAutomaticRedeem.dev @@ -0,0 +1,32 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/relayerAutomaticRedeem/package.json ./apps/relayerAutomaticRedeem/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/relayerAutomaticRedeem ./apps/relayerAutomaticRedeem/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build + +CMD ["pnpm", "run", "dev:relayerAutomaticRedeem"] \ No newline at end of file diff --git a/backend/Dockerfile.relayerOnChainAum.dev b/backend/Dockerfile.relayerOnChainAum.dev new file mode 100644 index 00000000..99e1aefa --- /dev/null +++ b/backend/Dockerfile.relayerOnChainAum.dev @@ -0,0 +1,32 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/relayerOnChainAum/package.json ./apps/relayerOnChainAum/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/relayerOnChainAum ./apps/relayerOnChainAum/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build + +CMD ["pnpm", "run", "dev:relayerOnChainAum"] \ No newline at end of file diff --git a/backend/LOGGING.md b/backend/LOGGING.md new file mode 100644 index 00000000..30c959be --- /dev/null +++ b/backend/LOGGING.md @@ -0,0 +1,120 @@ +# Logging Guide + +## Overview + +The StarkNet Vault Kit backend uses Winston for structured logging across all applications. The logging system provides clean, consistent output with support for both console and file logging. + +## Configuration + +Logging can be configured via environment variables: + +```bash +# Log level (error, warn, info, debug) +LOG_LEVEL=info + +# Enable file logging (automatically enabled in production) +ENABLE_FILE_LOGGING=false + +# Directory for log files +LOG_DIR=logs + +# Environment (affects log format) +NODE_ENV=development +``` + +## Log Levels + +- **error**: Error messages and stack traces +- **warn**: Warning messages +- **info**: General information (default) +- **debug**: Detailed debugging information + +## Output Format + +### Development (Console) +``` +10:45:23 info [API:Main] Server started on port 3000 +10:45:24 debug [Indexer:Service] Processing block { + "blockNumber": 12345, + "timestamp": 1234567890 +} +``` + +### Production (JSON) +```json +{ + "timestamp": "2024-01-01T10:45:23.000Z", + "level": "info", + "service": "starknet-vault-kit", + "context": "API:Main", + "message": "Server started on port 3000", + "environment": "production" +} +``` + +## Usage in Code + +```typescript +import { Logger } from "@forge/logger"; + +// Create a logger with context +const logger = Logger.create("MyService"); + +// Log messages +logger.info("Service started"); +logger.debug("Processing data", { count: 100 }); +logger.warn("High memory usage", { usage: "85%" }); +logger.error("Failed to process", error); +``` + +## File Logging + +When file logging is enabled, logs are written to: +- `logs/starknet-vault-kit-YYYY-MM-DD.log` - All logs +- `logs/starknet-vault-kit-error-YYYY-MM-DD.log` - Error logs only + +Files are automatically rotated daily and kept for 14 days. + +## Best Practices + +1. **Use appropriate log levels** + - `error`: For errors that need immediate attention + - `warn`: For potential issues or degraded performance + - `info`: For important application events + - `debug`: For detailed debugging information + +2. **Include context in log messages** + ```typescript + logger.info("Processing redeem", { + redeemId: 123, + user: "0x..." + }); + ``` + +3. **Avoid logging sensitive information** + - Never log private keys, passwords, or tokens + - Truncate addresses when appropriate + +4. **Keep messages concise** + - Use clear, descriptive messages + - Avoid emojis and special characters + - Put details in metadata objects + +## Application Contexts + +Each application uses its own context prefix: + +- **API**: `API:Main`, `API:Service`, `API:Controller` +- **Indexer**: `Indexer:Main`, `Indexer:Service` +- **RelayerAutomaticRedeem**: `RelayerAutomaticRedeem:Main`, `RelayerAutomaticRedeem:Service` +- **StarkNet**: `Starknet:Service` + +## Monitoring + +In production, logs can be aggregated and monitored using tools like: +- ELK Stack (Elasticsearch, Logstash, Kibana) +- Datadog +- New Relic +- CloudWatch (AWS) + +The JSON format makes it easy to parse and query logs in these systems. \ No newline at end of file diff --git a/backend/apps/api/README.md b/backend/apps/api/README.md deleted file mode 100644 index 71681cd1..00000000 --- a/backend/apps/api/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# API Service - -NestJS-based HTTP API for the StarkNet Vault Kit backend. - -## Features - -- RESTful API endpoints -- Swagger documentation -- Health checks -- StarkNet integration - -## Development - -```bash -# Run in development mode -pnpm dev:api - -# Build -pnpm build:api - -# Start production -pnpm --filter api start -``` - -## Endpoints - -- `GET /` - Basic health check -- `GET /health` - Detailed health status -- `GET /api` - Swagger UI documentation \ No newline at end of file diff --git a/backend/apps/api/nest-cli.json b/backend/apps/api/nest-cli.json index bc097ceb..e84723eb 100644 --- a/backend/apps/api/nest-cli.json +++ b/backend/apps/api/nest-cli.json @@ -3,6 +3,9 @@ "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { - "deleteOutDir": true - } + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/api/src/main" } \ No newline at end of file diff --git a/backend/apps/api/nodemon.json b/backend/apps/api/nodemon.json deleted file mode 100644 index ec1b3153..00000000 --- a/backend/apps/api/nodemon.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "watch": ["src", "../../../libs"], - "ext": "ts,js,json", - "ignore": ["node_modules", "dist"], - "env": { - "NODE_ENV": "development" - } -} \ No newline at end of file diff --git a/backend/apps/api/package.json b/backend/apps/api/package.json index 4f53d2bf..39e4416d 100644 --- a/backend/apps/api/package.json +++ b/backend/apps/api/package.json @@ -5,8 +5,8 @@ "main": "dist/main.js", "scripts": { "build": "nest build", - "dev": "nodemon --exec ts-node src/main.ts", - "start": "node dist/main.js", + "dev": "nest start --watch", + "start": "node dist/apps/api/src/main.js", "lint": "eslint \"src/**/*.ts\" --fix", "test": "jest" }, @@ -14,28 +14,14 @@ "@forge/config": "workspace:*", "@forge/db": "workspace:*", "@forge/logger": "workspace:*", - "@nestjs/axios": "^3.0.0", + "@forge/starknet": "workspace:*", + "@nestjs/axios": "^3.1.3", "@nestjs/common": "^10.0.0", - "@nestjs/config": "^3.0.0", - "@nestjs/core": "^10.0.0", + "@nestjs/core": "^10.4.20", "@nestjs/platform-express": "^10.0.0", - "@nestjs/swagger": "^7.0.0", + "@nestjs/swagger": "^7.4.2", "@nestjs/terminus": "^11.0.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", "decimal.js": "^10.6.0", - "reflect-metadata": "^0.2.0", - "rxjs": "^7.8.1", - "starknet": "^6.0.0" - }, - "devDependencies": { - "@nestjs/cli": "^10.0.0", - "@nestjs/testing": "^10.0.0", - "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "jest": "^29.5.0", - "ts-jest": "^29.1.0", - "ts-node": "^10.9.1", - "nodemon": "^3.0.0" + "starknet": "^7.6.4" } } \ No newline at end of file diff --git a/backend/apps/api/src/app.controller.ts b/backend/apps/api/src/app.controller.ts index cda78b68..2325b627 100644 --- a/backend/apps/api/src/app.controller.ts +++ b/backend/apps/api/src/app.controller.ts @@ -1,54 +1,88 @@ -import { Controller, Get, Param, Query } from "@nestjs/common"; -import { ApiTags, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger"; -import { AppService } from "./app.service"; -import { PendingRedeem } from "./types/strategy"; +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger'; +import { AppService, PendingRedeem, StrategyAnalytics, RedeemRequiredAssets } from './app.service'; -@ApiTags("api") +@ApiTags('api') @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() - @ApiResponse({ status: 200, description: "API information and available endpoints" }) + @ApiResponse({ status: 200, description: 'API information and available endpoints' }) getApiInfo() { return this.appService.getApiInfo(); } - @Get("pending-redeems/:address") - @ApiParam({ name: "address", description: "User address" }) + @Get('pending-redeems/:address') + @ApiParam({ name: 'address', description: 'User address' }) @ApiQuery({ - name: "limit", + name: 'limit', required: false, - description: "Number of pending redeems to return", + description: 'Number of pending redeems to return', type: Number, }) @ApiQuery({ - name: "offset", + name: 'offset', required: false, - description: "Number of pending redeems to skip", + description: 'Number of pending redeems to skip', type: Number, }) - @ApiResponse({ status: 200, description: "Pending redeems for the address" }) + @ApiResponse({ status: 200, description: 'Pending redeems for the address' }) async getPendingRedeems( - @Param("address") address: string, - @Query("limit") limit?: string, - @Query("offset") offset?: string + @Param('address') address: string, + @Query('limit') limit?: string, + @Query('offset') offset?: string ): Promise { const limitNumber = limit ? parseInt(limit, 10) : undefined; const offsetNumber = offset ? parseInt(offset, 10) : undefined; return this.appService.getPendingRedeems(address, limitNumber, offsetNumber); } - @Get("reports/last") - @ApiResponse({ status: 200, description: "Latest report from database" }) + @Get('reports/last') + @ApiResponse({ status: 200, description: 'Latest report from database' }) async getLastReport() { return this.appService.getLastReport(); } - @Get("redeems/:id") - @ApiParam({ name: "id", description: "Redeem ID" }) - @ApiResponse({ status: 200, description: "Redeem details by ID" }) - async getRedeemById(@Param("id") id: string) { + @Get('redeems/:id') + @ApiParam({ name: 'id', description: 'Redeem ID' }) + @ApiResponse({ status: 200, description: 'Redeem details by ID' }) + async getRedeemById(@Param('id') id: string) { return this.appService.getRedeemById(id); } + + @Get('strategy-analytics') + @ApiQuery({ + name: 'limit', + required: false, + description: 'Number of reports to return', + type: Number, + }) + @ApiQuery({ + name: 'offset', + required: false, + description: 'Number of reports to skip', + type: Number, + }) + @ApiResponse({ status: 200, description: 'Strategy analytics data' }) + async getStrategyAnalytics( + @Query('limit') limit?: string, + @Query('offset') offset?: string + ): Promise { + const limitNumber = limit ? parseInt(limit, 10) : 10; + const offsetNumber = offset ? parseInt(offset, 10) : undefined; + return this.appService.getStrategyAnalytics(limitNumber, offsetNumber); + } + + @Get('redeem-required-assets') + @ApiResponse({ status: 200, description: 'Redeem required assets for each epoch' }) + async getRedeemRequiredAssets(): Promise { + return this.appService.getRedeemRequiredAssets(); + } + + @Get('indexer-status') + @ApiResponse({ status: 200, description: 'Current indexer status including last indexed block' }) + async getIndexerStatus() { + return this.appService.getIndexerStatus(); + } } diff --git a/backend/apps/api/src/app.module.ts b/backend/apps/api/src/app.module.ts index 1a479dd3..b73c25d0 100644 --- a/backend/apps/api/src/app.module.ts +++ b/backend/apps/api/src/app.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@forge/config'; import { PrismaModule } from '@forge/db'; -import { StarknetModule } from './starknet'; +import { StarknetModule } from '@forge/starknet'; import { HealthModule } from './health'; import { AppController } from './app.controller'; import { AppService } from './app.service'; diff --git a/backend/apps/api/src/app.service.ts b/backend/apps/api/src/app.service.ts index 2032a86f..dfc4267e 100644 --- a/backend/apps/api/src/app.service.ts +++ b/backend/apps/api/src/app.service.ts @@ -1,16 +1,51 @@ -import { Injectable, OnModuleInit } from "@nestjs/common"; -import { validateAndParseAddress } from "starknet"; -import { PendingRedeem } from "./types/strategy"; -import { PrismaService } from "@forge/db"; -import { StarknetService } from "./starknet/starknet.service"; -import Decimal from "decimal.js"; -import { ConfigService } from "@forge/config"; -import { Logger } from "@forge/logger"; +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { PrismaService } from '@forge/db'; +import { StarknetService } from '@forge/starknet'; +import Decimal from 'decimal.js'; +import { ConfigService } from '@forge/config'; +import { Logger } from '@forge/logger'; +import { validateAndParseAddress } from 'starknet'; +// Types moved from @forge/core +export interface Strategy { + vault: string; + startBlockIndexing: number; +} + +export interface PendingRedeem { + epoch: number; + sharesBurn: string; + nominal: string; + assets: string; + redeemId: string; + timestamp: number; + transactionHash: string; +} + +export interface StrategyAnalytics { + sharePrice: string; + totalSupply: string; + totalAssets: string; + epoch: string; + timestamp: number; + managementFeeShares: string; + performanceFeeShares: string; + apy1Report?: string; + apy2Reports?: string; + apy3Reports?: string; + redeemDelaySeconds?: number; +} + +export interface RedeemRequiredAssets { + epoch: number; + is_current_epoch: boolean; + redeem_assets_required: string; + cumulated_liquidity_required: string; +} @Injectable() export class AppService implements OnModuleInit { strategyDecimals: number; - private readonly logger = Logger.create("AppService"); + private readonly logger = Logger.create('API:Service'); constructor( private readonly prismaService: PrismaService, @@ -20,114 +55,111 @@ export class AppService implements OnModuleInit { async onModuleInit() { try { - const vaultAddress = this.configService.get("VAULT_ADDRESS") as string; - this.logger.info("Initializing AppService", { vaultAddress }); - - this.strategyDecimals = Number( - await this.starknetService.vault_decimals(vaultAddress) - ); - - this.logger.info("AppService initialized successfully", { + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + this.logger.info('Initializing AppService', { vaultAddress }); + this.strategyDecimals = Number(await this.starknetService.vault_decimals(vaultAddress)); + this.logger.info('AppService initialized successfully', { strategyDecimals: this.strategyDecimals, + vaultAddress, }); } catch (error) { - this.logger.error("Failed to initialize AppService", error); + this.logger.error('Failed to initialize AppService', error); throw error; } } getApiInfo() { return { - name: "StarkNet Vault Kit API", - version: "1.0.0", - description: "Backend API for StarkNet Vault Kit", + name: 'StarkNet Vault Kit API', + version: '1.0.0', + description: 'Backend API for StarkNet Vault Kit', endpoints: { - health: "/health", - pendingRedeems: "/pending-redeems/:address", - lastReport: "/reports/last", - redeemById: "/redeems/:id", + health: '/health', + pendingRedeems: '/pending-redeems/:address', + lastReport: '/reports/last', + redeemById: '/redeems/:id', + strategyAnalytics: '/strategy-analytics', + redeemRequiredAssets: '/redeem-required-assets', }, - documentation: "/api", + documentation: '/api', }; } - public async getPendingRedeems( - address: string, - limit?: number, - offset?: number - ): Promise { + public async getPendingRedeems(address: string, limit?: number, offset?: number): Promise { try { const addressToUse = validateAndParseAddress(address); - this.logger.info("Fetching pending redeems", { + this.logger.info('Fetching pending redeems', { address: addressToUse, limit, offset, }); - // Fetch pending redeems directly from PrismaService - const pendingRedeemsForStrategy = - await this.prismaService.fetchPendingRedeemsForAddress( - addressToUse, - limit, - offset - ); + const pendingRedeems: PendingRedeem[] = []; + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + + const lastRedeemRequestedIdForAddress = + await this.prismaService.fetchLastRedeemRequestedIdForAddress(addressToUse); - this.logger.debug("Found pending redeems from database", { + if (lastRedeemRequestedIdForAddress === undefined) { + return []; + } + + const pendingRedeemsForStrategy = await this.prismaService.fetchPendingRedeemsForAddress( + addressToUse, + -1, // Start from the beginning + Number(lastRedeemRequestedIdForAddress), // Up to the last requested ID + limit, + offset + ); + + this.logger.debug('Found pending redeems from database', { count: pendingRedeemsForStrategy.length, }); - // Map database results to PendingRedeem type with parallel processing - const pendingRedeemPromises = pendingRedeemsForStrategy.map( - async (redeem) => { - try { - const vaultAddress = this.configService.get( - "VAULT_ADDRESS" - ) as string; - const dueAssets = - await this.starknetService.vault_due_assets_from_id( - vaultAddress, - Number(redeem.redeemId) - ); - - return { - epoch: Number(redeem.epoch), - sharesBurn: this.formatBigIntToDecimal(redeem.shares), - nominal: this.formatBigIntToDecimal(redeem.assets), - assets: this.formatBigIntToDecimal(dueAssets), - redeemId: redeem.redeemId.toString(), - timestamp: redeem.timestamp, - transactionHash: redeem.transactionHash, - }; - } catch (error) { - this.logger.error("Failed to process pending redeem", error, { - redeemId: redeem.redeemId.toString(), - }); - throw error; - } + const pendingRedeemPromises = pendingRedeemsForStrategy.map(async (redeem) => { + try { + const dueAssets = await this.starknetService.vault_due_assets_from_id(vaultAddress, Number(redeem.redeemId)); + + return { + epoch: Number(redeem.epoch), + sharesBurn: this.formatBigIntToDecimal(redeem.shares), + nominal: this.formatBigIntToDecimal(redeem.assets), + assets: this.formatBigIntToDecimal(dueAssets), + redeemId: redeem.redeemId.toString(), + timestamp: redeem.timestamp, + transactionHash: redeem.transactionHash, + }; + } catch (error) { + this.logger.error('Failed to process pending redeem', error, { + redeemId: redeem.redeemId.toString(), + }); + throw error; } - ); + }); const resolvedPendingRedeems = await Promise.all(pendingRedeemPromises); + resolvedPendingRedeems.sort((a, b) => Number(b.redeemId) - Number(a.redeemId)); + pendingRedeems.push(...resolvedPendingRedeems); - this.logger.info("Successfully processed pending redeems", { + this.logger.info('Successfully processed pending redeems', { address: addressToUse, count: resolvedPendingRedeems.length, }); - return resolvedPendingRedeems; + return pendingRedeems; } catch (error) { - this.logger.error("Failed to get pending redeems", error, { address }); + this.logger.error('Failed to get pending redeems', error, { address }); throw error; } } public async getLastReport() { try { - this.logger.debug("Fetching last report"); + this.logger.debug('Fetching last report'); const lastReport = await this.prismaService.fetchLastReport(); if (!lastReport) { - this.logger.info("No reports found in database"); + this.logger.info('No reports found in database'); return null; } @@ -140,15 +172,11 @@ export class AppService implements OnModuleInit { newHandledEpochLen: lastReport.newHandledEpochLen.toString(), totalSupply: this.formatBigIntToDecimal(lastReport.totalSupply), totalAssets: this.formatBigIntToDecimal(lastReport.totalAssets), - managementFeeShares: this.formatBigIntToDecimal( - lastReport.managementFeeShares - ), - performanceFeeShares: this.formatBigIntToDecimal( - lastReport.performanceFeeShares - ), + managementFeeShares: this.formatBigIntToDecimal(lastReport.managementFeeShares), + performanceFeeShares: this.formatBigIntToDecimal(lastReport.performanceFeeShares), }; - this.logger.info("Successfully fetched last report", { + this.logger.info('Successfully fetched last report', { reportId: result.id, blockNumber: result.blockNumber, epoch: result.newEpoch, @@ -156,22 +184,21 @@ export class AppService implements OnModuleInit { return result; } catch (error) { - this.logger.error("Failed to get last report", error); + this.logger.error('Failed to get last report', error); throw error; } } public async getRedeemById(redeemId: string) { try { - this.logger.info("Fetching redeem by ID", { redeemId }); + this.logger.info('Fetching redeem by ID', { redeemId }); - const redeemRequested = - await this.prismaService.redeemRequested.findUnique({ - where: { redeemId: BigInt(redeemId) }, - }); + const redeemRequested = await this.prismaService.redeemRequested.findUnique({ + where: { redeemId: BigInt(redeemId) }, + }); if (!redeemRequested) { - this.logger.info("Redeem request not found", { redeemId }); + this.logger.info('Redeem request not found', { redeemId }); return null; } @@ -179,24 +206,17 @@ export class AppService implements OnModuleInit { where: { redeemId: BigInt(redeemId) }, }); - // Get current due assets from the contract (only if not claimed yet) let currentDueAssets: bigint | null = null; if (!redeemClaimed) { try { - const vaultAddress = this.configService.get( - "VAULT_ADDRESS" - ) as string; - currentDueAssets = - await this.starknetService.vault_due_assets_from_id( - vaultAddress, - Number(redeemId) - ); - this.logger.debug("Fetched current due assets from contract", { + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + currentDueAssets = await this.starknetService.vault_due_assets_from_id(vaultAddress, Number(redeemId)); + this.logger.debug('Fetched current due assets from contract', { redeemId, dueAssets: currentDueAssets?.toString(), }); } catch (error) { - this.logger.warn("Failed to fetch due assets for redeem ID", { + this.logger.warn('Failed to fetch due assets for redeem ID', { redeemId, error: error.message, }); @@ -210,19 +230,15 @@ export class AppService implements OnModuleInit { epoch: redeemRequested.epoch.toString(), sharesBurn: this.formatBigIntToDecimal(redeemRequested.shares), nominal: this.formatBigIntToDecimal(redeemRequested.assets), - assets: currentDueAssets - ? this.formatBigIntToDecimal(currentDueAssets) - : null, + assets: currentDueAssets ? this.formatBigIntToDecimal(currentDueAssets) : null, requestTimestamp: redeemRequested.timestamp, requestTransactionHash: redeemRequested.transactionHash, claimedTimestamp: redeemClaimed?.timestamp, claimedTransactionHash: redeemClaimed?.transactionHash, - claimedAssets: redeemClaimed - ? this.formatBigIntToDecimal(redeemClaimed.assets) - : null, + claimedAssets: redeemClaimed ? this.formatBigIntToDecimal(redeemClaimed.assets) : null, }; - this.logger.info("Successfully fetched redeem details", { + this.logger.info('Successfully fetched redeem details', { redeemId, isClaimed: !!redeemClaimed, owner: result.owner, @@ -230,14 +246,188 @@ export class AppService implements OnModuleInit { return result; } catch (error) { - this.logger.error("Failed to get redeem by ID", error, { redeemId }); + this.logger.error('Failed to get redeem by ID', error, { redeemId }); + throw error; + } + } + + public async getStrategyAnalytics(limit: number = 10, offset?: number): Promise { + try { + this.logger.info('Fetching strategy analytics', { limit, offset }); + + const reports = await this.prismaService.fetchLastReports(limit, offset); + + if (!reports || reports.length === 0) { + this.logger.info('No reports found for analytics'); + return []; + } + + this.logger.debug('Found reports for analytics', { count: reports.length }); + + const calculateAPY = ( + currentSharePrice: Decimal, + previousSharePrice: Decimal, + currentTimestamp: number, + previousTimestamp: number + ): string => { + const priceRatio = currentSharePrice.div(previousSharePrice); + const growth = priceRatio.sub(1); + const timeDiffSeconds = currentTimestamp - previousTimestamp; + const timeDiffYears = timeDiffSeconds / (365.25 * 24 * 60 * 60); + const annualizedGrowth = growth.div(timeDiffYears); + return annualizedGrowth.mul(100).toString(); + }; + + const analytics = reports.map((report, index) => { + const sharePrice = + report.totalSupply > 0n + ? new Decimal(report.totalAssets.toString()).div(new Decimal(report.totalSupply.toString())) + : new Decimal(1); + + let apy1Report: string | undefined; + let apy2Reports: string | undefined; + let apy3Reports: string | undefined; + + if (index + 1 < reports.length) { + const prevReport = reports[index + 1]; + const prevSharePrice = + prevReport.totalSupply > 0n + ? new Decimal(prevReport.totalAssets.toString()).div(new Decimal(prevReport.totalSupply.toString())) + : new Decimal(1); + apy1Report = calculateAPY(sharePrice, prevSharePrice, report.timestamp, prevReport.timestamp); + } + + if (index + 2 < reports.length) { + const prevReport = reports[index + 2]; + const prevSharePrice = + prevReport.totalSupply > 0n + ? new Decimal(prevReport.totalAssets.toString()).div(new Decimal(prevReport.totalSupply.toString())) + : new Decimal(1); + apy2Reports = calculateAPY(sharePrice, prevSharePrice, report.timestamp, prevReport.timestamp); + } + + if (index + 3 < reports.length) { + const prevReport = reports[index + 3]; + const prevSharePrice = + prevReport.totalSupply > 0n + ? new Decimal(prevReport.totalAssets.toString()).div(new Decimal(prevReport.totalSupply.toString())) + : new Decimal(1); + apy3Reports = calculateAPY(sharePrice, prevSharePrice, report.timestamp, prevReport.timestamp); + } + + let redeemDelaySeconds: number | undefined; + + if (index === 0) { + redeemDelaySeconds = undefined; + } else { + const currentEpoch = Number(report.newEpoch); + const handlingReport = reports.find((r) => Number(r.newHandledEpochLen) >= currentEpoch); + + if (handlingReport) { + redeemDelaySeconds = handlingReport.timestamp - report.timestamp; + } else { + redeemDelaySeconds = undefined; + } + } + + return { + sharePrice: sharePrice.toString(), + totalSupply: this.formatBigIntToDecimal(report.totalSupply), + totalAssets: this.formatBigIntToDecimal(report.totalAssets), + epoch: report.newEpoch.toString(), + timestamp: report.timestamp, + managementFeeShares: this.formatBigIntToDecimal(report.managementFeeShares), + performanceFeeShares: this.formatBigIntToDecimal(report.performanceFeeShares), + apy1Report, + apy2Reports, + apy3Reports, + redeemDelaySeconds, + }; + }); + + this.logger.info('Successfully processed strategy analytics', { + count: analytics.length, + }); + + return analytics; + } catch (error) { + this.logger.error('Failed to get strategy analytics', error); + throw error; + } + } + + public async getRedeemRequiredAssets(): Promise { + try { + this.logger.info('Fetching redeem required assets'); + + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + + let [buffer, epoch, handledEpochLen] = await Promise.all([ + this.starknetService.vault_buffer(vaultAddress), + this.starknetService.vault_epoch(vaultAddress), + this.starknetService.vault_handled_epoch_len(vaultAddress), + ]); + + this.logger.debug('Fetched vault state', { + buffer: buffer?.toString(), + epoch: epoch?.toString(), + handledEpochLen: handledEpochLen?.toString(), + }); + + let redeemRequiredAssets: RedeemRequiredAssets[] = []; + + for (let index = Number(handledEpochLen); index <= Number(epoch); index++) { + const redeemAssets = await this.starknetService.vault_redeem_assets(vaultAddress, index); + + let cumulated_liquidity_required = redeemAssets; + + if (buffer >= redeemAssets) { + buffer -= redeemAssets; + cumulated_liquidity_required = 0n; + } else { + buffer = 0n; + cumulated_liquidity_required = redeemAssets - buffer; + } + + redeemRequiredAssets.push({ + epoch: index, + is_current_epoch: index === Number(epoch), + redeem_assets_required: this.formatBigIntToDecimal(redeemAssets), + cumulated_liquidity_required: this.formatBigIntToDecimal(cumulated_liquidity_required), + }); + } + + this.logger.info('Successfully calculated redeem required assets', { + epochsProcessed: redeemRequiredAssets.length, + }); + + return redeemRequiredAssets; + } catch (error) { + this.logger.error('Failed to get redeem required assets', error); throw error; } } formatBigIntToDecimal = (value: bigint): string => { - return new Decimal(value.toString()) - .div(new Decimal(10).pow(this.strategyDecimals)) - .toString(); + return new Decimal(value.toString()).div(new Decimal(10).pow(this.strategyDecimals)).toFixed(); }; + + async getIndexerStatus() { + const status = await this.prismaService.getIndexerStatus(); + if (!status) { + return { + lastBlock: 0, + updatedAt: null, + synced: false, + message: 'Indexer has not started yet', + }; + } + + return { + lastBlock: status.lastBlock, + updatedAt: status.updatedAt, + synced: true, + message: 'Indexer is running', + }; + } } diff --git a/backend/apps/api/src/health/health.module.ts b/backend/apps/api/src/health/health.module.ts index e0fcc4d3..50c51866 100644 --- a/backend/apps/api/src/health/health.module.ts +++ b/backend/apps/api/src/health/health.module.ts @@ -1,11 +1,11 @@ -import { Module } from "@nestjs/common"; -import { TerminusModule } from "@nestjs/terminus"; -import { HealthController } from "./health.controller"; -import { HttpModule } from "@nestjs/axios"; +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { HealthController } from './health.controller'; +import { HttpModule } from '@nestjs/axios'; @Module({ providers: [], controllers: [HealthController], imports: [TerminusModule, HttpModule], }) -export class HealthModule {} \ No newline at end of file +export class HealthModule {} diff --git a/backend/apps/api/src/main.ts b/backend/apps/api/src/main.ts index a05a891f..31be9327 100644 --- a/backend/apps/api/src/main.ts +++ b/backend/apps/api/src/main.ts @@ -1,32 +1,20 @@ -import { NestFactory } from "@nestjs/core"; -import { ValidationPipe } from "@nestjs/common"; -import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; -import { validateApiConfig } from "@forge/config"; -import { Logger, LogLevel, LoggerConfig } from "@forge/logger"; -import { AppModule } from "./app.module"; +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { validateApiConfig } from '@forge/config'; +import { Logger, initializeLogger } from '@forge/logger'; +import { AppModule } from './app.module'; async function bootstrap() { - // Configure global logger - const loggerConfig: LoggerConfig = { - level: (process.env.LOG_LEVEL as LogLevel) || LogLevel.INFO, - service: "vault-api", - enableConsole: true, - enableFile: process.env.NODE_ENV === "production", - logDir: process.env.LOG_DIR || "logs", - format: process.env.NODE_ENV === "production" ? "json" : "simple", - }; - - Logger.configure(loggerConfig); - const logger = Logger.create("Bootstrap"); + initializeLogger(); + const logger = Logger.create('API:Main'); try { - // Validate environment variables - const envConfig = validateApiConfig(process.env); - logger.info("Environment configuration validated successfully"); + validateApiConfig(process.env); + logger.info('Environment configuration validated successfully'); const app = await NestFactory.create(AppModule); - // Configure global validation pipes app.useGlobalPipes( new ValidationPipe({ whitelist: true, @@ -34,34 +22,29 @@ async function bootstrap() { transform: true, }) ); - logger.info("Global validation pipes configured"); + logger.info('Global validation pipes configured'); - // Enable CORS app.enableCors(); - logger.info("CORS enabled"); - - // Setup Swagger documentation + logger.info('CORS enabled'); const config = new DocumentBuilder() - .setTitle("StarkNet Vault Kit API") - .setDescription("API for StarkNet Vault Kit indexer and services") - .setVersion("1.0") + .setTitle('StarkNet Vault Kit API') + .setDescription('API for StarkNet Vault Kit indexer and services') + .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup("api", app, document); - logger.info("Swagger documentation configured at /api"); + SwaggerModule.setup('api', app, document); + logger.info('Swagger documentation configured at /api'); const port = process.env.PORT || 3000; await app.listen(port); - logger.info("🚀 StarkNet Vault Kit API started successfully", { - port, - environment: process.env.NODE_ENV || "development", - apiUrl: `http://localhost:${port}`, - docsUrl: `http://localhost:${port}/api`, + logger.info(`API server started on port ${port}`, { + environment: process.env.NODE_ENV || 'development', + docs: `/api`, }); } catch (error) { - logger.error("❌ Failed to start API server", error); + logger.error('Failed to start API server', error); process.exit(1); } } diff --git a/backend/apps/api/src/starknet/starknet.service.ts b/backend/apps/api/src/starknet/starknet.service.ts deleted file mode 100644 index 4bd30242..00000000 --- a/backend/apps/api/src/starknet/starknet.service.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { Injectable, OnModuleInit } from "@nestjs/common"; -import { ConfigService } from "@forge/config"; -import { Provider, RpcProvider, Contract } from "starknet"; -import { Logger } from "@forge/logger"; -import * as VAULT_ABI from "./abis/vault.json"; - -@Injectable() -export class StarknetService implements OnModuleInit { - private provider: Provider; - private readonly logger = Logger.create('StarknetService'); - - constructor(private configService: ConfigService) {} - - async onModuleInit() { - try { - const rpcUrl: string = - (this.configService.get("RPC_URL") as string) || - "https://starknet-mainnet.public.blastapi.io"; - - this.logger.info('Initializing StarkNet provider', { rpcUrl }); - this.provider = new RpcProvider({ nodeUrl: rpcUrl }); - this.logger.info('StarkNet provider initialized successfully'); - } catch (error) { - this.logger.error('Failed to initialize StarkNet provider', error); - throw error; - } - } - - async view( - address: string, - abi: any, - functionName: string, - calldata?: any[] - ) { - try { - this.logger.debug('Making contract view call', { - address, - functionName, - calldataLength: calldata?.length || 0 - }); - - const abiArray = Array.isArray(abi) ? abi : Object.values(abi); - const contract = new Contract(abiArray, address, this.provider); - - const result = calldata - ? await contract.call(functionName, calldata) - : await contract.call(functionName); - - this.logger.debug('Contract view call successful', { - address, - functionName, - resultType: typeof result - }); - - return result; - } catch (error) { - this.logger.error('Contract view call failed', error, { - address, - functionName, - calldata - }); - throw error; - } - } - - async vault_due_assets_from_id(address: string, id: number): Promise { - try { - this.logger.debug('Fetching due assets from vault', { address, id }); - - const due_assets = (await this.view( - address, - VAULT_ABI, - "due_assets_from_id", - [id] - )) as any; - - this.logger.debug('Successfully fetched due assets', { - address, - id, - dueAssets: due_assets?.toString() - }); - - return due_assets; - } catch (error) { - this.logger.error('Failed to fetch due assets from vault', error, { - address, - id - }); - throw error; - } - } - - async vault_decimals(address: string): Promise { - try { - this.logger.debug('Fetching vault decimals', { address }); - - const decimals = (await this.provider.callContract({ - contractAddress: address, - entrypoint: "decimals", - })) as any; - - this.logger.debug('Successfully fetched vault decimals', { - address, - decimals: decimals?.toString() - }); - - return decimals; - } catch (error) { - this.logger.error('Failed to fetch vault decimals', error, { address }); - throw error; - } - } -} diff --git a/backend/apps/api/src/types/strategy.ts b/backend/apps/api/src/types/strategy.ts index f94a11ee..ac2e3310 100644 --- a/backend/apps/api/src/types/strategy.ts +++ b/backend/apps/api/src/types/strategy.ts @@ -1,14 +1 @@ -export interface Strategy { - vault: string; - startBlockIndexing: number; -} - -export interface PendingRedeem { - epoch: number; - sharesBurn: string; - nominal: string; - assets: string; - redeemId: string; - timestamp: number; - transactionHash: string; -} \ No newline at end of file +export * from '../app.service'; \ No newline at end of file diff --git a/backend/apps/indexer/README.md b/backend/apps/indexer/README.md deleted file mode 100644 index 6f5dee1c..00000000 --- a/backend/apps/indexer/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Indexer Service - -Apibara-based event indexer for StarkNet Vault Kit smart contracts. - -## Features - -- Real-time event streaming from StarkNet -- Event decoding and processing -- Database persistence with batching -- Automatic reconnection and error handling - -## Development - -```bash -# Run in development mode -pnpm dev:indexer - -# Build -pnpm build:indexer - -# Start production -pnpm --filter indexer start -``` - -## Environment Variables - -- `APIBARA_TOKEN` - Apibara API token -- `STARKNET_RPC_URL` - StarkNet RPC endpoint -- `DATABASE_URL` - PostgreSQL connection string \ No newline at end of file diff --git a/backend/apps/indexer/nest-cli.json b/backend/apps/indexer/nest-cli.json new file mode 100644 index 00000000..ab44b5d0 --- /dev/null +++ b/backend/apps/indexer/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/indexer/src/main" +} \ No newline at end of file diff --git a/backend/apps/indexer/nodemon.json b/backend/apps/indexer/nodemon.json deleted file mode 100644 index ec1b3153..00000000 --- a/backend/apps/indexer/nodemon.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "watch": ["src", "../../../libs"], - "ext": "ts,js,json", - "ignore": ["node_modules", "dist"], - "env": { - "NODE_ENV": "development" - } -} \ No newline at end of file diff --git a/backend/apps/indexer/package.json b/backend/apps/indexer/package.json index 81e68cfe..62fe67bc 100644 --- a/backend/apps/indexer/package.json +++ b/backend/apps/indexer/package.json @@ -5,8 +5,8 @@ "main": "dist/main.js", "scripts": { "build": "tsc", - "dev": "nodemon --exec ts-node src/main.ts", - "start": "node dist/main.js", + "dev": "nest start --watch", + "start": "node dist/apps/indexer/src/main.js", "lint": "eslint \"src/**/*.ts\" --fix" }, "dependencies": { @@ -15,14 +15,11 @@ "@forge/config": "workspace:*", "@forge/db": "workspace:*", "@forge/logger": "workspace:*", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", - "reflect-metadata": "^0.2.0", - "starknet": "^6.0.0" + "starknet": "^7.6.4", + "@nestjs/core": "^10.0.0", + "@nestjs/common": "^10.0.0" }, "devDependencies": { - "@types/node": "^20.3.1", - "ts-node": "^10.9.1", - "nodemon": "^3.0.0" + "ts-node": "^10.9.1" } } \ No newline at end of file diff --git a/backend/apps/indexer/src/indexer.module.ts b/backend/apps/indexer/src/indexer.module.ts new file mode 100644 index 00000000..c7fb35aa --- /dev/null +++ b/backend/apps/indexer/src/indexer.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@forge/config'; +import { PrismaModule } from '@forge/db'; +import { StarknetModule } from '@forge/starknet'; +import { IndexerService } from './indexer.service'; + +@Module({ + imports: [ConfigModule, PrismaModule, StarknetModule], + providers: [IndexerService], + exports: [IndexerService], +}) +export class IndexerModule {} diff --git a/backend/apps/indexer/src/indexer.service.ts b/backend/apps/indexer/src/indexer.service.ts index a3fb7b94..cf2d1609 100644 --- a/backend/apps/indexer/src/indexer.service.ts +++ b/backend/apps/indexer/src/indexer.service.ts @@ -1,25 +1,17 @@ -import { - Filter, - FieldElement, - StarkNetCursor, - v1alpha2, -} from "@apibara/starknet"; -import { StreamClient } from "@apibara/protocol"; -import { DataFinality } from "@apibara/protocol/dist/proto/apibara/node/v1alpha2/DataFinality"; -import { hash as starknetHash, validateAndParseAddress } from "starknet"; -import { ConfigService } from "@forge/config"; -import { PrismaService } from "@forge/db"; -import { Logger } from "@forge/logger"; -import { - decodeReportEvent, - decodeRedeemRequestedEvent, - decodeRedeemClaimedEvent, -} from "./decoder"; +import { Injectable } from '@nestjs/common'; +import { Filter, FieldElement, StarkNetCursor, v1alpha2 } from '@apibara/starknet'; +import { StreamClient } from '@apibara/protocol'; +import { DataFinality } from '@apibara/protocol/dist/proto/apibara/node/v1alpha2/DataFinality'; +import { ConfigService } from '@forge/config'; +import { PrismaService } from '@forge/db'; +import { Logger } from '@forge/logger'; +import { hash, validateAndParseAddress } from 'starknet'; +import { decodeReportEvent, decodeRedeemRequestedEvent, decodeRedeemClaimedEvent } from './decoder'; import { BATCH_SIZE, MAX_RETRY_AFTER_RECONNECT_NO_INTERNAL_ERROR, MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR, -} from "./indexer.constants"; +} from './indexer.constants'; interface EventData { blockNumber: number; @@ -29,17 +21,12 @@ interface EventData { eventTypeHex: string; } +@Injectable() export class IndexerService { - private static readonly EVENT_KEYS = { - redeemRequested: FieldElement.toHex( - FieldElement.fromBigInt(starknetHash.getSelector("RedeemRequested")) - ), - redeemClaimed: FieldElement.toHex( - FieldElement.fromBigInt(starknetHash.getSelector("RedeemClaimed")) - ), - report: FieldElement.toHex( - FieldElement.fromBigInt(starknetHash.getSelector("Report")) - ), + private eventKeys: { + redeemRequested: string; + redeemClaimed: string; + report: string; }; public lastBlockIndexedVault = 0; @@ -56,9 +43,16 @@ export class IndexerService { private readonly configService: ConfigService, private readonly prismaService: PrismaService ) { - this.logger = Logger.create("IndexerService"); - this.url = "mainnet.starknet.a5a.ch"; - this.apibaraToken = this.configService.get("APIBARA_TOKEN") as string; + this.logger = Logger.create('Indexer:Service'); + this.url = 'mainnet.starknet.a5a.ch'; + this.apibaraToken = this.configService.get('APIBARA_TOKEN') as string; + + // Initialize event keys using hash function + this.eventKeys = { + redeemRequested: FieldElement.toHex(FieldElement.fromBigInt(BigInt(hash.getSelector('RedeemRequested')))), + redeemClaimed: FieldElement.toHex(FieldElement.fromBigInt(BigInt(hash.getSelector('RedeemClaimed')))), + report: FieldElement.toHex(FieldElement.fromBigInt(BigInt(hash.getSelector('Report')))), + }; const graceful = async () => { try { @@ -67,8 +61,8 @@ export class IndexerService { process.exit(0); } }; - process.on("SIGINT", graceful); - process.on("SIGTERM", graceful); + process.on('SIGINT', graceful); + process.on('SIGTERM', graceful); } async run() { @@ -80,18 +74,14 @@ export class IndexerService { url: this.url, token: this.apibaraToken, onReconnect: async (err, retryCount) => { - this.logger.error("Connection lost", err, { + this.logger.error('Connection lost', err, { retryCount, errorCode: err.code, }); if (err.code !== 13 && err.code !== 14) { - // Code 13 = internal error, 14 = unavailable return { reconnect: false }; } - const base = Math.min( - MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR, - 1000 * 2 ** Math.min(retryCount, 6) - ); + const base = Math.min(MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR, 1000 * 2 ** Math.min(retryCount, 6)); const jitter = Math.floor(Math.random() * 500); await new Promise((r) => setTimeout(r, base + jitter)); return { @@ -102,62 +92,48 @@ export class IndexerService { const filterBuilder = Filter.create().withHeader({ weak: false }); - const vaultAddress = this.configService.get("VAULT_ADDRESS") as string; + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; try { - this.vaultFe = FieldElement.fromBigInt( - validateAndParseAddress(vaultAddress) - ); + this.vaultFe = FieldElement.fromBigInt(validateAndParseAddress(vaultAddress)); } catch (e) { - this.logger.error("Invalid vault address", e, { vaultAddress }); + this.logger.error('Invalid vault address', e, { vaultAddress }); throw e; } - [ - IndexerService.EVENT_KEYS.redeemRequested, - IndexerService.EVENT_KEYS.redeemClaimed, - IndexerService.EVENT_KEYS.report, - ].forEach((keyHex) => { + [this.eventKeys.redeemRequested, this.eventKeys.redeemClaimed, this.eventKeys.report].forEach((keyHex) => { const key = FieldElement.fromBigInt(BigInt(keyHex)); filterBuilder.addEvent((event) => - event - .withFromAddress(this.vaultFe) - .withIncludeTransaction(true) - .withIncludeReceipt(true) - .withKeys([key]) + event.withFromAddress(this.vaultFe).withIncludeTransaction(true).withIncludeReceipt(true).withKeys([key]) ); }); - let startBlock = Number(this.configService.get("START_BLOCK")) || 0; - const forceStartBlock = this.configService.get("FORCE_START_BLOCK"); + let startBlock = Number(this.configService.get('START_BLOCK')) || 0; + const forceStartBlock = this.configService.get('FORCE_START_BLOCK'); - if (forceStartBlock == "true") { - this.logger.info( - `🔥 Force starting from block: ${startBlock} (FORCE_START_BLOCK=true, using START_BLOCK)` - ); + if (forceStartBlock == 'true') { + this.logger.info(`🔥 Force starting from block: ${startBlock} (FORCE_START_BLOCK=true, using START_BLOCK)`); } else { - const [lastRedeemRequested, lastRedeemClaimed, lastReport] = - await Promise.all([ - this.prismaService.fetchLastRedeemRequested(), - this.prismaService.fetchLastRedeemClaimed(), - this.prismaService.fetchLastReport(), - ]); + const [lastRedeemRequested, lastRedeemClaimed, lastReport, indexerStatus] = await Promise.all([ + this.prismaService.fetchLastRedeemRequested(), + this.prismaService.fetchLastRedeemClaimed(), + this.prismaService.fetchLastReport(), + this.prismaService.getIndexerStatus(), + ]); startBlock = Math.max( lastRedeemRequested?.blockNumber || 0, lastRedeemClaimed?.blockNumber || 0, lastReport?.blockNumber || 0, + indexerStatus?.lastBlock || 0, startBlock ); - this.logger.info( - `📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`, - { - startBlock, - lastRedeemRequestedBlock: lastRedeemRequested?.blockNumber || 0, - lastRedeemClaimedBlock: lastRedeemClaimed?.blockNumber || 0, - lastReportBlock: lastReport?.blockNumber || 0, - } - ); + this.logger.info(`📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`, { + startBlock, + lastRedeemRequestedBlock: lastRedeemRequested?.blockNumber || 0, + lastRedeemClaimedBlock: lastRedeemClaimed?.blockNumber || 0, + lastReportBlock: lastReport?.blockNumber || 0, + }); } const cursor = StarkNetCursor.createWithBlockNumber(startBlock); @@ -175,16 +151,16 @@ export class IndexerService { const blockNumber = block.header?.blockNumber; const timestamp = block.header?.timestamp?.seconds; if (!blockNumber) { - throw new Error("No block number"); + throw new Error('No block number'); } const blockNum = +blockNumber; if (!timestamp) { - throw new Error("No timestamp in block header"); + throw new Error('No timestamp in block header'); } - this.logger.debug("Processing block", { + this.logger.debug('Processing block', { blockNumber: blockNum, lastBlockIndexed: this.lastBlockIndexedVault, timestamp: Number(timestamp), @@ -194,19 +170,19 @@ export class IndexerService { const hash = event.transaction?.meta?.hash; if (!hash) { - throw new Error("No hash"); + throw new Error('No hash'); } const hashHex = FieldElement.toHex(hash); const eventType = event.event?.keys?.[0]; if (!eventType) { - throw new Error("No event type"); + throw new Error('No event type'); } const eventTypeHex = FieldElement.toHex(eventType); const data = event.event?.data; if (!data) { - throw new Error("No data"); + throw new Error('No data'); } const hexData: string[] = data.map((item) => { try { @@ -216,7 +192,7 @@ export class IndexerService { } }); - this.logger.debug("Processing event", { + this.logger.debug('Processing event', { eventType: this.getEventTypeName(eventTypeHex), blockNumber: blockNum, transactionHash: hashHex, @@ -232,7 +208,7 @@ export class IndexerService { eventTypeHex, }); } catch (error) { - this.logger.error("Failed to process event", error, { + this.logger.error('Failed to process event', error, { blockNumber: blockNum, transactionHash: hashHex, eventType: this.getEventTypeName(eventTypeHex), @@ -245,49 +221,32 @@ export class IndexerService { if (this.lastBlockIndexedVault < blockNum) { this.lastBlockIndexedVault = blockNum; + await this.prismaService.updateIndexerStatus(blockNum); + this.logger.debug('Updated indexer status', { lastBlock: blockNum }); } } } } await this.flushAllBuffers(); - throw new Error("Stream ended without reconnect"); + throw new Error('Stream ended without reconnect'); } private formatStarknetAddress(value: bigint): string { - return "0x" + value.toString(16).padStart(64, "0"); + return '0x' + value.toString(16).padStart(64, '0'); } - private async processEvent( - eventTypeHex: string, - hexData: string[], - eventData: EventData - ): Promise { + private async processEvent(eventTypeHex: string, hexData: string[], eventData: EventData): Promise { const { blockNumber, timestamp, transactionHash } = eventData; - if (eventTypeHex === IndexerService.EVENT_KEYS.redeemRequested) { - await this.bufferRedeemRequestedEvent( - hexData, - blockNumber, - timestamp, - transactionHash - ); - } else if (eventTypeHex === IndexerService.EVENT_KEYS.redeemClaimed) { - await this.bufferRedeemClaimedEvent( - hexData, - blockNumber, - timestamp, - transactionHash - ); - } else if (eventTypeHex === IndexerService.EVENT_KEYS.report) { - await this.bufferReportEvent( - hexData, - blockNumber, - timestamp, - transactionHash - ); + if (eventTypeHex === this.eventKeys.redeemRequested) { + await this.bufferRedeemRequestedEvent(hexData, blockNumber, timestamp, transactionHash); + } else if (eventTypeHex === this.eventKeys.redeemClaimed) { + await this.bufferRedeemClaimedEvent(hexData, blockNumber, timestamp, transactionHash); + } else if (eventTypeHex === this.eventKeys.report) { + await this.bufferReportEvent(hexData, blockNumber, timestamp, transactionHash); } else { - this.logger.warn("Unknown event type", { eventTypeHex }); + this.logger.warn('Unknown event type', { eventTypeHex }); } } @@ -303,12 +262,8 @@ export class IndexerService { blockNumber, timestamp, transactionHash, - owner: validateAndParseAddress( - this.formatStarknetAddress(redeemRequested.owner) - ), - receiver: validateAndParseAddress( - this.formatStarknetAddress(redeemRequested.receiver) - ), + owner: validateAndParseAddress(this.formatStarknetAddress(redeemRequested.owner)), + receiver: validateAndParseAddress(this.formatStarknetAddress(redeemRequested.receiver)), shares: redeemRequested.shares, assets: redeemRequested.assets, redeemId: redeemRequested.redeemId, @@ -321,7 +276,7 @@ export class IndexerService { await this.flushRedeemRequestedBuffer(); } } catch (error) { - this.logger.error("Failed to process RedeemRequested event", error, { + this.logger.error('Failed to process RedeemRequested event', error, { blockNumber, transactionHash, }); @@ -340,9 +295,7 @@ export class IndexerService { blockNumber, timestamp, transactionHash, - receiver: validateAndParseAddress( - this.formatStarknetAddress(redeemClaimed.receiver) - ), + receiver: validateAndParseAddress(this.formatStarknetAddress(redeemClaimed.receiver)), redeemRequestNominal: redeemClaimed.redeemRequestNominal, assets: redeemClaimed.assets, redeemId: redeemClaimed.redeemId, @@ -355,7 +308,7 @@ export class IndexerService { await this.flushRedeemClaimedBuffer(); } } catch (error) { - this.logger.error("Failed to process RedeemClaimed event", error, { + this.logger.error('Failed to process RedeemClaimed event', error, { blockNumber, transactionHash, }); @@ -388,7 +341,7 @@ export class IndexerService { await this.flushReportBuffer(); } } catch (error) { - this.logger.error("Failed to process Report event", error, { + this.logger.error('Failed to process Report event', error, { blockNumber, transactionHash, }); @@ -403,12 +356,12 @@ export class IndexerService { data: this.redeemRequestedBuffer, skipDuplicates: true, }); - this.logger.info("Flushed RedeemRequested events", { + this.logger.info('Flushed RedeemRequested events', { count: this.redeemRequestedBuffer.length, }); this.redeemRequestedBuffer = []; } catch (err) { - this.logger.error("Failed to flush RedeemRequested buffer", err); + this.logger.error('Failed to flush RedeemRequested buffer', err); throw err; } } @@ -421,12 +374,12 @@ export class IndexerService { data: this.redeemClaimedBuffer, skipDuplicates: true, }); - this.logger.info("Flushed RedeemClaimed events", { + this.logger.info('Flushed RedeemClaimed events', { count: this.redeemClaimedBuffer.length, }); this.redeemClaimedBuffer = []; } catch (err) { - this.logger.error("Failed to flush RedeemClaimed buffer", err); + this.logger.error('Failed to flush RedeemClaimed buffer', err); throw err; } } @@ -439,31 +392,25 @@ export class IndexerService { data: this.reportBuffer, skipDuplicates: true, }); - this.logger.info("Flushed Report events", { + this.logger.info('Flushed Report events', { count: this.reportBuffer.length, }); this.reportBuffer = []; } catch (err) { - this.logger.error("Failed to flush Report buffer", err); + this.logger.error('Failed to flush Report buffer', err); throw err; } } private async flushAllBuffers(): Promise { - await Promise.all([ - this.flushRedeemRequestedBuffer(), - this.flushRedeemClaimedBuffer(), - this.flushReportBuffer(), - ]); + await Promise.all([this.flushRedeemRequestedBuffer(), this.flushRedeemClaimedBuffer(), this.flushReportBuffer()]); } private getEventTypeName(eventTypeHex: string): string { - if (eventTypeHex === IndexerService.EVENT_KEYS.redeemRequested) - return "RedeemRequested"; - if (eventTypeHex === IndexerService.EVENT_KEYS.redeemClaimed) - return "RedeemClaimed"; - if (eventTypeHex === IndexerService.EVENT_KEYS.report) return "Report"; - return "Unknown"; + if (eventTypeHex === this.eventKeys.redeemRequested) return 'RedeemRequested'; + if (eventTypeHex === this.eventKeys.redeemClaimed) return 'RedeemClaimed'; + if (eventTypeHex === this.eventKeys.report) return 'Report'; + return 'Unknown'; } getStatus() { diff --git a/backend/apps/indexer/src/main.ts b/backend/apps/indexer/src/main.ts index bbceb387..c0c2092c 100644 --- a/backend/apps/indexer/src/main.ts +++ b/backend/apps/indexer/src/main.ts @@ -1,60 +1,31 @@ -import "reflect-metadata"; -import { ConfigService, validateIndexerConfig } from "@forge/config"; -import { PrismaService } from "@forge/db"; -import { Logger, LogLevel, LoggerConfig } from "@forge/logger"; -import { IndexerService } from "./indexer.service"; +import { NestFactory } from '@nestjs/core'; +import { validateIndexerConfig } from '@forge/config'; +import { Logger, initializeLogger } from '@forge/logger'; +import { IndexerModule } from './indexer.module'; +import { IndexerService } from './indexer.service'; async function bootstrap() { - // Configure global logger - const loggerConfig: LoggerConfig = { - level: (process.env.LOG_LEVEL as LogLevel) || LogLevel.INFO, - service: 'vault-indexer', - enableConsole: true, - enableFile: process.env.NODE_ENV === 'production', - logDir: process.env.LOG_DIR || 'logs', - format: process.env.NODE_ENV === 'production' ? 'json' : 'simple', - }; - - Logger.configure(loggerConfig); - const logger = Logger.create('Bootstrap'); + initializeLogger(); + const logger = Logger.create('Indexer:Main'); try { - // Validate environment variables - const config = validateIndexerConfig(process.env); + validateIndexerConfig(process.env); logger.info('Environment configuration validated successfully'); - // Create services - const configService = new ConfigService({}); - const prismaService = new PrismaService(); + const app = await NestFactory.create(IndexerModule); + const indexerService = app.get(IndexerService); - // Connect to database - await prismaService.$connect(); - logger.info('Database connection established'); + logger.info('Starting StarkNet Vault Kit Indexer'); - logger.info('🔥 Starting StarkNet Vault Kit Indexer...'); - - // Create and start indexer - const indexer = new IndexerService(configService, prismaService); - - await indexer.run(); + await indexerService.run(); } catch (error) { - logger.error('❌ Indexer failed', error); - - // Attempt graceful cleanup - try { - const prismaService = new PrismaService(); - await prismaService.$disconnect(); - logger.info('Database connection closed gracefully'); - } catch (cleanupError) { - logger.error('Failed to cleanup database connection', cleanupError); - } - + logger.error('Indexer failed', error); process.exit(1); } } bootstrap().catch((error) => { - const logger = Logger.create('Bootstrap'); - logger.error('❌ Bootstrap failed', error); + const logger = Logger.create('Indexer:Main'); + logger.error('Bootstrap failed', error); process.exit(1); }); diff --git a/backend/apps/relayerAutomaticRedeem/nest-cli.json b/backend/apps/relayerAutomaticRedeem/nest-cli.json new file mode 100644 index 00000000..98e5edb5 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/relayerAutomaticRedeem/src/main" +} \ No newline at end of file diff --git a/backend/apps/relayerAutomaticRedeem/package.json b/backend/apps/relayerAutomaticRedeem/package.json new file mode 100644 index 00000000..523506e6 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/package.json @@ -0,0 +1,25 @@ +{ + "name": "relayerAutomaticRedeem", + "version": "0.0.1", + "description": "Automatic redeem relayer for StarkNet Vault Kit - processes pending redeems automatically", + "main": "dist/main.js", + "scripts": { + "build": "tsc", + "dev": "nest start --watch", + "start": "node dist/main.js", + "lint": "eslint \"src/**/*.ts\" --fix" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/db": "workspace:*", + "@forge/logger": "workspace:*", + "@forge/starknet": "workspace:*", + "cron": "^4.3.3", + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" + }, + "devDependencies": { + "@types/cron": "^2.0.0", + "ts-node": "^10.9.1" + } +} \ No newline at end of file diff --git a/backend/apps/relayerAutomaticRedeem/src/main.ts b/backend/apps/relayerAutomaticRedeem/src/main.ts new file mode 100644 index 00000000..c4422df8 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/main.ts @@ -0,0 +1,53 @@ +import { NestFactory } from "@nestjs/core"; +import { validateRelayerAutomaticRedeemConfig } from "@forge/config"; +import { Logger, initializeLogger } from "@forge/logger"; +import { RelayerAutomaticRedeemModule } from "./relayerAutomaticRedeem.module"; +import { RelayerAutomaticRedeemService } from "./relayerAutomaticRedeem.service"; + +async function bootstrap() { + initializeLogger(); + const logger = Logger.create("RelayerAutomaticRedeem:Main"); + + try { + validateRelayerAutomaticRedeemConfig(process.env); + logger.info('Environment configuration validated successfully'); + + const app = await NestFactory.create(RelayerAutomaticRedeemModule); + await app.init(); + const relayerAutomaticRedeemService = app.get(RelayerAutomaticRedeemService); + + logger.info("Starting StarkNet Vault Kit Relayer"); + + let shouldShutdown = false; + const gracefulShutdown = async (signal: string) => { + logger.info(`Received ${signal}, shutting down gracefully`); + shouldShutdown = true; + await app.close(); + process.exit(0); + }; + + process.on("SIGINT", () => gracefulShutdown("SIGINT")); + process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); + + while (!shouldShutdown) { + try { + await relayerAutomaticRedeemService.execute(); + + logger.debug("Waiting 5 minutes before next execution"); + await new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000)); + } catch (error) { + logger.error("Relayer execution failed:", error); + + logger.warn("Waiting 1 minute before retry after error"); + await new Promise(resolve => setTimeout(resolve, 1 * 60 * 1000)); + } + } + + logger.info("Relayer started successfully"); + } catch (error) { + logger.error("Bootstrap failed", error); + process.exit(1); + } +} + +bootstrap(); diff --git a/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.constants.ts b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.constants.ts new file mode 100644 index 00000000..ef680b46 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.constants.ts @@ -0,0 +1,4 @@ +export const SLEEP_TIME_AFTER_AUTO_REDEEM_CHECK = 30 * 60 * 1000; // 30 minutes +export const SLEEP_TIME_AFTER_AUTO_REDEEM_CHECK_FAILED = 10 * 60 * 1000; // 10 minutes +export const SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS = 10000; // 10 seconds +export const BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS = 25; diff --git a/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.module.ts b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.module.ts new file mode 100644 index 00000000..265418b1 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.module.ts @@ -0,0 +1,12 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule } from "@forge/config"; +import { PrismaModule } from "@forge/db"; +import { StarknetModule } from "@forge/starknet"; +import { RelayerAutomaticRedeemService } from "./relayerAutomaticRedeem.service"; + +@Module({ + imports: [ConfigModule, PrismaModule, StarknetModule], + providers: [RelayerAutomaticRedeemService], + exports: [RelayerAutomaticRedeemService], +}) +export class RelayerAutomaticRedeemModule {} \ No newline at end of file diff --git a/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.service.ts b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.service.ts new file mode 100644 index 00000000..e2782c99 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.service.ts @@ -0,0 +1,234 @@ +import { Logger } from '@forge/logger'; +import { PrismaService } from '@forge/db'; +import { ConfigService } from '@forge/config'; +import { StarknetService } from '@forge/starknet'; +import { + SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS, + BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS, +} from './relayerAutomaticRedeem.constants'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class RelayerAutomaticRedeemService { + private logger = Logger.create('RelayerAutomaticRedeem:Service'); + private isRunning = false; + + constructor( + private prismaService: PrismaService, + private starknetService: StarknetService, + private configService: ConfigService + ) {} + + async execute(): Promise { + if (this.isRunning) { + this.logger.debug('Auto-redeem job is already running, skipping execution'); + return; + } + + this.isRunning = true; + try { + await this.processAutoRedeem(); + } catch (error) { + this.logger.error('Auto-redeem job failed:', error); + } finally { + this.isRunning = false; + } + } + + private async processAutoRedeem(): Promise { + try { + this.logger.info('Starting auto-redeem process'); + + const indexerStatus = await this.prismaService.getIndexerStatus(); + if (!indexerStatus) { + this.logger.info('Indexer has not started yet, skipping auto-redeem'); + return; + } + + const currentBlock = await this.starknetService.getCurrentBlock(); + const blockDifference = currentBlock - indexerStatus.lastBlock; + + if (blockDifference > 10) { + this.logger.info('Indexer is not synced, waiting for synchronization', { + currentBlock, + lastIndexedBlock: indexerStatus.lastBlock, + blocksBehind: blockDifference, + }); + return; + } + + this.logger.info('Indexer is synced', { + currentBlock, + lastIndexedBlock: indexerStatus.lastBlock, + blockDifference, + }); + + const lastReport = await this.prismaService.fetchLastReport(); + + if (!lastReport) { + this.logger.info('No reports found, skipping auto-redeem'); + return; + } + + this.logger.info('Found last report', { + reportId: lastReport.id, + epoch: lastReport.newEpoch?.toString(), + handledEpochLen: lastReport.newHandledEpochLen?.toString(), + }); + + const pendingRedeems = await this.findPendingRedeems(lastReport); + + if (pendingRedeems.length === 0) { + this.logger.info('No pending redeems found for auto-redeem'); + return; + } + + this.logger.info(`Found ${pendingRedeems.length} pending redeems for processing`); + + await this.processPendingRedeemsBatches(pendingRedeems); + } catch (error) { + this.logger.error('Error in auto-redeem process:', error); + throw error; + } + } + + private async findPendingRedeems(lastReport: any): Promise { + try { + const pendingRedeems = await this.prismaService.redeemRequested.findMany({ + where: { + redeemId: { + notIn: await this.prismaService.redeemClaimed + .findMany({ + select: { redeemId: true }, + }) + .then((claims) => claims.map((c) => c.redeemId)), + }, + epoch: { + lte: lastReport.newHandledEpochLen || 0n, + }, + }, + orderBy: { redeemId: 'asc' }, + take: BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS * 5, + }); + + this.logger.debug('Found pending redeems from database', { + count: pendingRedeems.length, + epochFilter: lastReport.newHandledEpochLen?.toString(), + }); + + return pendingRedeems; + } catch (error) { + this.logger.error('Error finding pending redeems:', error); + return []; + } + } + + private async processPendingRedeemsBatches(pendingRedeems: any[]): Promise { + const batches = this.chunkArray(pendingRedeems, BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS); + + this.logger.info(`Processing ${batches.length} batches of redeems`); + + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + try { + this.logger.info(`Processing batch ${i + 1}/${batches.length} with ${batch.length} redeems`); + + await this.processBatch(batch); + + if (i < batches.length - 1) { + this.logger.debug(`Sleeping for ${SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS}ms before next batch`); + await this.sleep(SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS); + } + } catch (error) { + this.logger.error(`Error processing batch ${i + 1}:`, error); + } + } + } + + private async processBatch(batch: any[]): Promise { + try { + const redeemIds = batch.map((redeem) => redeem.redeemId); + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + const strategySymbol = (this.configService.get('STRATEGY_SYMBOL') as string) || 'default'; + + if (!vaultAddress) { + throw new Error('VAULT_ADDRESS configuration is required for auto-redeem'); + } + + this.logger.info('Processing redeem batch', { + vaultAddress, + strategySymbol, + redeemIds: redeemIds.map((id) => id.toString()), + batchSize: batch.length, + }); + + this.logger.info('Submitting claim redeem transaction to vault'); + const transactionHash = await this.starknetService.vault_claim_redeem(vaultAddress, redeemIds); + + this.logger.info('Transaction confirmed', { + transactionHash, + processedRedeemIds: redeemIds.map((id) => id.toString()), + }); + + await this.updateClaimRecords(batch, transactionHash); + + this.logger.info('Batch processed successfully', { + transactionHash, + processedCount: batch.length, + }); + } catch (error) { + this.logger.error('Error processing batch:', error); + throw error; + } + } + + private async updateClaimRecords(batch: any[], transactionHash: string): Promise { + try { + this.logger.info('Updating claim records in database', { + batchSize: batch.length, + transactionHash, + }); + + const currentTimestamp = Math.floor(Date.now() / 1000); + + const claimRecords = batch.map((redeem) => ({ + redeemId: redeem.redeemId, + receiver: redeem.receiver, + redeemRequestNominal: redeem.assets, + assets: redeem.assets, + epoch: redeem.epoch, + transactionHash: transactionHash, + timestamp: currentTimestamp, + blockNumber: 0, + })); + + await this.prismaService.redeemClaimed.createMany({ + data: claimRecords, + skipDuplicates: true, + }); + + this.logger.info('Successfully updated claim records', { + recordsCreated: claimRecords.length, + transactionHash, + }); + } catch (error) { + this.logger.error('Failed to update claim records in database', error, { + transactionHash, + batchSize: batch.length, + }); + this.logger.warn('Transaction was successful but database update failed - indexer will handle this'); + } + } + + private chunkArray(array: T[], chunkSize: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < array.length; i += chunkSize) { + chunks.push(array.slice(i, i + chunkSize)); + } + return chunks; + } + + private async sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} diff --git a/backend/apps/relayerAutomaticRedeem/tsconfig.json b/backend/apps/relayerAutomaticRedeem/tsconfig.json new file mode 100644 index 00000000..997d1e4f --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "composite": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] +} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/nest-cli.json b/backend/apps/relayerOnChainAum/nest-cli.json new file mode 100644 index 00000000..5031e8a1 --- /dev/null +++ b/backend/apps/relayerOnChainAum/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/relayerOnChainAum/src/main" +} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/package.json b/backend/apps/relayerOnChainAum/package.json new file mode 100644 index 00000000..635d5299 --- /dev/null +++ b/backend/apps/relayerOnChainAum/package.json @@ -0,0 +1,21 @@ +{ + "name": "relayerOnChainAum", + "version": "0.0.1", + "description": "On-chain AUM relayer for StarkNet Vault Kit", + "main": "dist/main.js", + "scripts": { + "build": "nest build", + "dev": "nest start --watch", + "start": "node dist/apps/relayerOnChainAum/src/main.js", + "lint": "eslint \"src/**/*.ts\" --fix", + "test": "jest" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/logger": "workspace:*", + "@forge/starknet": "workspace:*", + "@nestjs/core": "^10.0.0", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^4.0.0" + } +} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/src/app.module.ts b/backend/apps/relayerOnChainAum/src/app.module.ts new file mode 100644 index 00000000..8b39083b --- /dev/null +++ b/backend/apps/relayerOnChainAum/src/app.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule as NestConfigModule } from '@nestjs/config'; +import { ConfigModule } from '@forge/config'; +import { StarknetModule } from '@forge/starknet'; +import { RelayerService } from './relayer.service'; + +@Module({ + imports: [ + NestConfigModule.forRoot({ + isGlobal: true, + }), + ConfigModule, + StarknetModule, + ], + providers: [RelayerService], +}) +export class AppModule {} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/src/main.ts b/backend/apps/relayerOnChainAum/src/main.ts new file mode 100644 index 00000000..abc8bfa8 --- /dev/null +++ b/backend/apps/relayerOnChainAum/src/main.ts @@ -0,0 +1,24 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import { Logger, initializeLogger } from '@forge/logger'; +import { validateRelayerOnChainAumConfig } from '@forge/config'; + +async function bootstrap() { + initializeLogger(); + const logger = Logger.create('RelayerOnChainAum'); + + try { + validateRelayerOnChainAumConfig(process.env); + logger.info('Environment configuration validated successfully'); + } catch (error) { + logger.error('Environment validation failed', error); + process.exit(1); + } + await NestFactory.createApplicationContext(AppModule, { + logger, + }); + + logger.log('RelayerOnChainAum service started'); +} + +bootstrap(); \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/src/relayer.service.ts b/backend/apps/relayerOnChainAum/src/relayer.service.ts new file mode 100644 index 00000000..acdcf494 --- /dev/null +++ b/backend/apps/relayerOnChainAum/src/relayer.service.ts @@ -0,0 +1,92 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@forge/config'; +import { StarknetService } from '@forge/starknet'; +import { Logger } from '@forge/logger'; + +@Injectable() +export class RelayerService implements OnModuleInit { + private readonly logger = Logger.create('RelayerOnChainAum:Service'); + private vaultAddress: string; + private onChainAumProvider: string; + + private isRunning = false; + + constructor( + private configService: ConfigService, + private starknetService: StarknetService + ) {} + + async onModuleInit() { + this.vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + this.onChainAumProvider = this.configService.get('ON_CHAIN_AUM_PROVIDER') as string; + + this.logger.info('RelayerOnChainAum service initialized', { + vaultAddress: this.vaultAddress, + onChainAumProvider: this.onChainAumProvider, + }); + + // Start the continuous monitoring loop + this.startMonitoring(); + } + + private async startMonitoring() { + if (this.isRunning) return; + this.isRunning = true; + + this.logger.info('Starting AUM reporting monitoring loop'); + + while (this.isRunning) { + try { + const nextReportTime = await this.getNextReportTime(); + const now = await this.starknetService.getCurrentBlockTimestamp(); + + if (nextReportTime <= now) { + this.logger.info('Report is ready, triggering AUM provider report'); + await this.starknetService.triggerAumProviderReport(this.onChainAumProvider); + + await this.sleep(60 * 1000); + } else { + const sleepTime = (nextReportTime - now) * 1000; // Convert to milliseconds + const sleepMinutes = Math.round(sleepTime / (60 * 1000)); + + this.logger.info(`Next report ready in ${sleepMinutes} minutes, sleeping until then`, { + nextReportTime, + currentTime: now, + sleepTimeMs: sleepTime, + }); + + await this.sleep(sleepTime); + } + } catch (error) { + this.logger.error('Error in monitoring loop, retrying in 5 minutes', error); + await this.sleep(5 * 60 * 1000); // Sleep 5 minutes on error + } + } + } + + private async getNextReportTime(): Promise { + const [lastReportTimestamp, reportDelay] = await Promise.all([ + this.starknetService.getLastReportTimestamp(this.vaultAddress), + this.starknetService.getReportDelay(this.vaultAddress), + ]); + + const nextReportTime = Number(lastReportTimestamp) + Number(reportDelay); + + this.logger.debug('Calculated next report time', { + lastReportTimestamp: lastReportTimestamp.toString(), + reportDelay: reportDelay.toString(), + nextReportTime, + }); + + return nextReportTime; + } + + private sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + public stop() { + this.logger.info('Stopping AUM reporting monitoring'); + this.isRunning = false; + } +} diff --git a/backend/apps/relayerOnChainAum/tsconfig.json b/backend/apps/relayerOnChainAum/tsconfig.json new file mode 100644 index 00000000..1d63594f --- /dev/null +++ b/backend/apps/relayerOnChainAum/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "./dist" + }, + "include": ["src/**/*"] +} \ No newline at end of file diff --git a/backend/docker-compose.dev.yml b/backend/docker-compose.dev.yml index 662696b7..a53b97d8 100644 --- a/backend/docker-compose.dev.yml +++ b/backend/docker-compose.dev.yml @@ -1,8 +1,6 @@ -# version: '3.8' # obsolete attribute - services: postgres: - image: postgres:15 + image: postgres:15-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -10,18 +8,49 @@ services: volumes: - postgres_data:/var/lib/postgresql/data ports: - - "5432:5432" + - '5432:5432' healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: ['CMD-SHELL', 'pg_isready -U postgres'] interval: 10s timeout: 5s retries: 5 + api: + build: + context: . + dockerfile: Dockerfile.api.dev + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + volumes: + - ./apps/api:/app/apps/api + - ./libs:/app/libs + ports: + - '3000:3000' + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + command: > + sh -c " + until nc -z postgres 5432; + do + echo '⏳ Waiting for Postgres...'; + sleep 2; + done; + echo '✅ Postgres is up! Generating Prisma client...'; + pnpm prisma generate --schema=libs/db/prisma/schema.prisma; + echo '✅ Prisma client generated! Running migrations...'; + pnpm prisma migrate deploy --schema=libs/db/prisma/schema.prisma; + echo '✅ Migrations done! Starting API with hot reload...'; + pnpm run dev:api + " + indexer: build: context: . - dockerfile: Dockerfile.dev - target: indexer + dockerfile: Dockerfile.indexer.dev environment: - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} @@ -30,43 +59,95 @@ services: - START_BLOCK=${START_BLOCK} - FORCE_START_BLOCK=${FORCE_START_BLOCK} volumes: - - .:/app + - ./apps/indexer:/app/apps/indexer + - ./libs:/app/libs depends_on: - postgres: - condition: service_healthy + api: + condition: service_started restart: unless-stopped command: > sh -c " - echo '✅ Starting migration and indexer...'; - npx prisma migrate deploy; - echo '✅ Migration done! Starting Indexer...'; + until nc -z api 3000; + do + echo '⏳ Waiting for API (ready after Prisma)...'; + sleep 2; + done; + echo '✅ API is ready! Installing dependencies...'; pnpm run dev:indexer " - api: + relayer-automatic-redeem: build: context: . - dockerfile: Dockerfile.dev - target: api + dockerfile: Dockerfile.relayerAutomaticRedeem.dev environment: - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} - VAULT_ADDRESS=${VAULT_ADDRESS} + - RELAYER_ADDRESS=${RELAYER_ADDRESS} + - RELAYER_PRIVATE_KEY=${RELAYER_PRIVATE_KEY} + - CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *} volumes: - - .:/app - ports: - - "3000:3000" + - ./apps/relayerAutomaticRedeem:/app/apps/relayerAutomaticRedeem + - ./libs:/app/libs depends_on: - postgres: - condition: service_healthy + api: + condition: service_started restart: unless-stopped command: > sh -c " - echo '✅ Starting migration and API...'; - npx prisma migrate deploy; - echo '✅ Migration done! Starting API...'; - pnpm run dev:api + until nc -z api 3000; + do + echo '⏳ Waiting for API (ready after migration)...'; + sleep 2; + done; + echo '✅ API is ready! Installing dependencies...'; + pnpm run dev:relayerAutomaticRedeem + " + + relayer-on-chain-aum: + build: + context: . + dockerfile: Dockerfile.relayerOnChainAum.dev + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - RELAYER_ADDRESS=${RELAYER_ADDRESS} + - RELAYER_PRIVATE_KEY=${RELAYER_PRIVATE_KEY} + - ON_CHAIN_AUM_PROVIDER=${ON_CHAIN_AUM_PROVIDER} + volumes: + - ./apps/relayerOnChainAum:/app/apps/relayerOnChainAum + - ./libs:/app/libs + depends_on: + api: + condition: service_started + restart: unless-stopped + command: > + sh -c " + until nc -z api 3000; + do + echo '⏳ Waiting for API (ready after migration)...'; + sleep 2; + done; + echo '✅ API is ready! Starting on-chain AUM relayer...'; + pnpm run dev:relayerOnChainAum " + pgadmin: + image: dpage/pgadmin4 + container_name: forge-pgadmin-dev + restart: unless-stopped + environment: + PGADMIN_DEFAULT_EMAIL: admin@admin.com + PGADMIN_DEFAULT_PASSWORD: admin + ports: + - '5050:80' + depends_on: + - postgres + volumes: + - pgadmin_data:/var/lib/pgadmin + volumes: - postgres_data: \ No newline at end of file + postgres_data: + pgadmin_data: diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml deleted file mode 100644 index 99b4a60c..00000000 --- a/backend/docker-compose.yml +++ /dev/null @@ -1,66 +0,0 @@ -# version: '3.8' # obsolete attribute - -services: - postgres: - image: postgres:15 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: starknet_vault_kit - volumes: - - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 5s - retries: 5 - - indexer: - build: - context: . - dockerfile: Dockerfile.indexer - environment: - - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit - - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} - - APIBARA_TOKEN=${APIBARA_TOKEN} - - VAULT_ADDRESS=${VAULT_ADDRESS} - - START_BLOCK=${START_BLOCK} - - FORCE_START_BLOCK=${FORCE_START_BLOCK} - depends_on: - postgres: - condition: service_healthy - restart: unless-stopped - command: > - sh -c " - echo '✅ Starting migration and indexer...'; - npx prisma migrate deploy; - echo '✅ Migration done! Starting Indexer...'; - node dist/apps/indexer/main - " - - api: - build: - context: . - dockerfile: Dockerfile.api - environment: - - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit - - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} - - VAULT_ADDRESS=${VAULT_ADDRESS} - ports: - - "3000:3000" - depends_on: - postgres: - condition: service_healthy - restart: unless-stopped - command: > - sh -c " - echo '✅ Starting migration and API...'; - npx prisma migrate deploy; - echo '✅ Migration done! Starting API...'; - node dist/apps/api/main - " - -volumes: - postgres_data: \ No newline at end of file diff --git a/backend/libs/config/src/config.module.ts b/backend/libs/config/src/config.module.ts index f4c9e875..f52317f1 100644 --- a/backend/libs/config/src/config.module.ts +++ b/backend/libs/config/src/config.module.ts @@ -1,16 +1,14 @@ import { Module } from "@nestjs/common"; import { ConfigModule as NestConfigModule } from "@nestjs/config"; import { ConfigService } from "./config.service"; -import { validate } from "./env.validation"; @Module({ imports: [ NestConfigModule.forRoot({ isGlobal: true, - validate, }), ], providers: [ConfigService], exports: [ConfigService], }) -export class ConfigModule {} \ No newline at end of file +export class ConfigModule {} diff --git a/backend/libs/config/src/config.service.ts b/backend/libs/config/src/config.service.ts index 9ef6f44a..a1bb7298 100644 --- a/backend/libs/config/src/config.service.ts +++ b/backend/libs/config/src/config.service.ts @@ -1,7 +1,11 @@ -import { ConfigService as ConfigServiceSource } from "@nestjs/config"; +import { Injectable } from "@nestjs/common"; +import { ConfigService as NestConfigService } from "@nestjs/config"; + +@Injectable() +export class ConfigService { + constructor(private readonly configService: NestConfigService) {} -export class ConfigService extends ConfigServiceSource { public get(key: string): string | number | undefined { - return super.get(key, { infer: true }); + return this.configService.get(key, { infer: true }); } } diff --git a/backend/libs/config/src/env.validation.ts b/backend/libs/config/src/env.validation.ts index 49055d62..065da44e 100644 --- a/backend/libs/config/src/env.validation.ts +++ b/backend/libs/config/src/env.validation.ts @@ -1,5 +1,11 @@ import { plainToClass, Transform } from "class-transformer"; -import { IsNumber, IsOptional, validateSync, IsString, IsBoolean } from "class-validator"; +import { + IsNumber, + IsOptional, + validateSync, + IsString, + IsBoolean, +} from "class-validator"; // Base environment variables needed by all services export class BaseEnvironmentVariables { @@ -36,10 +42,47 @@ export class IndexerEnvironmentVariables extends BaseEnvironmentVariables { @IsOptional() @IsBoolean() - @Transform(({ value }) => value === 'true' || value === true) + @Transform(({ value }) => value === "true" || value === true) FORCE_START_BLOCK?: boolean; } +// RelayerAutomaticRedeem-specific environment variables +export class RelayerAutomaticRedeemEnvironmentVariables extends BaseEnvironmentVariables { + @IsString() + RPC_URL: string; + + @IsString() + VAULT_ADDRESS: string; + + @IsString() + RELAYER_ADDRESS: string; + + @IsString() + RELAYER_PRIVATE_KEY: string; + + @IsOptional() + @IsString() + CRON_SCHEDULE = "*/5 * * * *"; +} + +// RelayerOnChainAum-specific environment variables +export class RelayerOnChainAumEnvironmentVariables extends BaseEnvironmentVariables { + @IsString() + RPC_URL: string; + + @IsString() + VAULT_ADDRESS: string; + + @IsString() + RELAYER_ADDRESS: string; + + @IsString() + RELAYER_PRIVATE_KEY: string; + + @IsString() + ON_CHAIN_AUM_PROVIDER: string; +} + // Generic validation function function validateConfig( EnvClass: new () => T, @@ -67,7 +110,14 @@ export function validateIndexerConfig(config: Record) { return validateConfig(IndexerEnvironmentVariables, config); } -// Legacy export for backward compatibility -export function validate(config: Record) { - return validateApiConfig(config); +export function validateRelayerAutomaticRedeemConfig( + config: Record +) { + return validateConfig(RelayerAutomaticRedeemEnvironmentVariables, config); +} + +export function validateRelayerOnChainAumConfig( + config: Record +) { + return validateConfig(RelayerOnChainAumEnvironmentVariables, config); } diff --git a/backend/libs/db/package.json b/backend/libs/db/package.json index ef687c17..ca240dfd 100644 --- a/backend/libs/db/package.json +++ b/backend/libs/db/package.json @@ -6,10 +6,15 @@ "types": "dist/index.d.ts", "scripts": { "build": "tsc", - "dev": "tsc --watch" + "dev": "tsc --watch", + "prisma:generate": "prisma generate", + "prisma:migrate:dev": "prisma migrate dev", + "prisma:migrate:deploy": "prisma migrate deploy", + "prisma:studio": "prisma studio" }, "dependencies": { - "@nestjs/common": "^10.0.0", - "@prisma/client": "^5.0.0" + "@prisma/client": "^6.15.0", + "prisma": "6.15.0", + "@nestjs/common": "^10.0.0" } } \ No newline at end of file diff --git a/backend/prisma/migrations/20250826190954_init/migration.sql b/backend/libs/db/prisma/migrations/20250826190954_init/migration.sql similarity index 100% rename from backend/prisma/migrations/20250826190954_init/migration.sql rename to backend/libs/db/prisma/migrations/20250826190954_init/migration.sql diff --git a/backend/libs/db/prisma/migrations/20250904105108_init/migration.sql b/backend/libs/db/prisma/migrations/20250904105108_init/migration.sql new file mode 100644 index 00000000..ed5dadbc --- /dev/null +++ b/backend/libs/db/prisma/migrations/20250904105108_init/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `currentBlock` on the `IndexerStatus` table. All the data in the column will be lost. + - Added the required column `lastBlock` to the `IndexerStatus` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "public"."IndexerStatus" DROP COLUMN "currentBlock", +ADD COLUMN "lastBlock" INTEGER NOT NULL; diff --git a/backend/libs/db/prisma/migrations/migration_lock.toml b/backend/libs/db/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..044d57cd --- /dev/null +++ b/backend/libs/db/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/backend/prisma/schema.prisma b/backend/libs/db/prisma/schema.prisma similarity index 88% rename from backend/prisma/schema.prisma rename to backend/libs/db/prisma/schema.prisma index 55ac3fdb..1c18163b 100644 --- a/backend/prisma/schema.prisma +++ b/backend/libs/db/prisma/schema.prisma @@ -1,6 +1,5 @@ generator client { - provider = "prisma-client-js" - binaryTargets = ["native", "linux-arm64-openssl-3.0.x"] + provider = "prisma-client-js" } datasource db { @@ -64,6 +63,6 @@ model RedeemClaimed { model IndexerStatus { id Int @id @default(1) - currentBlock Int - updatedAt DateTime @updatedAt + lastBlock Int + updatedAt DateTime @updatedAt } diff --git a/backend/libs/db/src/prisma.service.ts b/backend/libs/db/src/prisma.service.ts index 2349cc9c..7cf8c8ff 100644 --- a/backend/libs/db/src/prisma.service.ts +++ b/backend/libs/db/src/prisma.service.ts @@ -1,12 +1,8 @@ -import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common"; -import { PrismaClient, RedeemClaimed } from "@prisma/client"; -import { Report, RedeemRequested } from "@prisma/client"; +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient, RedeemRequested, Report, RedeemClaimed, IndexerStatus } from '@prisma/client'; @Injectable() -export class PrismaService - extends PrismaClient - implements OnModuleInit, OnModuleDestroy -{ +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { async onModuleInit() { await this.$connect(); } @@ -15,10 +11,10 @@ export class PrismaService await this.$disconnect(); } - async fetchLastRedeemRequested(): Promise { + async fetchLastRedeemRequested(): Promise { try { const latest = await this.redeemRequested.findFirst({ - orderBy: { blockNumber: "desc" }, + orderBy: { blockNumber: 'desc' }, }); return latest as RedeemRequested | undefined; } catch (error) { @@ -30,7 +26,7 @@ export class PrismaService async fetchLastReport(): Promise { try { const latest = await this.report.findFirst({ - orderBy: { blockNumber: "desc" }, + orderBy: { blockNumber: 'desc' }, }); return latest as Report | undefined; } catch (error) { @@ -42,7 +38,7 @@ export class PrismaService async fetchLastReports(limit: number, offset?: number): Promise { try { const reports = await this.report.findMany({ - orderBy: { blockNumber: "desc" }, + orderBy: { blockNumber: 'desc' }, take: limit, ...(offset && { skip: offset }), }); @@ -56,7 +52,7 @@ export class PrismaService async fetchLastRedeemClaimed(): Promise { try { const latest = await this.redeemClaimed.findFirst({ - orderBy: { blockNumber: "desc" }, + orderBy: { blockNumber: 'desc' }, }); return latest as RedeemClaimed | undefined; } catch (error) { @@ -65,31 +61,68 @@ export class PrismaService } } + async fetchLastRedeemRequestedIdForAddress(receiver: string): Promise { + try { + const latest = await this.redeemRequested.findFirst({ + where: { receiver }, + orderBy: { redeemId: 'desc' }, + select: { redeemId: true }, + }); + return latest?.redeemId !== undefined ? Number(latest.redeemId) : undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } + } + + async fetchLastRedeemClaimedIdForAddress(receiver: string): Promise { + try { + const latest = await this.redeemClaimed.findFirst({ + where: { receiver }, + orderBy: { redeemId: 'desc' }, + select: { redeemId: true }, + }); + return latest?.redeemId !== undefined ? Number(latest.redeemId) : undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } + } public async fetchPendingRedeemsForAddress( receiver: string, + minRedeemId: number, + maxRedeemId: number, limit?: number, offset?: number ): Promise { try { - // Get all claimed redeem IDs for this address + // Get all claimed redeem IDs for this address in the range const claimedRedeemIds = await this.redeemClaimed.findMany({ - where: { receiver }, + where: { + receiver, + redeemId: { + gt: minRedeemId, + lte: maxRedeemId, + }, + }, select: { redeemId: true }, }); - - const claimedIds = claimedRedeemIds.map(r => r.redeemId); - - // Find all requested redeems that are NOT in the claimed list + + const claimedIds = claimedRedeemIds.map((r) => r.redeemId); + + // Find all requested redeems in the range that are NOT in the claimed list return this.redeemRequested.findMany({ where: { receiver, redeemId: { + gt: minRedeemId, + lte: maxRedeemId, notIn: claimedIds, }, }, orderBy: { - redeemId: "desc", + redeemId: 'desc', }, ...(limit && { take: limit }), ...(offset && { skip: offset }), @@ -99,4 +132,23 @@ export class PrismaService return []; } } + + async updateIndexerStatus(lastBlock: number): Promise { + return this.indexerStatus.upsert({ + where: { id: 1 }, + update: { lastBlock }, + create: { id: 1, lastBlock }, + }); + } + + async getIndexerStatus(): Promise { + try { + return await this.indexerStatus.findUnique({ + where: { id: 1 }, + }); + } catch (error) { + // Table doesn't exist yet, return null + return null; + } + } } diff --git a/backend/libs/logger/package.json b/backend/libs/logger/package.json index c4578697..3ebfafeb 100644 --- a/backend/libs/logger/package.json +++ b/backend/libs/logger/package.json @@ -11,8 +11,5 @@ "dependencies": { "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1" - }, - "devDependencies": { - "@types/node": "^20.0.0" } } \ No newline at end of file diff --git a/backend/libs/logger/src/index.ts b/backend/libs/logger/src/index.ts index f556ad70..7a9a5761 100644 --- a/backend/libs/logger/src/index.ts +++ b/backend/libs/logger/src/index.ts @@ -1,2 +1,3 @@ export * from './logger'; +export * from './logger.config'; export { Logger, LogLevel, LoggerConfig } from './logger'; \ No newline at end of file diff --git a/backend/libs/logger/src/logger.config.ts b/backend/libs/logger/src/logger.config.ts new file mode 100644 index 00000000..61417fc3 --- /dev/null +++ b/backend/libs/logger/src/logger.config.ts @@ -0,0 +1,22 @@ +import { LogLevel, LoggerConfig } from './logger'; + +export const getLoggerConfig = (): LoggerConfig => { + const isDevelopment = process.env.NODE_ENV === 'development'; + const logLevel = (process.env.LOG_LEVEL as LogLevel) || (isDevelopment ? LogLevel.DEBUG : LogLevel.INFO); + + return { + level: logLevel, + service: 'starknet-vault-kit', + enableConsole: true, + enableFile: process.env.ENABLE_FILE_LOGGING === 'true' || !isDevelopment, + logDir: process.env.LOG_DIR || 'logs', + maxFiles: '14d', + maxSize: '20m', + format: isDevelopment ? 'simple' : 'json', + }; +}; + +export const initializeLogger = (): void => { + const { Logger } = require('./logger'); + Logger.configure(getLoggerConfig()); +}; \ No newline at end of file diff --git a/backend/libs/logger/src/logger.ts b/backend/libs/logger/src/logger.ts index a91257f3..3184320c 100644 --- a/backend/libs/logger/src/logger.ts +++ b/backend/libs/logger/src/logger.ts @@ -73,11 +73,16 @@ export class Logger { new winston.transports.Console({ level, format: winston.format.combine( - winston.format.colorize(), + winston.format.colorize({ all: false, level: true }), winston.format.timestamp({ format: 'HH:mm:ss' }), - winston.format.printf(({ timestamp, level, message, context, service, ...meta }) => { - const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; - return `${timestamp} [${level}] [${service}:${context}] ${message}${metaStr}`; + winston.format.printf(({ timestamp, level, message, context, ...meta }) => { + const contextStr = context ? `[${context}]` : ''; + const metaStr = Object.keys(meta).length > 1 ? ` ${JSON.stringify( + Object.fromEntries( + Object.entries(meta).filter(([key]) => key !== 'service') + ), null, 2 + )}` : ''; + return `${timestamp} ${level} ${contextStr} ${message}${metaStr}`; }) ), }) diff --git a/backend/libs/starknet/package.json b/backend/libs/starknet/package.json new file mode 100644 index 00000000..48d4df1c --- /dev/null +++ b/backend/libs/starknet/package.json @@ -0,0 +1,17 @@ +{ + "name": "@forge/starknet", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "watch": "tsc --watch", + "clean": "rm -rf dist" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/logger": "workspace:*", + "@nestjs/common": "^10.0.0", + "starknet": "^7.6.4" + } +} \ No newline at end of file diff --git a/backend/libs/starknet/src/abis/aumProvider.json b/backend/libs/starknet/src/abis/aumProvider.json new file mode 100644 index 00000000..30d91447 --- /dev/null +++ b/backend/libs/starknet/src/abis/aumProvider.json @@ -0,0 +1,99 @@ +[ + { + "type": "impl", + "name": "AumProvider4626Impl", + "interface_name": "vault::aum_provider::aum_provider_4626::interface::IAumProvider4626" + }, + { + "type": "interface", + "name": "vault::aum_provider::aum_provider_4626::interface::IAumProvider4626", + "items": [ + { + "type": "function", + "name": "get_strategy_4626", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "BaseAumProviderImpl", + "interface_name": "vault::aum_provider::interface::IBaseAumProvider" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "interface", + "name": "vault::aum_provider::interface::IBaseAumProvider", + "items": [ + { + "type": "function", + "name": "aum", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "report", + "inputs": [], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "vault", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "strategy4626", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "event", + "name": "vault::aum_provider::base_aum_provider::BaseAumProviderComponent::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "vault::aum_provider::aum_provider_4626::aum_provider_4626::AumProvider4626::Event", + "kind": "enum", + "variants": [ + { + "name": "BaseAumProviderEvent", + "type": "vault::aum_provider::base_aum_provider::BaseAumProviderComponent::Event", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/backend/apps/api/src/starknet/abis/vault.json b/backend/libs/starknet/src/abis/vault.json similarity index 100% rename from backend/apps/api/src/starknet/abis/vault.json rename to backend/libs/starknet/src/abis/vault.json diff --git a/backend/apps/api/src/starknet/index.ts b/backend/libs/starknet/src/index.ts similarity index 100% rename from backend/apps/api/src/starknet/index.ts rename to backend/libs/starknet/src/index.ts diff --git a/backend/apps/api/src/starknet/starknet.module.ts b/backend/libs/starknet/src/starknet.module.ts similarity index 100% rename from backend/apps/api/src/starknet/starknet.module.ts rename to backend/libs/starknet/src/starknet.module.ts diff --git a/backend/libs/starknet/src/starknet.service.ts b/backend/libs/starknet/src/starknet.service.ts new file mode 100644 index 00000000..01d97643 --- /dev/null +++ b/backend/libs/starknet/src/starknet.service.ts @@ -0,0 +1,328 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@forge/config'; +import { Provider, RpcProvider, Contract, Call, uint256, Account } from 'starknet'; +import { Logger } from '@forge/logger'; +import * as VAULT_ABI from './abis/vault.json'; + +@Injectable() +export class StarknetService implements OnModuleInit { + private provider: Provider; + private readonly logger = Logger.create('Starknet:Service'); + + constructor(private configService: ConfigService) {} + + async onModuleInit() { + try { + const rpcUrl: string = + (this.configService.get('RPC_URL') as string) || 'https://starknet-mainnet.public.blastapi.io'; + + this.logger.info('Initializing StarkNet provider', { rpcUrl }); + this.provider = new RpcProvider({ nodeUrl: rpcUrl }); + this.logger.info('StarkNet provider initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize StarkNet provider', error); + throw error; + } + } + + getSigner() { + return new Account( + this.provider, + this.configService.get(`RELAYER_ADDRESS`) as string, + this.configService.get(`RELAYER_PRIVATE_KEY`) as string, + undefined, + '0x3' + ); + } + + async view(address: string, abi: any, functionName: string, calldata?: any[]) { + try { + this.logger.debug('Making contract view call', { + address, + functionName, + calldataLength: calldata?.length || 0, + }); + + const abiArray = Array.isArray(abi) ? abi : Object.values(abi); + const contract = new Contract(abiArray, address, this.provider); + + const result = calldata ? await contract.call(functionName, calldata) : await contract.call(functionName); + + this.logger.debug('Contract view call successful', { + address, + functionName, + resultType: typeof result, + }); + + return result; + } catch (error) { + this.logger.error('Contract view call failed', error, { + address, + functionName, + calldata, + }); + throw error; + } + } + + async vault_due_assets_from_id(address: string, id: number): Promise { + try { + this.logger.debug('Fetching due assets from vault', { address, id }); + + const due_assets = (await this.view(address, VAULT_ABI, 'due_assets_from_id', [id])) as any; + + this.logger.debug('Successfully fetched due assets', { + address, + id, + dueAssets: due_assets?.toString(), + }); + + return due_assets; + } catch (error) { + this.logger.error('Failed to fetch due assets from vault', error, { + address, + id, + }); + throw error; + } + } + + async vault_decimals(address: string): Promise { + try { + this.logger.debug('Fetching vault decimals', { address }); + + const decimals = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'decimals', + })) as any; + + this.logger.debug('Successfully fetched vault decimals', { + address, + decimals: decimals?.toString(), + }); + + return decimals; + } catch (error) { + this.logger.error('Failed to fetch vault decimals', error, { address }); + throw error; + } + } + + async vault_buffer(address: string): Promise { + try { + this.logger.debug('Fetching vault buffer', { address }); + + const buffer = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'buffer', + })) as any; + + this.logger.debug('Successfully fetched vault buffer', { + address, + buffer: buffer?.toString(), + }); + + return buffer; + } catch (error) { + this.logger.error('Failed to fetch vault buffer', error, { address }); + throw error; + } + } + + async vault_epoch(address: string): Promise { + try { + this.logger.debug('Fetching vault epoch', { address }); + + const epoch = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'epoch', + })) as any; + + this.logger.debug('Successfully fetched vault epoch', { + address, + epoch: epoch?.toString(), + }); + + return epoch; + } catch (error) { + this.logger.error('Failed to fetch vault epoch', error, { address }); + throw error; + } + } + + async vault_handled_epoch_len(address: string): Promise { + try { + this.logger.debug('Fetching vault handled epoch length', { address }); + + const handledEpochLen = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'handled_epoch_len', + })) as any; + + this.logger.debug('Successfully fetched vault handled epoch length', { + address, + handledEpochLen: handledEpochLen?.toString(), + }); + + return handledEpochLen; + } catch (error) { + this.logger.error('Failed to fetch vault handled epoch length', error, { + address, + }); + throw error; + } + } + + async vault_redeem_assets(address: string, epoch: number): Promise { + try { + this.logger.debug('Fetching vault redeem assets', { address, epoch }); + + const redeemAssets = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'redeem_assets', + calldata: [epoch], + })) as any; + + this.logger.debug('Successfully fetched vault redeem assets', { + address, + epoch, + redeemAssets: redeemAssets?.toString(), + }); + + return redeemAssets; + } catch (error) { + this.logger.error('Failed to fetch vault redeem assets', error, { + address, + epoch, + }); + throw error; + } + } + + async vault_claim_redeem(address: string, redeemIds: bigint[]) { + const functionName = 'claim_redeem'; + + const calls: Array = redeemIds.map((redeemId) => { + const redeemIdUint256 = uint256.bnToUint256(redeemId); + return { + contractAddress: address, + entrypoint: functionName, + calldata: [redeemIdUint256.low, redeemIdUint256.high], + }; + }); + + try { + const { transaction_hash } = await this.getSigner().execute(calls); + const txReceipt = await this.provider.waitForTransaction(transaction_hash); + if (txReceipt.isSuccess()) { + return transaction_hash; + } else { + throw new Error( + `tx failed, hash: ${transaction_hash}, status: ${txReceipt.isSuccess() ? 'SUCCESS' : 'FAILED'}` + ); + } + } catch (error) { + throw error; + } + } + + async getCurrentBlock(): Promise { + try { + const block = await this.provider.getBlock('latest'); + this.logger.debug('Fetched current block', { + blockNumber: block.block_number, + }); + return block.block_number; + } catch (error) { + this.logger.error('Failed to fetch current block', error); + throw error; + } + } + + async getCurrentBlockTimestamp(): Promise { + try { + const block = await this.provider.getBlock('latest'); + this.logger.debug('Fetched current block timestamp', { + blockNumber: block.timestamp, + }); + return block.timestamp; + } catch (error) { + this.logger.error('Failed to fetch current block timestamp', error); + throw error; + } + } + + async getLastReportTimestamp(vaultAddress: string): Promise { + try { + const result = await this.view(vaultAddress, VAULT_ABI, 'last_report_timestamp'); + this.logger.debug('Fetched last report timestamp', { + vaultAddress, + timestamp: result.toString(), + }); + return result as bigint; + } catch (error) { + this.logger.error('Failed to get last report timestamp', error, { + vaultAddress, + }); + throw error; + } + } + + async getReportDelay(vaultAddress: string): Promise { + try { + const result = await this.view(vaultAddress, VAULT_ABI, 'report_delay'); + this.logger.debug('Fetched report delay', { + vaultAddress, + delay: result.toString(), + }); + return result as bigint; + } catch (error) { + this.logger.error('Failed to get report delay', error, { + vaultAddress, + }); + throw error; + } + } + + async triggerAumProviderReport(aumProviderAddress: string): Promise { + try { + this.logger.info('Triggering AUM provider report', { + aumProviderAddress, + }); + + const calls = [ + { + contractAddress: aumProviderAddress, + entrypoint: 'report', + calldata: [], + }, + ]; + + const signer = this.getSigner(); + const { transaction_hash } = await signer.execute(calls); + + this.logger.info('Report transaction submitted', { + transactionHash: transaction_hash, + aumProviderAddress, + }); + + const txReceipt = await this.provider.waitForTransaction(transaction_hash); + + if (txReceipt.isSuccess()) { + this.logger.info('Report transaction successful', { + transactionHash: transaction_hash, + }); + return transaction_hash; + } else { + throw new Error( + `Report transaction failed: ${transaction_hash}, status: ${txReceipt.isSuccess() ? 'SUCCESS' : 'FAILED'}` + ); + } + } catch (error) { + this.logger.error('Failed to trigger AUM provider report', error, { + aumProviderAddress, + }); + throw error; + } + } +} diff --git a/backend/libs/starknet/tsconfig.json b/backend/libs/starknet/tsconfig.json new file mode 100644 index 00000000..997d1e4f --- /dev/null +++ b/backend/libs/starknet/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "composite": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] +} \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index eb6fd29d..84f91515 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,18 +12,24 @@ "scripts": { "dev:api": "pnpm --filter api dev", "dev:indexer": "pnpm --filter indexer dev", - "dev:all": "concurrently \"pnpm dev:api\" \"pnpm dev:indexer\"", + "dev:relayerAutomaticRedeem": "pnpm --filter relayerAutomaticRedeem dev", + "dev:relayerOnChainAum": "pnpm --filter relayerOnChainAum dev", + "dev:all": "concurrently \"pnpm dev:api\" \"pnpm dev:indexer\" \"pnpm dev:relayerAutomaticRedeem\" \"pnpm dev:relayerOnChainAum\"", "start:api": "pnpm --filter api start", "start:indexer": "pnpm --filter indexer start", + "start:relayerAutomaticRedeem": "pnpm --filter relayerAutomaticRedeem start", + "start:relayerOnChainAum": "pnpm --filter relayerOnChainAum start", "build": "pnpm --recursive --filter='!@forge/root' run build", "build:api": "pnpm --filter api build", "build:indexer": "pnpm --filter indexer build", + "build:relayerAutomaticRedeem": "pnpm --filter relayerAutomaticRedeem build", + "build:relayerOnChainAum": "pnpm --filter relayerOnChainAum build", "lint": "pnpm --recursive --filter='!@forge/root' run lint", "test": "pnpm --recursive --filter='!@forge/root' run test", - "prisma:generate": "prisma generate", - "prisma:migrate:dev": "prisma migrate dev", - "prisma:migrate:deploy": "prisma migrate deploy", - "prisma:studio": "prisma studio" + "prisma:generate": "pnpm --filter @forge/db prisma:generate", + "prisma:migrate:dev": "pnpm --filter @forge/db prisma:migrate:dev", + "prisma:migrate:deploy": "pnpm --filter @forge/db prisma:migrate:deploy", + "prisma:studio": "pnpm --filter @forge/db prisma:studio" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -40,7 +46,7 @@ "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", - "prisma": "^5.0.0", + "prisma": "6.15.0", "source-map-support": "^0.5.21", "supertest": "^7.0.0", "ts-jest": "^29.1.0", @@ -50,6 +56,16 @@ "typescript": "^5.1.3" }, "dependencies": { - "@prisma/client": "^5.0.0" + "@nestjs/axios": "^3.1.3", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.4.2", + "@nestjs/terminus": "^10.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.2" } } \ No newline at end of file diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index d3c19102..b6215b70 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -8,9 +8,39 @@ importers: .: dependencies: - '@prisma/client': - specifier: ^5.0.0 - version: 5.22.0(prisma@5.22.0) + '@nestjs/axios': + specifier: ^3.1.3 + version: 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^3.0.0 + version: 3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + '@nestjs/swagger': + specifier: ^7.4.2 + version: 7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + '@nestjs/terminus': + specifier: ^10.0.0 + version: 10.3.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + rxjs: + specifier: ^7.8.2 + version: 7.8.2 devDependencies: '@nestjs/cli': specifier: ^10.0.0 @@ -29,13 +59,13 @@ importers: version: 29.5.14 '@types/node': specifier: ^20.3.1 - version: 20.19.11 + version: 20.19.12 '@typescript-eslint/eslint-plugin': specifier: ^8.0.0 - version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + version: 8.42.0(@typescript-eslint/parser@8.42.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.40.0(eslint@8.57.1)(typescript@5.9.2) + version: 8.42.0(eslint@8.57.1)(typescript@5.9.2) concurrently: specifier: ^8.0.0 version: 8.2.2 @@ -50,13 +80,13 @@ importers: version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + version: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) prettier: specifier: ^3.0.0 version: 3.6.2 prisma: - specifier: ^5.0.0 - version: 5.22.0 + specifier: 6.15.0 + version: 6.15.0(typescript@5.9.2) source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -65,13 +95,13 @@ importers: version: 7.1.4 ts-jest: specifier: ^29.1.0 - version: 29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)))(typescript@5.9.2) + version: 29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)))(typescript@5.9.2) ts-loader: specifier: ^9.4.3 - version: 9.5.2(typescript@5.9.2)(webpack@5.97.1) + version: 9.5.4(typescript@5.9.2)(webpack@5.97.1) ts-node: specifier: ^10.9.1 - version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + version: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -90,70 +120,33 @@ importers: '@forge/logger': specifier: workspace:* version: link:../../libs/logger + '@forge/starknet': + specifier: workspace:* + version: link:../../libs/starknet '@nestjs/axios': - specifier: ^3.0.0 + specifier: ^3.1.3 version: 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) '@nestjs/common': specifier: ^10.0.0 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/config': - specifier: ^3.0.0 - version: 3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) '@nestjs/core': - specifier: ^10.0.0 + specifier: ^10.4.20 version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': specifier: ^10.0.0 version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) '@nestjs/swagger': - specifier: ^7.0.0 + specifier: ^7.4.2 version: 7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) '@nestjs/terminus': specifier: ^11.0.0 - version: 11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.2) - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.14.0 - version: 0.14.2 + version: 11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) decimal.js: specifier: ^10.6.0 version: 10.6.0 - reflect-metadata: - specifier: ^0.2.0 - version: 0.2.2 - rxjs: - specifier: ^7.8.1 - version: 7.8.2 starknet: - specifier: ^6.0.0 - version: 6.24.1 - devDependencies: - '@nestjs/cli': - specifier: ^10.0.0 - version: 10.4.9 - '@nestjs/testing': - specifier: ^10.0.0 - version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)) - '@types/jest': - specifier: ^29.5.2 - version: 29.5.14 - '@types/node': - specifier: ^20.3.1 - version: 20.19.11 - jest: - specifier: ^29.5.0 - version: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) - nodemon: - specifier: ^3.0.0 - version: 3.1.10 - ts-jest: - specifier: ^29.1.0 - version: 29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)))(typescript@5.9.2) - ts-node: - specifier: ^10.9.1 - version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + specifier: ^7.6.4 + version: 7.6.4 apps/indexer: dependencies: @@ -172,28 +165,71 @@ importers: '@forge/logger': specifier: workspace:* version: link:../../libs/logger - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.14.0 - version: 0.14.2 - reflect-metadata: - specifier: ^0.2.0 - version: 0.2.2 + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) starknet: - specifier: ^6.0.0 - version: 6.24.1 + specifier: ^7.6.4 + version: 7.6.4 devDependencies: - '@types/node': - specifier: ^20.3.1 - version: 20.19.11 - nodemon: - specifier: ^3.0.0 - version: 3.1.10 ts-node: specifier: ^10.9.1 - version: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + version: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) + + apps/relayerAutomaticRedeem: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/db': + specifier: workspace:* + version: link:../../libs/db + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + '@forge/starknet': + specifier: workspace:* + version: link:../../libs/starknet + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cron: + specifier: ^4.3.3 + version: 4.3.3 + devDependencies: + '@types/cron': + specifier: ^2.0.0 + version: 2.4.3 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) + + apps/relayerOnChainAum: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + '@forge/starknet': + specifier: workspace:* + version: link:../../libs/starknet + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^4.0.0 + version: 4.0.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) libs/config: dependencies: @@ -216,8 +252,11 @@ importers: specifier: ^10.0.0 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@prisma/client': - specifier: ^5.0.0 - version: 5.22.0(prisma@5.22.0) + specifier: ^6.15.0 + version: 6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2) + prisma: + specifier: 6.15.0 + version: 6.15.0(typescript@5.9.2) libs/logger: dependencies: @@ -227,10 +266,21 @@ importers: winston-daily-rotate-file: specifier: ^4.7.1 version: 4.7.1(winston@3.17.0) - devDependencies: - '@types/node': - specifier: ^20.0.0 - version: 20.19.11 + + libs/starknet: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../config + '@forge/logger': + specifier: workspace:* + version: link:../logger + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + starknet: + specifier: ^7.6.4 + version: 7.6.4 packages: @@ -449,8 +499,8 @@ packages: '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + '@eslint-community/eslint-utils@4.8.0': + resolution: {integrity: sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -639,6 +689,12 @@ packages: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 rxjs: ^7.1.0 + '@nestjs/config@4.0.2': + resolution: {integrity: sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + '@nestjs/core@10.4.20': resolution: {integrity: sha512-kRdtyKA3+Tu70N3RQ4JgmO1E3LzAMs/eppj7SfjabC7TgqNWoS4RLhWl4BqmsNVmjj6D5jgfPVtHtgYkU3AfpQ==} peerDependencies: @@ -697,6 +753,54 @@ packages: class-validator: optional: true + '@nestjs/terminus@10.3.0': + resolution: {integrity: sha512-vOJGCwt1OgrFuuxWQwPoaHqy9m9CfIk2qMUX2mosZLK5dFVJSEjHXrklkh3/Fw9PiUnfzvYFfiAdJRzUaxx+5Q==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + '@nestjs/microservices': ^9.0.0 || ^10.0.0 + '@nestjs/mongoose': ^9.0.0 || ^10.0.0 + '@nestjs/sequelize': ^9.0.0 || ^10.0.0 + '@nestjs/typeorm': ^9.0.0 || ^10.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + '@nestjs/terminus@11.0.0': resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==} peerDependencies: @@ -798,29 +902,35 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@prisma/client@5.22.0': - resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==} - engines: {node: '>=16.13'} + '@prisma/client@6.15.0': + resolution: {integrity: sha512-wR2LXUbOH4cL/WToatI/Y2c7uzni76oNFND7+23ypLllBmIS8e3ZHhO+nud9iXSXKFt1SoM3fTZvHawg63emZw==} + engines: {node: '>=18.18'} peerDependencies: prisma: '*' + typescript: '>=5.1.0' peerDependenciesMeta: prisma: optional: true + typescript: + optional: true - '@prisma/debug@5.22.0': - resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==} + '@prisma/config@6.15.0': + resolution: {integrity: sha512-KMEoec9b2u6zX0EbSEx/dRpx1oNLjqJEBZYyK0S3TTIbZ7GEGoVyGyFRk4C72+A38cuPLbfQGQvgOD+gBErKlA==} - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': - resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==} + '@prisma/debug@6.15.0': + resolution: {integrity: sha512-y7cSeLuQmyt+A3hstAs6tsuAiVXSnw9T55ra77z0nbNkA8Lcq9rNcQg6PI00by/+WnE/aMRJ/W7sZWn2cgIy1g==} - '@prisma/engines@5.22.0': - resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==} + '@prisma/engines-version@6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb': + resolution: {integrity: sha512-a/46aK5j6L3ePwilZYEgYDPrhBQ/n4gYjLxT5YncUTJJNRnTCVjPF86QdzUOLRdYjCLfhtZp9aum90W0J+trrg==} - '@prisma/fetch-engine@5.22.0': - resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==} + '@prisma/engines@6.15.0': + resolution: {integrity: sha512-opITiR5ddFJ1N2iqa7mkRlohCZqVSsHhRcc29QXeldMljOf4FSellLT0J5goVb64EzRTKcIDeIsJBgmilNcKxA==} - '@prisma/get-platform@5.22.0': - resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + '@prisma/fetch-engine@6.15.0': + resolution: {integrity: sha512-xcT5f6b+OWBq6vTUnRCc7qL+Im570CtwvgSj+0MTSGA1o9UDSKZ/WANvwtiRXdbYWECpyC3CukoG3A04VTAPHw==} + + '@prisma/get-platform@6.15.0': + resolution: {integrity: sha512-Jbb+Xbxyp05NSR1x2epabetHiXvpO8tdN2YNoWoA/ZsbYyxxu/CO/ROBauIFuMXs3Ti+W7N7SJtWsHGaWte9Rg==} '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -867,9 +977,15 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@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==} + '@tokenizer/inflate@0.2.7': resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} engines: {node: '>=18'} @@ -907,6 +1023,10 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cron@2.4.3': + resolution: {integrity: sha512-ViRBkoZD9Rk0hGeMdd2GHGaOaZuH9mDmwsE5/Zo53Ftwcvh7h9VJc8lIt2wdgEwS4EW5lbtTX6vlE0idCLPOyA==} + deprecated: This is a stub types definition. cron provides its own type definitions, so you do not need this installed. + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -943,11 +1063,14 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/luxon@3.7.1': + resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/node@20.19.11': - resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} + '@types/node@20.19.12': + resolution: {integrity: sha512-lSOjyS6vdO2G2g2CWrETTV3Jz2zlCXHpu1rcubLKpz9oj+z/1CceHlj+yq53W+9zgb98nSov/wjEKYDNauD+Hw==} '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -967,8 +1090,8 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - '@types/validator@13.15.2': - resolution: {integrity: sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==} + '@types/validator@13.15.3': + resolution: {integrity: sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -976,63 +1099,63 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.40.0': - resolution: {integrity: sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==} + '@typescript-eslint/eslint-plugin@8.42.0': + resolution: {integrity: sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.40.0 + '@typescript-eslint/parser': ^8.42.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.40.0': - resolution: {integrity: sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==} + '@typescript-eslint/parser@8.42.0': + resolution: {integrity: sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.40.0': - resolution: {integrity: sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==} + '@typescript-eslint/project-service@8.42.0': + resolution: {integrity: sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.40.0': - resolution: {integrity: sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==} + '@typescript-eslint/scope-manager@8.42.0': + resolution: {integrity: sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.40.0': - resolution: {integrity: sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==} + '@typescript-eslint/tsconfig-utils@8.42.0': + resolution: {integrity: sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.40.0': - resolution: {integrity: sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==} + '@typescript-eslint/type-utils@8.42.0': + resolution: {integrity: sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.40.0': - resolution: {integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==} + '@typescript-eslint/types@8.42.0': + resolution: {integrity: sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.40.0': - resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==} + '@typescript-eslint/typescript-estree@8.42.0': + resolution: {integrity: sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.40.0': - resolution: {integrity: sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==} + '@typescript-eslint/utils@8.42.0': + resolution: {integrity: sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.40.0': - resolution: {integrity: sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==} + '@typescript-eslint/visitor-keys@8.42.0': + resolution: {integrity: sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -1262,8 +1385,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.25.3: - resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} + browserslist@4.25.4: + resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1288,6 +1411,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1312,8 +1443,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001735: - resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} + caniuse-lite@1.0.30001739: + resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==} cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} @@ -1342,6 +1473,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -1350,6 +1485,9 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} @@ -1450,9 +1588,16 @@ packages: engines: {node: ^14.13.0 || >=16.0.0} hasBin: true + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1498,6 +1643,10 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cron@4.3.3: + resolution: {integrity: sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==} + engines: {node: '>=18.x'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1526,8 +1675,8 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - dedent@1.6.0: - resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + dedent@1.7.0: + resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -1537,6 +1686,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -1548,6 +1701,9 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1556,6 +1712,9 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1583,10 +1742,22 @@ packages: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} + dotenv-expand@12.0.1: + resolution: {integrity: sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==} + engines: {node: '>=12'} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1597,8 +1768,11 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.207: - resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==} + effect@3.16.12: + resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + + electron-to-chromium@1.5.214: + resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -1610,6 +1784,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} @@ -1761,10 +1939,17 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1784,8 +1969,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -1796,9 +1981,6 @@ packages: 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==} - fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -1921,6 +2103,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1962,10 +2148,6 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2007,9 +2189,6 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore-by-default@1.0.1: - resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2103,9 +2282,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isomorphic-fetch@3.0.0: - resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -2270,6 +2446,10 @@ packages: node-notifier: optional: true + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2333,8 +2513,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libphonenumber-js@1.12.12: - resolution: {integrity: sha512-aWVR6xXYYRvnK0v/uIwkf5Lthq9Jpn0N8TISW/oDTWlYB2sOimuiLn9Q26aUw4KxkJoiT8ACdiw44Y8VwKFIfQ==} + libphonenumber-js@1.12.15: + resolution: {integrity: sha512-TMDCtIhWUDHh91wRC+wFuGlIzKdPzaTUHHVrIZ3vPUEoNaXFLrsIQ1ZpAeZeXApIF6rvDksMTvjrIQlLKaYxqQ==} lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2374,8 +2554,8 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - lossless-json@4.1.1: - resolution: {integrity: sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==} + lossless-json@4.2.0: + resolution: {integrity: sha512-bsHH3x+7acZfqokfn9Ks/ej96yF/z6oGGw1aBmXesq4r3fAjhdG4uYuqzDgZMk5g1CZUd5w3kwwIp9K1LOYUiA==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2383,6 +2563,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + luxon@3.7.1: + resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} + engines: {node: '>=12'} + magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} @@ -2503,6 +2687,9 @@ packages: node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2518,11 +2705,6 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - nodemon@3.1.10: - resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} - engines: {node: '>=10'} - hasBin: true - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2531,6 +2713,11 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + nypm@0.6.1: + resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2543,6 +2730,9 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2636,6 +2826,12 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2655,6 +2851,9 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -2676,10 +2875,15 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prisma@5.22.0: - resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==} - engines: {node: '>=16.13'} + prisma@6.15.0: + resolution: {integrity: sha512-E6RCgOt+kUVtjtZgLQDBJ6md2tDItLJNExwI0XJeBc1FKL+Vwb+ovxXxuok9r8oBgsOXBA33fGDuE/0qDdCWqQ==} + engines: {node: '>=18.18'} hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} @@ -2696,12 +2900,6 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - - pstree.remy@1.1.8: - resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2717,9 +2915,6 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2734,6 +2929,9 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -2745,6 +2943,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} @@ -2763,9 +2965,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -2855,9 +3054,6 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - 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'} @@ -2903,10 +3099,6 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - simple-update-notifier@2.0.0: - resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} - engines: {node: '>=10'} - sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2945,8 +3137,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} - starknet@6.24.1: - resolution: {integrity: sha512-g7tiCt73berhcNi41otlN3T3kxZnIvZhMi8WdC21Y6GC6zoQgbI2z1t7JAZF9c4xZiomlanwVnurcpyfEdyMpg==} + starknet@7.6.4: + resolution: {integrity: sha512-FB20IaLCDbh/XomkB+19f5jmNxG+RzNdRO7QUhm7nfH81UPIt2C/MyWAlHCYkbv2wznSEb73wpxbp9tytokTgQ==} + engines: {node: '>=22'} statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} @@ -3007,10 +3200,6 @@ packages: resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==} engines: {node: '>=14.18.0'} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3034,8 +3223,8 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} - tapable@2.2.2: - resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} engines: {node: '>=6'} terser-webpack-plugin@5.3.14: @@ -3054,8 +3243,8 @@ packages: uglify-js: optional: true - terser@5.43.1: - resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} + terser@5.44.0: + resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} engines: {node: '>=10'} hasBin: true @@ -3072,6 +3261,9 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -3091,14 +3283,6 @@ packages: resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} engines: {node: '>=14.16'} - touch@3.1.1: - resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} - hasBin: true - - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} - tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3143,8 +3327,8 @@ packages: jest-util: optional: true - ts-loader@9.5.2: - resolution: {integrity: sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==} + ts-loader@9.5.4: + resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==} engines: {node: '>=12.0.0'} peerDependencies: typescript: '*' @@ -3224,20 +3408,13 @@ packages: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} - uint8array-extras@1.4.1: - resolution: {integrity: sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==} + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} - undefsafe@2.0.5: - resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3255,9 +3432,6 @@ packages: 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==} @@ -3311,9 +3485,6 @@ packages: webpack-cli: optional: true - whatwg-fetch@3.6.20: - resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} - whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -3467,7 +3638,7 @@ snapshots: '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 convert-source-map: 2.0.0 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3486,7 +3657,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.3 + browserslist: 4.25.4 lru-cache: 5.1.1 semver: 6.3.1 @@ -3626,7 +3797,7 @@ snapshots: '@babel/parser': 7.28.3 '@babel/template': 7.27.2 '@babel/types': 7.28.2 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -3654,7 +3825,7 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 - '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.8.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 @@ -3664,7 +3835,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -3692,7 +3863,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -3723,27 +3894,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2))': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -3768,7 +3939,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -3786,7 +3957,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.19.11 + '@types/node': 20.19.12 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -3808,7 +3979,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.30 - '@types/node': 20.19.11 + '@types/node': 20.19.12 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -3878,7 +4049,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.11 + '@types/node': 20.19.12 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -3970,6 +4141,14 @@ snapshots: lodash: 4.17.21 rxjs: 7.8.2 + '@nestjs/config@4.0.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 16.4.7 + dotenv-expand: 12.0.1 + lodash: 4.17.21 + rxjs: 7.8.2 + '@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4043,7 +4222,21 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.2 - '@nestjs/terminus@11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/terminus@10.3.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optionalDependencies: + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 + '@nestjs/axios': 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@prisma/client': 6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2) + + '@nestjs/terminus@11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4055,7 +4248,7 @@ snapshots: '@grpc/grpc-js': 1.13.4 '@grpc/proto-loader': 0.7.15 '@nestjs/axios': 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) - '@prisma/client': 5.22.0(prisma@5.22.0) + '@prisma/client': 6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2) '@nestjs/testing@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20))': dependencies: @@ -4102,30 +4295,40 @@ snapshots: '@pkgr/core@0.2.9': {} - '@prisma/client@5.22.0(prisma@5.22.0)': + '@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2)': optionalDependencies: - prisma: 5.22.0 + prisma: 6.15.0(typescript@5.9.2) + typescript: 5.9.2 + + '@prisma/config@6.15.0': + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.16.12 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast - '@prisma/debug@5.22.0': {} + '@prisma/debug@6.15.0': {} - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {} + '@prisma/engines-version@6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb': {} - '@prisma/engines@5.22.0': + '@prisma/engines@6.15.0': dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/fetch-engine': 5.22.0 - '@prisma/get-platform': 5.22.0 + '@prisma/debug': 6.15.0 + '@prisma/engines-version': 6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb + '@prisma/fetch-engine': 6.15.0 + '@prisma/get-platform': 6.15.0 - '@prisma/fetch-engine@5.22.0': + '@prisma/fetch-engine@6.15.0': dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/get-platform': 5.22.0 + '@prisma/debug': 6.15.0 + '@prisma/engines-version': 6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb + '@prisma/get-platform': 6.15.0 - '@prisma/get-platform@5.22.0': + '@prisma/get-platform@6.15.0': dependencies: - '@prisma/debug': 5.22.0 + '@prisma/debug': 6.15.0 '@protobufjs/aspromise@1.1.2': {} @@ -4167,11 +4370,15 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@standard-schema/spec@1.0.0': {} + '@starknet-io/types-js@0.7.10': {} + '@starknet-io/types-js@0.8.4': {} + '@tokenizer/inflate@0.2.7': dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 fflate: 0.8.2 token-types: 6.1.1 transitivePeerDependencies: @@ -4211,11 +4418,15 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.19.11 + '@types/node': 20.19.12 '@types/connect@3.4.38': dependencies: - '@types/node': 20.19.11 + '@types/node': 20.19.12 + + '@types/cron@2.4.3': + dependencies: + cron: 4.3.3 '@types/eslint-scope@3.7.7': dependencies: @@ -4231,7 +4442,7 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.19.11 + '@types/node': 20.19.12 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 0.17.5 @@ -4245,7 +4456,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.19.11 + '@types/node': 20.19.12 '@types/http-errors@2.0.5': {} @@ -4266,9 +4477,11 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/luxon@3.7.1': {} + '@types/mime@1.3.5': {} - '@types/node@20.19.11': + '@types/node@20.19.12': dependencies: undici-types: 6.21.0 @@ -4279,19 +4492,19 @@ snapshots: '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.19.11 + '@types/node': 20.19.12 '@types/serve-static@1.15.8': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 20.19.11 + '@types/node': 20.19.12 '@types/send': 0.17.5 '@types/stack-utils@2.0.3': {} '@types/triple-beam@1.3.5': {} - '@types/validator@13.15.2': {} + '@types/validator@13.15.3': {} '@types/yargs-parser@21.0.3': {} @@ -4299,14 +4512,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.40.0(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.40.0 - '@typescript-eslint/type-utils': 8.40.0(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/utils': 8.40.0(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.40.0 + '@typescript-eslint/parser': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/type-utils': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 7.0.5 @@ -4316,57 +4529,57 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.9.2)': + '@typescript-eslint/parser@8.42.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.40.0 - '@typescript-eslint/types': 8.40.0 - '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.40.0 - debug: 4.4.1(supports-color@5.5.0) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 + debug: 4.4.1 eslint: 8.57.1 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.40.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.42.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) - '@typescript-eslint/types': 8.40.0 - debug: 4.4.1(supports-color@5.5.0) + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + debug: 4.4.1 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.40.0': + '@typescript-eslint/scope-manager@8.42.0': dependencies: - '@typescript-eslint/types': 8.40.0 - '@typescript-eslint/visitor-keys': 8.40.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 - '@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.42.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.40.0(eslint@8.57.1)(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.42.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.40.0 - '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.40.0(eslint@8.57.1)(typescript@5.9.2) - debug: 4.4.1(supports-color@5.5.0) + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + debug: 4.4.1 eslint: 8.57.1 ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.40.0': {} + '@typescript-eslint/types@8.42.0': {} - '@typescript-eslint/typescript-estree@8.40.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.42.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.40.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) - '@typescript-eslint/types': 8.40.0 - '@typescript-eslint/visitor-keys': 8.40.0 - debug: 4.4.1(supports-color@5.5.0) + '@typescript-eslint/project-service': 8.42.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -4376,20 +4589,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.40.0(eslint@8.57.1)(typescript@5.9.2)': + '@typescript-eslint/utils@8.42.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.40.0 - '@typescript-eslint/types': 8.40.0 - '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) + '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) eslint: 8.57.1 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.40.0': + '@typescript-eslint/visitor-keys@8.42.0': dependencies: - '@typescript-eslint/types': 8.40.0 + '@typescript-eslint/types': 8.42.0 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} @@ -4530,7 +4743,7 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4699,12 +4912,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.25.3: + browserslist@4.25.4: dependencies: - caniuse-lite: 1.0.30001735 - electron-to-chromium: 1.5.207 + caniuse-lite: 1.0.30001739 + electron-to-chromium: 1.5.214 node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.3) + update-browserslist-db: 1.1.3(browserslist@4.25.4) bs-logger@0.2.6: dependencies: @@ -4727,6 +4940,21 @@ snapshots: bytes@3.1.2: {} + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4750,7 +4978,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001735: {} + caniuse-lite@1.0.30001739: {} cardinal@2.1.1: dependencies: @@ -4782,18 +5010,26 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chrome-trace-event@1.0.4: {} ci-info@3.9.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + cjs-module-lexer@1.4.3: {} class-transformer@0.5.1: {} class-validator@0.14.2: dependencies: - '@types/validator': 13.15.2 - libphonenumber-js: 1.12.12 + '@types/validator': 13.15.3 + libphonenumber-js: 1.12.15 validator: 13.15.15 cli-boxes@2.2.1: {} @@ -4892,8 +5128,12 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 + confbox@0.2.2: {} + consola@2.15.3: {} + consola@3.4.2: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -4924,13 +5164,13 @@ snapshots: optionalDependencies: typescript: 5.7.2 - create-jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + create-jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -4941,6 +5181,11 @@ snapshots: create-require@1.1.1: {} + cron@4.3.3: + dependencies: + '@types/luxon': 3.7.1 + luxon: 3.7.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4955,18 +5200,18 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.1(supports-color@5.5.0): + debug@4.4.1: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 5.5.0 decimal.js@10.6.0: {} - dedent@1.6.0: {} + dedent@1.7.0: {} deep-is@0.1.4: {} + deepmerge-ts@7.1.5: {} + deepmerge@4.3.1: {} defaults@1.0.4: @@ -4979,10 +5224,14 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + defu@6.1.4: {} + delayed-stream@1.0.0: {} depd@2.0.0: {} + destr@2.0.5: {} + destroy@1.2.0: {} detect-newline@3.1.0: {} @@ -5002,8 +5251,16 @@ snapshots: dotenv-expand@10.0.0: {} + dotenv-expand@12.0.1: + dependencies: + dotenv: 16.6.1 + dotenv@16.4.5: {} + dotenv@16.4.7: {} + + dotenv@16.6.1: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5014,7 +5271,12 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.207: {} + effect@3.16.12: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + + electron-to-chromium@1.5.214: {} emittery@0.13.1: {} @@ -5022,6 +5284,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.0: {} + enabled@2.0.0: {} encodeurl@1.0.2: {} @@ -5031,7 +5295,7 @@ snapshots: enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.2 + tapable: 2.2.3 error-ex@1.3.2: dependencies: @@ -5094,7 +5358,7 @@ snapshots: eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.1) '@eslint-community/regexpp': 4.12.1 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 @@ -5105,7 +5369,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -5219,12 +5483,18 @@ snapshots: transitivePeerDependencies: - supports-color + exsolve@1.0.7: {} + external-editor@3.1.0: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -5243,7 +5513,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.0.6: {} + fast-uri@3.1.0: {} fastq@1.19.1: dependencies: @@ -5255,11 +5525,6 @@ snapshots: fecha@4.2.3: {} - fetch-cookie@3.0.1: - dependencies: - set-cookie-parser: 2.7.1 - tough-cookie: 4.1.4 - fflate@0.8.2: {} figures@3.2.0: @@ -5279,7 +5544,7 @@ snapshots: '@tokenizer/inflate': 0.2.7 strtok3: 10.3.4 token-types: 6.1.1 - uint8array-extras: 1.4.1 + uint8array-extras: 1.5.0 transitivePeerDependencies: - supports-color @@ -5339,7 +5604,7 @@ snapshots: node-abort-controller: 3.1.1 schema-utils: 3.3.0 semver: 7.7.2 - tapable: 2.2.2 + tapable: 2.2.3 typescript: 5.7.2 webpack: 5.97.1 @@ -5402,6 +5667,15 @@ snapshots: get-stream@6.0.1: {} + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.1 + pathe: 2.0.3 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5451,8 +5725,6 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-own-prop@2.0.0: {} @@ -5489,8 +5761,6 @@ snapshots: ieee754@1.2.1: {} - ignore-by-default@1.0.1: {} - ignore@5.3.2: {} ignore@7.0.5: {} @@ -5586,13 +5856,6 @@ snapshots: isexe@2.0.0: {} - isomorphic-fetch@3.0.0: - dependencies: - node-fetch: 2.7.0 - whatwg-fetch: 3.6.20 - transitivePeerDependencies: - - encoding - istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -5623,7 +5886,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -5654,10 +5917,10 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 chalk: 4.1.2 co: 4.6.0 - dedent: 1.6.0 + dedent: 1.7.0 is-generator-fn: 2.1.0 jest-each: 29.7.0 jest-matcher-utils: 29.7.0 @@ -5674,16 +5937,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + jest-cli@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + create-jest: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -5693,7 +5956,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + jest-config@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): dependencies: '@babel/core': 7.28.3 '@jest/test-sequencer': 29.7.0 @@ -5718,8 +5981,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.19.11 - ts-node: 10.9.2(@types/node@20.19.11)(typescript@5.9.2) + '@types/node': 20.19.12 + ts-node: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -5748,7 +6011,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -5758,7 +6021,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.19.11 + '@types/node': 20.19.12 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -5797,7 +6060,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -5832,7 +6095,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -5860,7 +6123,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 @@ -5906,7 +6169,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -5925,7 +6188,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.11 + '@types/node': 20.19.12 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -5934,29 +6197,31 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.11 + '@types/node': 20.19.12 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.19.11 + '@types/node': 20.19.12 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)): + jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest-cli: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node + jiti@2.5.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -6007,7 +6272,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libphonenumber-js@1.12.12: {} + libphonenumber-js@1.12.15: {} lines-and-columns@1.2.4: {} @@ -6045,7 +6310,7 @@ snapshots: long@5.3.2: {} - lossless-json@4.1.1: {} + lossless-json@4.2.0: {} lru-cache@10.4.3: {} @@ -6053,6 +6318,8 @@ snapshots: dependencies: yallist: 3.1.1 + luxon@3.7.1: {} + magic-string@0.30.8: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6148,6 +6415,8 @@ snapshots: dependencies: lodash: 4.17.21 + node-fetch-native@1.6.7: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -6156,31 +6425,28 @@ snapshots: node-releases@2.0.19: {} - nodemon@3.1.10: - dependencies: - chokidar: 3.6.0 - debug: 4.4.1(supports-color@5.5.0) - ignore-by-default: 1.0.1 - minimatch: 3.1.2 - pstree.remy: 1.1.8 - semver: 7.7.2 - simple-update-notifier: 2.0.0 - supports-color: 5.5.0 - touch: 3.1.1 - undefsafe: 2.0.5 - normalize-path@3.0.0: {} npm-run-path@4.0.1: dependencies: path-key: 3.1.1 + nypm@0.6.1: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.1 + object-assign@4.1.1: {} object-hash@2.2.0: {} object-inspect@1.13.4: {} + ohash@2.0.11: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -6274,6 +6540,10 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -6286,6 +6556,12 @@ snapshots: dependencies: find-up: 4.1.0 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + pluralize@8.0.0: {} prelude-ls@1.2.1: {} @@ -6302,11 +6578,14 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prisma@5.22.0: + prisma@6.15.0(typescript@5.9.2): dependencies: - '@prisma/engines': 5.22.0 + '@prisma/config': 6.15.0 + '@prisma/engines': 6.15.0 optionalDependencies: - fsevents: 2.3.3 + typescript: 5.9.2 + transitivePeerDependencies: + - magicast prompts@2.4.2: dependencies: @@ -6325,7 +6604,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.19.11 + '@types/node': 20.19.12 long: 5.3.2 proxy-addr@2.0.7: @@ -6335,12 +6614,6 @@ snapshots: proxy-from-env@1.1.0: {} - psl@1.15.0: - dependencies: - punycode: 2.3.1 - - pstree.remy@1.1.8: {} - punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -6353,8 +6626,6 @@ snapshots: dependencies: side-channel: 1.1.0 - querystringify@2.2.0: {} - queue-microtask@1.2.3: {} randombytes@2.1.0: @@ -6370,6 +6641,11 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + react-is@18.3.1: {} readable-stream@3.6.2: @@ -6382,6 +6658,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + redeyed@2.1.1: dependencies: esprima: 4.0.1 @@ -6394,8 +6672,6 @@ snapshots: require-from-string@2.0.2: {} - requires-port@1.0.0: {} - resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -6493,8 +6769,6 @@ snapshots: transitivePeerDependencies: - supports-color - set-cookie-parser@2.7.1: {} - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -6550,10 +6824,6 @@ snapshots: dependencies: is-arrayish: 0.3.2 - simple-update-notifier@2.0.0: - dependencies: - semver: 7.7.2 - sisteransi@1.0.5: {} slash@3.0.0: {} @@ -6584,21 +6854,18 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 - starknet@6.24.1: + starknet@7.6.4: 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-07': '@starknet-io/types-js@0.7.10' + '@starknet-io/starknet-types-08': '@starknet-io/types-js@0.8.4' abi-wan-kanabi: 2.2.4 - fetch-cookie: 3.0.1 - isomorphic-fetch: 3.0.0 - lossless-json: 4.1.1 + lossless-json: 4.2.0 pako: 2.1.0 - starknet-types-07: '@starknet-io/types-js@0.7.10' ts-mixer: 6.0.4 - transitivePeerDependencies: - - encoding statuses@2.0.1: {} @@ -6649,7 +6916,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.1 fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 3.5.4 @@ -6666,10 +6933,6 @@ snapshots: transitivePeerDependencies: - supports-color - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -6688,7 +6951,7 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - tapable@2.2.2: {} + tapable@2.2.3: {} terser-webpack-plugin@5.3.14(webpack@5.97.1): dependencies: @@ -6696,10 +6959,10 @@ snapshots: jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - terser: 5.43.1 + terser: 5.44.0 webpack: 5.97.1 - terser@5.43.1: + terser@5.44.0: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 @@ -6718,6 +6981,8 @@ snapshots: through@2.3.8: {} + tinyexec@1.0.1: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -6736,15 +7001,6 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - touch@3.1.1: {} - - tough-cookie@4.1.4: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 - tr46@0.0.3: {} tree-kill@1.2.2: {} @@ -6755,12 +7011,12 @@ snapshots: dependencies: typescript: 5.9.2 - ts-jest@29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)))(typescript@5.9.2): + ts-jest@29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)))(typescript@5.9.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 29.7.0(@types/node@20.19.11)(ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2)) + jest: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -6775,7 +7031,7 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.28.3) jest-util: 29.7.0 - ts-loader@9.5.2(typescript@5.9.2)(webpack@5.97.1): + ts-loader@9.5.4(typescript@5.9.2)(webpack@5.97.1): dependencies: chalk: 4.1.2 enhanced-resolve: 5.18.3 @@ -6787,14 +7043,14 @@ snapshots: ts-mixer@6.0.4: {} - ts-node@10.9.2(@types/node@20.19.11)(typescript@5.9.2): + ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.11 + '@types/node': 20.19.12 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -6809,7 +7065,7 @@ snapshots: dependencies: chalk: 4.1.2 enhanced-resolve: 5.18.3 - tapable: 2.2.2 + tapable: 2.2.3 tsconfig-paths: 4.2.0 tsconfig-paths@4.2.0: @@ -6850,21 +7106,17 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 - uint8array-extras@1.4.1: {} - - undefsafe@2.0.5: {} + uint8array-extras@1.5.0: {} undici-types@6.21.0: {} - universalify@0.2.0: {} - universalify@2.0.1: {} unpipe@1.0.0: {} - update-browserslist-db@1.1.3(browserslist@4.25.3): + update-browserslist-db@1.1.3(browserslist@4.25.4): dependencies: - browserslist: 4.25.3 + browserslist: 4.25.4 escalade: 3.2.0 picocolors: 1.1.1 @@ -6872,11 +7124,6 @@ snapshots: 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: {} utils-merge@1.0.1: {} @@ -6920,7 +7167,7 @@ snapshots: '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 - browserslist: 4.25.3 + browserslist: 4.25.4 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -6933,7 +7180,7 @@ snapshots: mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 3.3.0 - tapable: 2.2.2 + tapable: 2.2.3 terser-webpack-plugin: 5.3.14(webpack@5.97.1) watchpack: 2.4.4 webpack-sources: 3.3.3 @@ -6942,8 +7189,6 @@ snapshots: - esbuild - uglify-js - whatwg-fetch@3.6.20: {} - whatwg-url@5.0.0: dependencies: tr46: 0.0.3 diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92c..00000000 --- a/backend/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/backend/tsconfig.base.json b/backend/tsconfig.base.json index f01996b6..095d9586 100644 --- a/backend/tsconfig.base.json +++ b/backend/tsconfig.base.json @@ -23,7 +23,9 @@ "@forge/config": ["libs/config/src"], "@forge/config/*": ["libs/config/src/*"], "@forge/logger": ["libs/logger/src"], - "@forge/logger/*": ["libs/logger/src/*"] + "@forge/logger/*": ["libs/logger/src/*"], + "@forge/starknet": ["libs/starknet/src"], + "@forge/starknet/*": ["libs/starknet/src/*"] } }, "exclude": ["node_modules", "dist"] From cf09fe00ae187bfd1423abf981be8bd88088394c Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:31:09 +0200 Subject: [PATCH 21/54] Update backend README with comprehensive status and TODOs - Document incomplete relayer services with clear warnings - Update API endpoints count and descriptions to match implementation - Add comprehensive "What's Missing / TODO" section covering: - Incomplete services (automatic redeem and on-chain AUM relayers) - Missing features (auth, rate limiting, caching, monitoring, testing) - Configuration gaps (env validation, secrets management) - Update Docker instructions for development setup - Improve library descriptions with actual functionality --- backend/README.md | 66 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/backend/README.md b/backend/README.md index 58469cc1..dcd027a0 100644 --- a/backend/README.md +++ b/backend/README.md @@ -10,21 +10,26 @@ It provides minimal functionality to index vault events and expose basic API end ### Applications -- **`api`** - Minimal HTTP API with 4 endpoints +- **`api`** - HTTP API with vault data endpoints - **`indexer`** - Event indexer for vault reports and redeems +- **`relayerAutomaticRedeem`** - ⚠️ **INCOMPLETE** - Automatic redeem processing service +- **`relayerOnChainAum`** - ⚠️ **INCOMPLETE** - On-chain AUM management service ### Libraries - **`@forge/config`** - Configuration management -- **`@forge/db`** - Prisma database client -- **`@forge/logger`** - Logging utilities +- **`@forge/db`** - Prisma database client with Prisma schema +- **`@forge/logger`** - Structured logging with Winston +- **`@forge/starknet`** - StarkNet interaction service -## API Endpoints (4 total) +## API Endpoints (6 total) -1. `GET /health` - Health check (`{ status: "ok" }`) +1. `GET /health` - Health check and API info 2. `GET /pending-redeems/:address` - Pending redeems for an address (with limit/offset) 3. `GET /reports/last` - Latest report from database 4. `GET /redeems/:id` - Redeem details by ID +5. `GET /strategy-analytics` - Strategy analytics with APY calculations (with limit/offset) +6. `GET /redeem-required-assets` - Required assets for pending redeems by epoch ## Events Indexed (3 total) @@ -41,7 +46,41 @@ The indexer only tracks these events: - `RedeemClaimed` - `IndexerStatus` -## Quick Start with Docker +## ⚠️ What's Missing / TODO + +### Incomplete Services + +1. **`relayerAutomaticRedeem`** - Service skeleton exists but needs implementation: + - Automatic detection of claimable redeems + - Transaction submission logic for claiming + - Error handling and retry mechanisms + - Configuration for gas management + +2. **`relayerOnChainAum`** - Basic structure exists but missing: + - AUM calculation and reporting logic + - Integration with AUM providers + - Scheduled execution framework + - On-chain transaction handling + +### Missing Features + +- **Authentication/Authorization** - All endpoints are currently public +- **Rate Limiting** - No API rate limiting implemented +- **Caching** - No caching layer for expensive operations +- **Monitoring** - Basic logging exists but no metrics/alerting +- **Error Recovery** - Limited error handling in indexer +- **Database Migrations** - Schema exists but migration strategy incomplete +- **Testing** - No test suite implemented +- **API Documentation** - No OpenAPI/Swagger documentation + +### Configuration Gaps + +- **Environment Validation** - Incomplete validation of required env vars +- **Network Configuration** - Limited network-specific configurations +- **Service Discovery** - No service mesh or discovery mechanism +- **Secrets Management** - Basic env vars, no proper secrets handling + +## Quick Start with Docker (Development) 1. **Clone and setup:** @@ -50,19 +89,24 @@ cp .env.example .env # Edit .env with your configuration ``` -2. **Run with Docker Compose:** +2. **Run with Docker Compose (Development Mode):** ```bash -docker-compose up -d +docker-compose -f docker-compose.dev.yml up -d ``` This starts: - PostgreSQL database -- Indexer service -- API service on port 3000 +- API service on port 3000 (with hot reload) +- Indexer service (with hot reload) +- ⚠️ **Note**: Relayer services have Dockerfiles but are not yet functional + +3. **Available Services:** + - API: http://localhost:3000 + - Database: localhost:5432 -3. **Check health:** +4. **Check health:** ```bash curl http://localhost:3000/health From 35663e7b5ade1229ba7678acd3e7ec141fb7eaa7 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:32:28 +0200 Subject: [PATCH 22/54] Update README.md --- backend/README.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/backend/README.md b/backend/README.md index dcd027a0..02ebcd83 100644 --- a/backend/README.md +++ b/backend/README.md @@ -46,40 +46,6 @@ The indexer only tracks these events: - `RedeemClaimed` - `IndexerStatus` -## ⚠️ What's Missing / TODO - -### Incomplete Services - -1. **`relayerAutomaticRedeem`** - Service skeleton exists but needs implementation: - - Automatic detection of claimable redeems - - Transaction submission logic for claiming - - Error handling and retry mechanisms - - Configuration for gas management - -2. **`relayerOnChainAum`** - Basic structure exists but missing: - - AUM calculation and reporting logic - - Integration with AUM providers - - Scheduled execution framework - - On-chain transaction handling - -### Missing Features - -- **Authentication/Authorization** - All endpoints are currently public -- **Rate Limiting** - No API rate limiting implemented -- **Caching** - No caching layer for expensive operations -- **Monitoring** - Basic logging exists but no metrics/alerting -- **Error Recovery** - Limited error handling in indexer -- **Database Migrations** - Schema exists but migration strategy incomplete -- **Testing** - No test suite implemented -- **API Documentation** - No OpenAPI/Swagger documentation - -### Configuration Gaps - -- **Environment Validation** - Incomplete validation of required env vars -- **Network Configuration** - Limited network-specific configurations -- **Service Discovery** - No service mesh or discovery mechanism -- **Secrets Management** - Basic env vars, no proper secrets handling - ## Quick Start with Docker (Development) 1. **Clone and setup:** From 38429ff07b2b30e38fa5d91ca218f025f5bd8f69 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:08:09 +0200 Subject: [PATCH 23/54] Add BringLiquidity event to vault contract - Add new event structure with caller, amount, and state info - Emit event when liquidity is brought back from allocators - Update vault hash in config after contract changes --- packages/vault/src/vault/vault.cairo | 23 +++++++++++++++++++---- scripts/configs/config.json | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index af0030e1..e6e3f6ac 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -159,7 +159,8 @@ pub mod Vault { // Vault-specific events RedeemRequested: RedeemRequested, // Emitted when a redemption is requested RedeemClaimed: RedeemClaimed, // Emitted when a redemption is claimed - Report: Report // Emitted when oracle reports new AUM + Report: Report, // Emitted when oracle reports new AUM + BringLiquidity: BringLiquidity // Emitted when liquidity is brought back from allocators } /// Event emitted when a user requests a redemption @@ -194,6 +195,15 @@ pub mod Vault { pub performance_fee_shares: u256 // Performance fee shares minted } + /// Event emitted when liquidity is brought back from allocators + #[derive(Drop, starknet::Event)] + pub struct BringLiquidity { + pub caller: ContractAddress, // Address that initiated the liquidity transfer + pub amount: u256, // Amount of assets brought back to vault + pub new_buffer: u256, // New buffer amount after the transfer + pub new_aum: u256 // New AUM amount after the transfer + } + /// Initialize the vault with configuration parameters /// Sets up all components, roles, and fee structure #[constructor] @@ -775,10 +785,15 @@ pub mod Vault { fn bring_liquidity( ref self: ContractState, amount: u256, ) { // Amount of assets to bring back + let caller = get_caller_address(); ERC20ABIDispatcher { contract_address: self.erc4626.asset() } - .transfer_from(get_caller_address(), starknet::get_contract_address(), amount); - self.buffer.write(self.buffer.read() + amount); // Increase buffer - self.aum.write(self.aum.read() - amount); // Decrease deployed AUM + .transfer_from(caller, starknet::get_contract_address(), amount); + let new_buffer = self.buffer.read() + amount; // Calculate new buffer + let new_aum = self.aum.read() - amount; // Calculate new AUM + self.buffer.write(new_buffer); // Increase buffer + self.aum.write(new_aum); // Decrease deployed AUM + + self.emit(BringLiquidity { caller, amount, new_buffer, new_aum }); } // --- State Getter Functions --- diff --git a/scripts/configs/config.json b/scripts/configs/config.json index 22cfe6cf..a3189f5f 100644 --- a/scripts/configs/config.json +++ b/scripts/configs/config.json @@ -5,7 +5,7 @@ }, "mainnet": { "hash": { - "Vault": "0x5058442b8c9d18dc0fdfa4beea4c2d5dc843bc1d121dc6f8acba9f9252b4072", + "Vault": "0x516d937281e70cc858ddc2a5a2c98fe97782d290564c6e94d2e8c91dd70cc2f", "VaultAllocator": "0x7f2faf80d197cbfd7286fa0b8328e9d15eff219138611573beb8280a92712e5", "RedeemRequest": "0x213cf96be799ccd7d4775e3798c880c093197ac19313d6032f43c8d22a6346a", "AvnuMiddleware": "0x142dff46d17eb94e9c77a771968ccd8257d8eed5d6a84c3e37f50e15dc39a9f", From aafcffe45554aff1d46381f650cf693c89952df9 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:17:20 +0200 Subject: [PATCH 24/54] Add GitHub workflow and Docker files --- .github/workflows/release.yml | 68 ++++++++++++++++++++++++++++ backend/Dockerfile.api | 30 ++++++++++++ backend/Dockerfile.indexer | 21 +++++++++ backend/Dockerfile.relayerOnChainAum | 22 +++++++++ 4 files changed, 141 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 backend/Dockerfile.api create mode 100644 backend/Dockerfile.indexer create mode 100644 backend/Dockerfile.relayerOnChainAum diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..8b6a939b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: release-images + +on: + push: + tags: + - "v*" # ex: v1.0.0 + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - service: api + dockerfile: backend/Dockerfile.api + image: ghcr.io/forgeyields/svk-api + - service: indexer + dockerfile: backend/Dockerfile.indexer + image: ghcr.io/forgeyields/svk-indexer + - service: aum + dockerfile: backend/Dockerfile.relayerOnChainAum + image: ghcr.io/forgeyields/svk-aum + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract version + id: ver + run: | + TAG="${GITHUB_REF_NAME}" + echo "version=${TAG#v}" >> $GITHUB_OUTPUT + echo "mm=$(echo ${TAG#v} | awk -F. '{print $1"."$2}')" >> $GITHUB_OUTPUT + echo "maj=$(echo ${TAG#v} | awk -F. '{print $1}')" >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build + Push ${{ matrix.service }} + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + platforms: linux/amd64 + tags: | + ${{ matrix.image }}:v${{ steps.ver.outputs.version }} + ${{ matrix.image }}:v${{ steps.ver.outputs.mm }} + ${{ matrix.image }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: false + sbom: false diff --git a/backend/Dockerfile.api b/backend/Dockerfile.api new file mode 100644 index 00000000..c0292bfb --- /dev/null +++ b/backend/Dockerfile.api @@ -0,0 +1,30 @@ +# ---- build ---- +FROM node:20-alpine AS build +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.9.0 --activate +COPY . . +RUN pnpm install --frozen-lockfile +# build tous les workspaces (libs + apps) pour résoudre les liens pnpm +RUN pnpm -r build +# garder uniquement deps prod +RUN pnpm prune --prod + +# ---- runtime ---- +FROM node:20-alpine +ENV NODE_ENV=production +WORKDIR /app +# user non-root +RUN addgroup -S app && adduser -S app -G app +USER app +# copier deps prod +COPY --from=build /app/node_modules ./node_modules +# copier manifests afin que les liens pnpm workspace restent valides +COPY --from=build /app/package.json ./ +COPY --from=build /app/pnpm-workspace.yaml ./ +# copier le code compilé des apps et libs +COPY --from=build /app/apps/api/dist ./apps/api/dist +COPY --from=build /app/libs ./libs +# (facultatif) si Prisma client est généré dans libs/db, on garde son dossier +CMD ["node", "apps/api/dist/main.js"] +EXPOSE 3001 + \ No newline at end of file diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer new file mode 100644 index 00000000..c46d7de6 --- /dev/null +++ b/backend/Dockerfile.indexer @@ -0,0 +1,21 @@ +# ---- build ---- +FROM node:20-alpine AS build +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.9.0 --activate +COPY . . +RUN pnpm install --frozen-lockfile +RUN pnpm -r build +RUN pnpm prune --prod + +# ---- runtime ---- +FROM node:20-alpine +ENV NODE_ENV=production +WORKDIR /app +RUN addgroup -S app && adduser -S app -G app +USER app +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/package.json ./ +COPY --from=build /app/pnpm-workspace.yaml ./ +COPY --from=build /app/apps/indexer/dist ./apps/indexer/dist +COPY --from=build /app/libs ./libs +CMD ["node", "apps/indexer/dist/main.js"] diff --git a/backend/Dockerfile.relayerOnChainAum b/backend/Dockerfile.relayerOnChainAum new file mode 100644 index 00000000..c11363f6 --- /dev/null +++ b/backend/Dockerfile.relayerOnChainAum @@ -0,0 +1,22 @@ +# ---- build ---- + FROM node:20-alpine AS build + WORKDIR /app + RUN corepack enable && corepack prepare pnpm@9.9.0 --activate + COPY . . + RUN pnpm install --frozen-lockfile + RUN pnpm -r build + RUN pnpm prune --prod + + # ---- runtime ---- + FROM node:20-alpine + ENV NODE_ENV=production + WORKDIR /app + RUN addgroup -S app && adduser -S app -G app + USER app + COPY --from=build /app/node_modules ./node_modules + COPY --from=build /app/package.json ./ + COPY --from=build /app/pnpm-workspace.yaml ./ + COPY --from=build /app/apps/relayerOnChainAum/dist ./apps/relayerOnChainAum/dist + COPY --from=build /app/libs ./libs + CMD ["node", "apps/relayerOnChainAum/dist/main.js"] + \ No newline at end of file From 7ef907cfe1a05328d75fc969f850539670db1ee1 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:21:52 +0200 Subject: [PATCH 25/54] Fix Docker build context and dockerfile paths for backend monorepo --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b6a939b..ce1e0c7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,13 +17,13 @@ jobs: matrix: include: - service: api - dockerfile: backend/Dockerfile.api + dockerfile: Dockerfile.api image: ghcr.io/forgeyields/svk-api - service: indexer - dockerfile: backend/Dockerfile.indexer + dockerfile: Dockerfile.indexer image: ghcr.io/forgeyields/svk-indexer - service: aum - dockerfile: backend/Dockerfile.relayerOnChainAum + dockerfile: Dockerfile.relayerOnChainAum image: ghcr.io/forgeyields/svk-aum steps: @@ -54,7 +54,7 @@ jobs: - name: Build + Push ${{ matrix.service }} uses: docker/build-push-action@v6 with: - context: . + context: backend file: ${{ matrix.dockerfile }} push: true platforms: linux/amd64 From 0d3ada0ad9c177e5f8328fab3c2ed0fcfdcdfd09 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:29:08 +0200 Subject: [PATCH 26/54] Add Prisma client generation to all Dockerfiles --- backend/Dockerfile.api | 2 ++ backend/Dockerfile.indexer | 2 ++ backend/Dockerfile.relayerOnChainAum | 2 ++ 3 files changed, 6 insertions(+) diff --git a/backend/Dockerfile.api b/backend/Dockerfile.api index c0292bfb..9e2648a9 100644 --- a/backend/Dockerfile.api +++ b/backend/Dockerfile.api @@ -4,6 +4,8 @@ WORKDIR /app RUN corepack enable && corepack prepare pnpm@9.9.0 --activate COPY . . RUN pnpm install --frozen-lockfile +# generate Prisma client +RUN pnpm prisma:generate # build tous les workspaces (libs + apps) pour résoudre les liens pnpm RUN pnpm -r build # garder uniquement deps prod diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer index c46d7de6..9e9bdae5 100644 --- a/backend/Dockerfile.indexer +++ b/backend/Dockerfile.indexer @@ -4,6 +4,8 @@ WORKDIR /app RUN corepack enable && corepack prepare pnpm@9.9.0 --activate COPY . . RUN pnpm install --frozen-lockfile +# generate Prisma client +RUN pnpm prisma:generate RUN pnpm -r build RUN pnpm prune --prod diff --git a/backend/Dockerfile.relayerOnChainAum b/backend/Dockerfile.relayerOnChainAum index c11363f6..edbb1429 100644 --- a/backend/Dockerfile.relayerOnChainAum +++ b/backend/Dockerfile.relayerOnChainAum @@ -4,6 +4,8 @@ RUN corepack enable && corepack prepare pnpm@9.9.0 --activate COPY . . RUN pnpm install --frozen-lockfile + # generate Prisma client + RUN pnpm prisma:generate RUN pnpm -r build RUN pnpm prune --prod From d67e16d74ba7382b8c51f8f601710c984d24b32e Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:30:58 +0200 Subject: [PATCH 27/54] Add missing Dockerfile.relayerAutomaticRedeem and update CI workflow --- backend/Dockerfile.relayerAutomaticRedeem | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 backend/Dockerfile.relayerAutomaticRedeem diff --git a/backend/Dockerfile.relayerAutomaticRedeem b/backend/Dockerfile.relayerAutomaticRedeem new file mode 100644 index 00000000..83da54b6 --- /dev/null +++ b/backend/Dockerfile.relayerAutomaticRedeem @@ -0,0 +1,30 @@ +# ---- build ---- +FROM node:20-alpine AS build +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.9.0 --activate +COPY . . +RUN pnpm install --frozen-lockfile +# generate Prisma client +RUN pnpm prisma:generate +# build tous les workspaces (libs + apps) pour résoudre les liens pnpm +RUN pnpm -r build +# garder uniquement deps prod +RUN pnpm prune --prod + +# ---- runtime ---- +FROM node:20-alpine +ENV NODE_ENV=production +WORKDIR /app +# user non-root +RUN addgroup -S app && adduser -S app -G app +USER app +# copier deps prod +COPY --from=build /app/node_modules ./node_modules +# copier manifests afin que les liens pnpm workspace restent valides +COPY --from=build /app/package.json ./ +COPY --from=build /app/pnpm-workspace.yaml ./ +# copier le code compilé des apps et libs +COPY --from=build /app/apps/relayerAutomaticRedeem/dist ./apps/relayerAutomaticRedeem/dist +COPY --from=build /app/libs ./libs +# (facultatif) si Prisma client est généré dans libs/db, on garde son dossier +CMD ["node", "apps/relayerAutomaticRedeem/dist/main.js"] \ No newline at end of file From c36822ea79a8563f618f2baf2b0857bb752e32a3 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:31:24 +0200 Subject: [PATCH 28/54] Update release.yml --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce1e0c7f..9984eb61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,6 +25,9 @@ jobs: - service: aum dockerfile: Dockerfile.relayerOnChainAum image: ghcr.io/forgeyields/svk-aum + - service: relayer-redeem + dockerfile: Dockerfile.relayerAutomaticRedeem + image: ghcr.io/forgeyields/svk-relayer-redeem steps: - name: Checkout From c859a5b46cd88ed6f4fe8dd419b728baff2c8703 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:34:23 +0200 Subject: [PATCH 29/54] Fix Docker context and file paths - use root context with backend/ prefix --- .github/workflows/release.yml | 10 +++++----- backend/Dockerfile.api | 2 +- backend/Dockerfile.indexer | 2 +- backend/Dockerfile.relayerAutomaticRedeem | 2 +- backend/Dockerfile.relayerOnChainAum | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9984eb61..1d6eef41 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,16 +17,16 @@ jobs: matrix: include: - service: api - dockerfile: Dockerfile.api + dockerfile: backend/Dockerfile.api image: ghcr.io/forgeyields/svk-api - service: indexer - dockerfile: Dockerfile.indexer + dockerfile: backend/Dockerfile.indexer image: ghcr.io/forgeyields/svk-indexer - service: aum - dockerfile: Dockerfile.relayerOnChainAum + dockerfile: backend/Dockerfile.relayerOnChainAum image: ghcr.io/forgeyields/svk-aum - service: relayer-redeem - dockerfile: Dockerfile.relayerAutomaticRedeem + dockerfile: backend/Dockerfile.relayerAutomaticRedeem image: ghcr.io/forgeyields/svk-relayer-redeem steps: @@ -57,7 +57,7 @@ jobs: - name: Build + Push ${{ matrix.service }} uses: docker/build-push-action@v6 with: - context: backend + context: . file: ${{ matrix.dockerfile }} push: true platforms: linux/amd64 diff --git a/backend/Dockerfile.api b/backend/Dockerfile.api index 9e2648a9..5e6c1ad2 100644 --- a/backend/Dockerfile.api +++ b/backend/Dockerfile.api @@ -2,7 +2,7 @@ FROM node:20-alpine AS build WORKDIR /app RUN corepack enable && corepack prepare pnpm@9.9.0 --activate -COPY . . +COPY backend/ . RUN pnpm install --frozen-lockfile # generate Prisma client RUN pnpm prisma:generate diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer index 9e9bdae5..6247f73a 100644 --- a/backend/Dockerfile.indexer +++ b/backend/Dockerfile.indexer @@ -2,7 +2,7 @@ FROM node:20-alpine AS build WORKDIR /app RUN corepack enable && corepack prepare pnpm@9.9.0 --activate -COPY . . +COPY backend/ . RUN pnpm install --frozen-lockfile # generate Prisma client RUN pnpm prisma:generate diff --git a/backend/Dockerfile.relayerAutomaticRedeem b/backend/Dockerfile.relayerAutomaticRedeem index 83da54b6..8f2272ed 100644 --- a/backend/Dockerfile.relayerAutomaticRedeem +++ b/backend/Dockerfile.relayerAutomaticRedeem @@ -2,7 +2,7 @@ FROM node:20-alpine AS build WORKDIR /app RUN corepack enable && corepack prepare pnpm@9.9.0 --activate -COPY . . +COPY backend/ . RUN pnpm install --frozen-lockfile # generate Prisma client RUN pnpm prisma:generate diff --git a/backend/Dockerfile.relayerOnChainAum b/backend/Dockerfile.relayerOnChainAum index edbb1429..c56b7a5f 100644 --- a/backend/Dockerfile.relayerOnChainAum +++ b/backend/Dockerfile.relayerOnChainAum @@ -2,7 +2,7 @@ FROM node:20-alpine AS build WORKDIR /app RUN corepack enable && corepack prepare pnpm@9.9.0 --activate - COPY . . + COPY backend/ . RUN pnpm install --frozen-lockfile # generate Prisma client RUN pnpm prisma:generate From 1d0fd7e77cda2a235a73c505c21db1754c6a3806 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 21:12:47 +0200 Subject: [PATCH 30/54] Fix Docker configuration and add docker-compose.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix all Dockerfiles to properly handle workspace dependencies - Remove pnpm prune --prod to preserve all dependencies - Add symlinks for missing dependencies (decimal.js, starknet, @apibara packages, cron) - Fix API service port exposure (3000 instead of 3001) - Add complete docker-compose.yml with all services uncommented - Add missing environment variables for onchain-aum-provider - Ensure proper file permissions and user setup 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- backend/Dockerfile.api | 22 ++++-- backend/Dockerfile.indexer | 16 ++++- backend/Dockerfile.relayerAutomaticRedeem | 14 ++-- backend/Dockerfile.relayerOnChainAum | 13 +++- backend/docker-compose.yml | 82 +++++++++++++++++++++++ 5 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 backend/docker-compose.yml diff --git a/backend/Dockerfile.api b/backend/Dockerfile.api index 5e6c1ad2..7dbd51be 100644 --- a/backend/Dockerfile.api +++ b/backend/Dockerfile.api @@ -8,8 +8,7 @@ RUN pnpm install --frozen-lockfile RUN pnpm prisma:generate # build tous les workspaces (libs + apps) pour résoudre les liens pnpm RUN pnpm -r build -# garder uniquement deps prod -RUN pnpm prune --prod +# copy the compiled apps directly without pruning to preserve all dependencies # ---- runtime ---- FROM node:20-alpine @@ -17,16 +16,27 @@ ENV NODE_ENV=production WORKDIR /app # user non-root RUN addgroup -S app && adduser -S app -G app -USER app # copier deps prod COPY --from=build /app/node_modules ./node_modules # copier manifests afin que les liens pnpm workspace restent valides COPY --from=build /app/package.json ./ COPY --from=build /app/pnpm-workspace.yaml ./ +# copier les package.json des apps et libs pour les deps +COPY --from=build /app/apps/api/package.json ./apps/api/ +COPY --from=build /app/libs/*/package.json ./libs/*/ # copier le code compilé des apps et libs COPY --from=build /app/apps/api/dist ./apps/api/dist COPY --from=build /app/libs ./libs -# (facultatif) si Prisma client est généré dans libs/db, on garde son dossier -CMD ["node", "apps/api/dist/main.js"] -EXPOSE 3001 +# créer les symlinks pour les packages workspace et dependencies manquantes +RUN mkdir -p node_modules/@forge && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/decimal.js@10.6.0/node_modules/decimal.js node_modules/decimal.js && \ + ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ + chown -R app:app . +USER app +CMD ["node", "apps/api/dist/apps/api/src/main.js"] +EXPOSE 3000 \ No newline at end of file diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer index 6247f73a..65acd78d 100644 --- a/backend/Dockerfile.indexer +++ b/backend/Dockerfile.indexer @@ -7,17 +7,27 @@ RUN pnpm install --frozen-lockfile # generate Prisma client RUN pnpm prisma:generate RUN pnpm -r build -RUN pnpm prune --prod +# skip pruning to preserve all workspace dependencies # ---- runtime ---- FROM node:20-alpine ENV NODE_ENV=production WORKDIR /app RUN addgroup -S app && adduser -S app -G app -USER app COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/package.json ./ COPY --from=build /app/pnpm-workspace.yaml ./ COPY --from=build /app/apps/indexer/dist ./apps/indexer/dist COPY --from=build /app/libs ./libs -CMD ["node", "apps/indexer/dist/main.js"] +# créer les symlinks pour les packages workspace et dependencies manquantes +RUN mkdir -p node_modules/@forge node_modules/@apibara && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ + ln -sf ../.pnpm/@apibara+protocol@0.4.9/node_modules/@apibara/protocol node_modules/@apibara/protocol && \ + ln -sf ../.pnpm/@apibara+starknet@0.5.0/node_modules/@apibara/starknet node_modules/@apibara/starknet && \ + chown -R app:app . +USER app +CMD ["node", "apps/indexer/dist/apps/indexer/src/main.js"] diff --git a/backend/Dockerfile.relayerAutomaticRedeem b/backend/Dockerfile.relayerAutomaticRedeem index 8f2272ed..63ff47e5 100644 --- a/backend/Dockerfile.relayerAutomaticRedeem +++ b/backend/Dockerfile.relayerAutomaticRedeem @@ -8,8 +8,7 @@ RUN pnpm install --frozen-lockfile RUN pnpm prisma:generate # build tous les workspaces (libs + apps) pour résoudre les liens pnpm RUN pnpm -r build -# garder uniquement deps prod -RUN pnpm prune --prod +# skip pruning to preserve all workspace dependencies # ---- runtime ---- FROM node:20-alpine @@ -17,7 +16,6 @@ ENV NODE_ENV=production WORKDIR /app # user non-root RUN addgroup -S app && adduser -S app -G app -USER app # copier deps prod COPY --from=build /app/node_modules ./node_modules # copier manifests afin que les liens pnpm workspace restent valides @@ -26,5 +24,13 @@ COPY --from=build /app/pnpm-workspace.yaml ./ # copier le code compilé des apps et libs COPY --from=build /app/apps/relayerAutomaticRedeem/dist ./apps/relayerAutomaticRedeem/dist COPY --from=build /app/libs ./libs -# (facultatif) si Prisma client est généré dans libs/db, on garde son dossier +# créer les symlinks pour les packages workspace et dependencies manquantes +RUN mkdir -p node_modules/@forge && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/cron@4.3.3/node_modules/cron node_modules/cron && \ + chown -R app:app . +USER app CMD ["node", "apps/relayerAutomaticRedeem/dist/main.js"] \ No newline at end of file diff --git a/backend/Dockerfile.relayerOnChainAum b/backend/Dockerfile.relayerOnChainAum index c56b7a5f..ff1302c3 100644 --- a/backend/Dockerfile.relayerOnChainAum +++ b/backend/Dockerfile.relayerOnChainAum @@ -7,18 +7,25 @@ # generate Prisma client RUN pnpm prisma:generate RUN pnpm -r build - RUN pnpm prune --prod + # skip pruning to preserve all workspace dependencies # ---- runtime ---- FROM node:20-alpine ENV NODE_ENV=production WORKDIR /app RUN addgroup -S app && adduser -S app -G app - USER app COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/package.json ./ COPY --from=build /app/pnpm-workspace.yaml ./ COPY --from=build /app/apps/relayerOnChainAum/dist ./apps/relayerOnChainAum/dist COPY --from=build /app/libs ./libs - CMD ["node", "apps/relayerOnChainAum/dist/main.js"] + # créer les symlinks pour les packages workspace + RUN mkdir -p node_modules/@forge && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + chown -R app:app . + USER app + CMD ["node", "apps/relayerOnChainAum/dist/apps/relayerOnChainAum/src/main.js"] \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 00000000..0e327b67 --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,82 @@ +services: + postgres: + image: postgres:15-alpine + container_name: vault_postgres + environment: + POSTGRES_DB: starknet_vault_kit + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_HOST_AUTH_METHOD: trust + ports: + - '5432:5432' + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres -d starknet_vault_kit'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + restart: unless-stopped + + api: + build: + context: .. + dockerfile: backend/Dockerfile.api + container_name: vault_api + ports: + - '3000:3000' + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - LOG_LEVEL=${LOG_LEVEL} + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + restart: unless-stopped + + indexer: + build: + context: .. + dockerfile: backend/Dockerfile.indexer + container_name: vault_indexer + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - APIBARA_TOKEN=${APIBARA_TOKEN} + - START_BLOCK=${START_BLOCK} + - LOG_LEVEL=${LOG_LEVEL} + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + + onchain-aum-provider: + build: + context: .. + dockerfile: backend/Dockerfile.relayerOnChainAum + container_name: vault_aum_provider + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - LOG_LEVEL=${LOG_LEVEL} + - ON_CHAIN_AUM_PROVIDER=${ON_CHAIN_AUM_PROVIDER} + - RELAYER_ADDRESS=${RELAYER_ADDRESS} + - RELAYER_PRIVATE_KEY=${RELAYER_PRIVATE_KEY} + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + +volumes: + pgdata: + driver: local From 43428105ecc63e20d9d715bbd1ca6fe15082e86e Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 22:24:55 +0200 Subject: [PATCH 31/54] Fix missing dependencies in Docker containers Add decimal.js and starknet symlinks to resolve runtime dependency issues in indexer and relayerOnChainAum containers. --- backend/Dockerfile.indexer | 4 ++++ backend/Dockerfile.relayerOnChainAum | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer index 65acd78d..496328e9 100644 --- a/backend/Dockerfile.indexer +++ b/backend/Dockerfile.indexer @@ -17,6 +17,9 @@ RUN addgroup -S app && adduser -S app -G app COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/package.json ./ COPY --from=build /app/pnpm-workspace.yaml ./ +# copier les package.json des apps et libs pour les deps +COPY --from=build /app/apps/indexer/package.json ./apps/indexer/ +COPY --from=build /app/libs/*/package.json ./libs/*/ COPY --from=build /app/apps/indexer/dist ./apps/indexer/dist COPY --from=build /app/libs ./libs # créer les symlinks pour les packages workspace et dependencies manquantes @@ -25,6 +28,7 @@ RUN mkdir -p node_modules/@forge node_modules/@apibara && \ ln -sf ../../libs/db node_modules/@forge/db && \ ln -sf ../../libs/logger node_modules/@forge/logger && \ ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/decimal.js@10.6.0/node_modules/decimal.js node_modules/decimal.js && \ ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ ln -sf ../.pnpm/@apibara+protocol@0.4.9/node_modules/@apibara/protocol node_modules/@apibara/protocol && \ ln -sf ../.pnpm/@apibara+starknet@0.5.0/node_modules/@apibara/starknet node_modules/@apibara/starknet && \ diff --git a/backend/Dockerfile.relayerOnChainAum b/backend/Dockerfile.relayerOnChainAum index ff1302c3..58af4d20 100644 --- a/backend/Dockerfile.relayerOnChainAum +++ b/backend/Dockerfile.relayerOnChainAum @@ -17,14 +17,19 @@ COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/package.json ./ COPY --from=build /app/pnpm-workspace.yaml ./ + # copier les package.json des apps et libs pour les deps + COPY --from=build /app/apps/relayerOnChainAum/package.json ./apps/relayerOnChainAum/ + COPY --from=build /app/libs/*/package.json ./libs/*/ COPY --from=build /app/apps/relayerOnChainAum/dist ./apps/relayerOnChainAum/dist COPY --from=build /app/libs ./libs - # créer les symlinks pour les packages workspace + # créer les symlinks pour les packages workspace et dependencies manquantes RUN mkdir -p node_modules/@forge && \ ln -sf ../../libs/config node_modules/@forge/config && \ ln -sf ../../libs/db node_modules/@forge/db && \ ln -sf ../../libs/logger node_modules/@forge/logger && \ ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/decimal.js@10.6.0/node_modules/decimal.js node_modules/decimal.js && \ + ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ chown -R app:app . USER app CMD ["node", "apps/relayerOnChainAum/dist/apps/relayerOnChainAum/src/main.js"] From 05387e233d4dd655e0931ecaaa910965bdaefae3 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 22:55:15 +0200 Subject: [PATCH 32/54] Add missing @forge/starknet dependency to indexer package.json --- backend/apps/indexer/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/apps/indexer/package.json b/backend/apps/indexer/package.json index 62fe67bc..9f651e95 100644 --- a/backend/apps/indexer/package.json +++ b/backend/apps/indexer/package.json @@ -15,6 +15,7 @@ "@forge/config": "workspace:*", "@forge/db": "workspace:*", "@forge/logger": "workspace:*", + "@forge/starknet": "workspace:*", "starknet": "^7.6.4", "@nestjs/core": "^10.0.0", "@nestjs/common": "^10.0.0" From 6c6343f41617602882ded2b6d510dd823a3f65c6 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:02:21 +0200 Subject: [PATCH 33/54] Update pnpm lockfile for indexer dependency changes --- backend/pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index b6215b70..f649d0fe 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -165,6 +165,9 @@ importers: '@forge/logger': specifier: workspace:* version: link:../../libs/logger + '@forge/starknet': + specifier: workspace:* + version: link:../../libs/starknet '@nestjs/common': specifier: ^10.0.0 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) From 30bb56821949594abf4482d4493beb1756948189 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:15:38 +0200 Subject: [PATCH 34/54] Remove unnecessary @forge/starknet dependency from indexer --- backend/Dockerfile.indexer | 1 - backend/apps/indexer/package.json | 1 - backend/pnpm-lock.yaml | 3 --- 3 files changed, 5 deletions(-) diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer index 496328e9..10d5279e 100644 --- a/backend/Dockerfile.indexer +++ b/backend/Dockerfile.indexer @@ -27,7 +27,6 @@ RUN mkdir -p node_modules/@forge node_modules/@apibara && \ ln -sf ../../libs/config node_modules/@forge/config && \ ln -sf ../../libs/db node_modules/@forge/db && \ ln -sf ../../libs/logger node_modules/@forge/logger && \ - ln -sf ../../libs/starknet node_modules/@forge/starknet && \ ln -sf .pnpm/decimal.js@10.6.0/node_modules/decimal.js node_modules/decimal.js && \ ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ ln -sf ../.pnpm/@apibara+protocol@0.4.9/node_modules/@apibara/protocol node_modules/@apibara/protocol && \ diff --git a/backend/apps/indexer/package.json b/backend/apps/indexer/package.json index 9f651e95..62fe67bc 100644 --- a/backend/apps/indexer/package.json +++ b/backend/apps/indexer/package.json @@ -15,7 +15,6 @@ "@forge/config": "workspace:*", "@forge/db": "workspace:*", "@forge/logger": "workspace:*", - "@forge/starknet": "workspace:*", "starknet": "^7.6.4", "@nestjs/core": "^10.0.0", "@nestjs/common": "^10.0.0" diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index f649d0fe..b6215b70 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -165,9 +165,6 @@ importers: '@forge/logger': specifier: workspace:* version: link:../../libs/logger - '@forge/starknet': - specifier: workspace:* - version: link:../../libs/starknet '@nestjs/common': specifier: ^10.0.0 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) From 1197088800b44dd3345cf16849162d31bbe02848 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:16:09 +0200 Subject: [PATCH 35/54] Update indexer.module.ts --- backend/apps/indexer/src/indexer.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/apps/indexer/src/indexer.module.ts b/backend/apps/indexer/src/indexer.module.ts index c7fb35aa..eecb3961 100644 --- a/backend/apps/indexer/src/indexer.module.ts +++ b/backend/apps/indexer/src/indexer.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@forge/config'; import { PrismaModule } from '@forge/db'; -import { StarknetModule } from '@forge/starknet'; import { IndexerService } from './indexer.service'; @Module({ - imports: [ConfigModule, PrismaModule, StarknetModule], + imports: [ConfigModule, PrismaModule], providers: [IndexerService], exports: [IndexerService], }) From b32e21a80040a5952ca4a2839f51e78846aba937 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:39:35 +0200 Subject: [PATCH 36/54] Remove flashloan functionality from vault allocator - Remove flashloan interface and implementation from manager - Remove flashloan decoder/sanitizer methods - Remove flashloan mock and related test scenarios - Clean up unused Vesu integration interfaces - Simplify manager constructor by removing vesu_singleton dependency This refactoring removes the complex flashloan mechanism and associated test infrastructure, streamlining the vault allocator architecture. --- .../base_decoder_and_sanitizer.cairo | 14 - .../decoders_and_sanitizers/interface.cairo | 8 - .../src/integration_interfaces/vesu.cairo | 211 +--------- packages/vault_allocator/src/lib.cairo | 1 - .../vault_allocator/src/manager/errors.cairo | 12 - .../src/manager/interface.cairo | 10 - .../vault_allocator/src/manager/manager.cairo | 111 +----- .../vault_allocator/src/mocks/flashloan.cairo | 139 ------- .../vault_allocator/src/test/creator.cairo | 15 +- .../leveraged_loop_staked_ether.cairo | 272 ------------- .../src/test/units/manager.cairo | 374 +----------------- packages/vault_allocator/src/test/utils.cairo | 33 -- 12 files changed, 9 insertions(+), 1191 deletions(-) delete mode 100644 packages/vault_allocator/src/mocks/flashloan.cairo delete mode 100644 packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo index 020d5caa..62cc34bb 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo @@ -28,20 +28,6 @@ pub mod BaseDecoderAndSanitizerComponent { serialized_struct.span() } - fn flash_loan( - self: @ComponentState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) -> Span { - let mut serialized_struct: Array = ArrayTrait::new(); - receiver.serialize(ref serialized_struct); - asset.serialize(ref serialized_struct); - is_legacy.serialize(ref serialized_struct); - serialized_struct.span() - } fn bring_liquidity(self: @ComponentState, amount: u256) -> Span { let mut serialized_struct: Array = ArrayTrait::new(); diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo index 6f175354..e5f266e1 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo @@ -7,13 +7,5 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IBaseDecoderAndSanitizer { fn approve(self: @T, spender: ContractAddress, amount: u256) -> Span; - fn flash_loan( - self: @T, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) -> Span; fn bring_liquidity(self: @T, amount: u256) -> Span; } diff --git a/packages/vault_allocator/src/integration_interfaces/vesu.cairo b/packages/vault_allocator/src/integration_interfaces/vesu.cairo index 73aaccd4..47ee1fce 100644 --- a/packages/vault_allocator/src/integration_interfaces/vesu.cairo +++ b/packages/vault_allocator/src/integration_interfaces/vesu.cairo @@ -5,206 +5,8 @@ use starknet::ContractAddress; #[starknet::interface] -pub trait ISingletonV2< - TContractState, -> { // fn creator_nonce(self: @TContractState, creator: ContractAddress) -> felt252; +pub trait ISingletonV2 { fn extension(self: @TContractState, pool_id: felt252) -> ContractAddress; - // fn asset_config_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> (AssetConfig, u256); - // fn asset_config( - // ref self: TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> (AssetConfig, u256); - // fn ltv_config( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> LTVConfig; - // fn position_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (Position, u256, u256); - // fn position( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (Position, u256, u256); - // fn check_collateralization_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (bool, u256, u256); - // fn check_collateralization( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (bool, u256, u256); - // fn rate_accumulator_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> u256; - // fn rate_accumulator(ref self: TContractState, pool_id: felt252, asset: ContractAddress) -> - // u256; - // fn utilization_unsafe(self: @TContractState, pool_id: felt252, asset: ContractAddress) -> - // u256; - // fn utilization(ref self: TContractState, pool_id: felt252, asset: ContractAddress) -> u256; - // fn delegation( - // self: @TContractState, - // pool_id: felt252, - // delegator: ContractAddress, - // delegatee: ContractAddress, - // ) -> bool; - // fn calculate_pool_id( - // self: @TContractState, caller_address: ContractAddress, nonce: felt252, - // ) -> felt252; - // fn calculate_debt( - // self: @TContractState, nominal_debt: i257, rate_accumulator: u256, asset_scale: u256, - // ) -> u256; - // fn calculate_nominal_debt( - // self: @TContractState, debt: i257, rate_accumulator: u256, asset_scale: u256, - // ) -> u256; - // fn calculate_collateral_shares_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, collateral: i257, - // ) -> u256; - // fn calculate_collateral_shares( - // ref self: TContractState, pool_id: felt252, asset: ContractAddress, collateral: i257, - // ) -> u256; - // fn calculate_collateral_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, collateral_shares: i257, - // ) -> u256; - // fn calculate_collateral( - // ref self: TContractState, pool_id: felt252, asset: ContractAddress, collateral_shares: - // i257, - // ) -> u256; - // fn deconstruct_collateral_amount_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // collateral: Amount, - // ) -> (i257, i257); - // fn deconstruct_collateral_amount( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // collateral: Amount, - // ) -> (i257, i257); - // fn deconstruct_debt_amount_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // debt: Amount, - // ) -> (i257, i257); - // fn deconstruct_debt_amount( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // debt: Amount, - // ) -> (i257, i257); - // fn context_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> Context; - // fn context( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> Context; - // fn create_pool( - // ref self: TContractState, - // asset_params: Span, - // ltv_params: Span, - // extension: ContractAddress, - // ) -> felt252; - // fn modify_position( - // ref self: TContractState, params: ModifyPositionParams, - // ) -> UpdatePositionResponse; - // fn liquidate_position( - // ref self: TContractState, params: LiquidatePositionParams, - // ) -> UpdatePositionResponse; - fn flash_loan( - ref self: TContractState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ); - // fn modify_delegation( -// ref self: TContractState, pool_id: felt252, delegatee: ContractAddress, delegation: bool, -// ); -// fn donate_to_reserve( -// ref self: TContractState, pool_id: felt252, asset: ContractAddress, amount: u256, -// ); -// fn retrieve_from_reserve( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// receiver: ContractAddress, -// amount: u256, -// ); -// fn set_asset_config(ref self: TContractState, pool_id: felt252, params: AssetParams); -// fn set_ltv_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// ltv_config: LTVConfig, -// ); -// fn set_asset_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: u256, -// ); -// fn set_extension(ref self: TContractState, pool_id: felt252, extension: ContractAddress); -// fn set_extension_whitelist( -// ref self: TContractState, extension: ContractAddress, approved: bool, -// ); -// fn claim_fee_shares(ref self: TContractState, pool_id: felt252, asset: ContractAddress); - - // fn migrate_pool( -// ref self: TContractState, -// pool_id: felt252, -// extension: ContractAddress, -// creator: ContractAddress, -// asset_configs: Span<(ContractAddress, AssetConfig)>, -// ltv_configs: Span<(ContractAddress, ContractAddress, LTVConfig)>, -// ); -// fn migrate_position( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// from: ContractAddress, -// to: ContractAddress, -// ); -// fn set_migrator(ref self: TContractState, migrator: ContractAddress); - - // fn upgrade_name(self: @TContractState) -> felt252; -// fn upgrade(ref self: TContractState, new_implementation: ClassHash); } #[starknet::interface] @@ -383,14 +185,3 @@ pub trait IDefaultExtensionPOV2< // fn upgrade_name(self: @TContractState) -> felt252; // fn upgrade(ref self: TContractState, new_implementation: ClassHash); } - -#[starknet::interface] -pub trait IFlashloanReceiver { - fn on_flash_loan( - ref self: TContractState, - sender: ContractAddress, - asset: ContractAddress, - amount: u256, - data: Span, - ); -} diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index c4c72327..0cd4f363 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -69,7 +69,6 @@ pub mod mocks { pub mod counter; pub mod erc20; pub mod erc4626; - pub mod flashloan; pub mod vault; } diff --git a/packages/vault_allocator/src/manager/errors.cairo b/packages/vault_allocator/src/manager/errors.cairo index c232c949..8eb9aa4c 100644 --- a/packages/vault_allocator/src/manager/errors.cairo +++ b/packages/vault_allocator/src/manager/errors.cairo @@ -18,16 +18,4 @@ pub mod Errors { pub fn not_vault_allocator() { panic!("Not vault allocator"); } - - pub fn flash_loan_not_executed() { - panic!("Flash loan not executed"); - } - - pub fn not_vesu_singleton() { - panic!("Not vesu singleton"); - } - - pub fn bad_flash_loan_intent_hash() { - panic!("Bad flash loan intent hash"); - } } diff --git a/packages/vault_allocator/src/manager/interface.cairo b/packages/vault_allocator/src/manager/interface.cairo index 21e59cda..e5546961 100644 --- a/packages/vault_allocator/src/manager/interface.cairo +++ b/packages/vault_allocator/src/manager/interface.cairo @@ -6,7 +6,6 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IManager { - fn vesu_singleton(self: @T) -> ContractAddress; fn vault_allocator(self: @T) -> ContractAddress; fn set_manage_root(ref self: T, target: ContractAddress, root: felt252); fn manage_root(self: @T, target: ContractAddress) -> felt252; @@ -20,14 +19,5 @@ pub trait IManager { selectors: Span, calldatas: Span>, ); - - fn flash_loan( - ref self: T, - recipient: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ); } diff --git a/packages/vault_allocator/src/manager/manager.cairo b/packages/vault_allocator/src/manager/manager.cairo index 1deeb830..c47d9d2c 100644 --- a/packages/vault_allocator/src/manager/manager.cairo +++ b/packages/vault_allocator/src/manager/manager.cairo @@ -8,10 +8,8 @@ pub mod Manager { pub const OWNER_ROLE: felt252 = selector!("OWNER_ROLE"); pub const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE"); use core::hash::HashStateTrait; - use core::num::traits::Zero; use core::pedersen::PedersenTrait; use openzeppelin::access::accesscontrol::AccessControlComponent; - use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::merkle_tree::merkle_proof; @@ -22,14 +20,9 @@ pub mod Manager { Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; - use vault_allocator::integration_interfaces::vesu::{ - IFlashloanReceiver, ISingletonV2Dispatcher, ISingletonV2DispatcherTrait, - }; + use starknet::{ContractAddress, get_caller_address}; use vault_allocator::manager::errors::Errors; - use vault_allocator::manager::interface::{ - IManager, IManagerDispatcher, IManagerDispatcherTrait, - }; + use vault_allocator::manager::interface::IManager; use vault_allocator::vault_allocator::interface::{ IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, }; @@ -50,10 +43,7 @@ pub mod Manager { #[substorage(v0)] pausable: PausableComponent::Storage, vault_allocator: IVaultAllocatorDispatcher, - vesu_singleton: ISingletonV2Dispatcher, manage_root: Map, - flash_loan_intent_hash: felt252, - performing_flash_loan: bool, } #[event] @@ -68,10 +58,7 @@ pub mod Manager { #[constructor] fn constructor( - ref self: ContractState, - owner: ContractAddress, - vault_allocator: ContractAddress, - vesu_singleton: ContractAddress, + ref self: ContractState, owner: ContractAddress, vault_allocator: ContractAddress, ) { self.access_control.initializer(); self.access_control.set_role_admin(OWNER_ROLE, OWNER_ROLE); @@ -79,7 +66,6 @@ pub mod Manager { self.access_control._grant_role(OWNER_ROLE, owner); self.access_control._grant_role(PAUSER_ROLE, owner); self.vault_allocator.write(IVaultAllocatorDispatcher { contract_address: vault_allocator }); - self.vesu_singleton.write(ISingletonV2Dispatcher { contract_address: vesu_singleton }); } @@ -101,67 +87,9 @@ pub mod Manager { } } - #[abi(embed_v0)] - impl ManagerFlashloanReceiverImpl of IFlashloanReceiver { - fn on_flash_loan( - ref self: ContractState, - sender: ContractAddress, - asset: ContractAddress, - amount: u256, - data: Span, - ) { - let vesu_singleton = self.vesu_singleton.read().contract_address; - if (get_caller_address() != vesu_singleton) { - Errors::not_vesu_singleton(); - } - let intent_hash = self._get_flash_loan_intent_hash_from_span(data); - if (intent_hash != self.flash_loan_intent_hash.read()) { - Errors::bad_flash_loan_intent_hash(); - } - self.flash_loan_intent_hash.write(0); - - let vault_allocator = self.vault_allocator.read(); - let asset_dispatcher = ERC20ABIDispatcher { contract_address: asset }; - - asset_dispatcher.transfer(vault_allocator.contract_address, amount); - - let mut data = data.clone(); - let (proofs, decoder_and_sanitizers, targets, selectors, calldatas) = Serde::< - ( - Span>, - Span, - Span, - Span, - Span>, - ), - >::deserialize(ref data) - .unwrap(); - - let this = get_contract_address(); - IManagerDispatcher { contract_address: this } - .manage_vault_with_merkle_verification( - proofs, decoder_and_sanitizers, targets, selectors, calldatas, - ); - - let mut calldata = ArrayTrait::new(); - this.serialize(ref calldata); - amount.serialize(ref calldata); - - vault_allocator - .manage( - Call { to: asset, selector: selector!("transfer"), calldata: calldata.span() }, - ); - asset_dispatcher.approve(vesu_singleton, amount); - } - } - #[abi(embed_v0)] impl ManagerImpl of IManager { - fn vesu_singleton(self: @ContractState) -> ContractAddress { - self.vesu_singleton.read().contract_address - } - fn vault_allocator(self: @ContractState) -> ContractAddress { self.vault_allocator.read().contract_address } @@ -220,26 +148,6 @@ pub mod Manager { .manage(Call { to: target, selector: selector, calldata: calldata }); } } - - fn flash_loan( - ref self: ContractState, - recipient: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) { - if (get_caller_address() != self.vault_allocator.read().contract_address) { - Errors::not_vault_allocator(); - } - self.flash_loan_intent_hash.write(self._get_flash_loan_intent_hash_from_span(data)); - self.performing_flash_loan.write(true); - self.vesu_singleton.read().flash_loan(recipient, asset, amount, is_legacy, data); - self.performing_flash_loan.write(false); - if (self.flash_loan_intent_hash.read().is_non_zero()) { - Errors::flash_loan_not_executed(); - } - } } @@ -305,18 +213,5 @@ pub mod Manager { let leaf_hash = state.finalize(); merkle_proof::verify_pedersen(proof, root, leaf_hash) } - - fn _get_flash_loan_intent_hash_from_span( - self: @ContractState, data: Span, - ) -> felt252 { - let mut serialized_struct: Array = ArrayTrait::new(); - data.serialize(ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } - state.finalize() - } } } diff --git a/packages/vault_allocator/src/mocks/flashloan.cairo b/packages/vault_allocator/src/mocks/flashloan.cairo deleted file mode 100644 index cc7ce590..00000000 --- a/packages/vault_allocator/src/mocks/flashloan.cairo +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2025 Starknet Vault Kit -// Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use starknet::{ContractAddress, get_contract_address}; - -fn transfer_asset( - asset: ContractAddress, - sender: ContractAddress, - to: ContractAddress, - amount: u256, - is_legacy: bool, -) { - let erc20 = ERC20ABIDispatcher { contract_address: asset }; - if sender == get_contract_address() { - assert!(erc20.transfer(to, amount), "transfer-failed"); - } else if is_legacy { - assert!(erc20.transferFrom(sender, to, amount), "transferFrom-failed"); - } else { - assert!(erc20.transfer_from(sender, to, amount), "transfer-from-failed"); - } -} - -#[starknet::contract] -pub mod FlashLoanSingletonMock { - use core::num::traits::Zero; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, get_caller_address}; - use vault_allocator::integration_interfaces::vesu::{ - IFlashloanReceiverDispatcher, IFlashloanReceiverDispatcherTrait, - }; - use super::{get_contract_address, transfer_asset}; - - - #[storage] - struct Storage { - do_nothing: bool, - i_did_something: bool, - do_wrong_callback: bool, - } - - #[constructor] - fn constructor(ref self: ContractState) {} - - #[abi(embed_v0)] - impl FlashLoanSingletonMockImpl of super::IFlashLoanSingletonMock { - fn do_wrong_callback(self: @ContractState) -> bool { - self.do_wrong_callback.read() - } - - fn set_do_wrong_callback(ref self: ContractState, do_wrong_callback: bool) { - self.do_wrong_callback.write(do_wrong_callback); - } - - fn flash_loan( - ref self: ContractState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) { - if (!self.do_nothing.read()) { - if (!self.do_wrong_callback.read()) { - transfer_asset(asset, get_contract_address(), receiver, amount, is_legacy); - IFlashloanReceiverDispatcher { contract_address: receiver } - .on_flash_loan(get_caller_address(), asset, amount, data); - transfer_asset(asset, receiver, get_contract_address(), amount, is_legacy); - } else { - let mut flash_loan_data_proofs: Array> = ArrayTrait::new(); - flash_loan_data_proofs.append(array![Zero::zero()].span()); - let mut flash_loan_data_decoder_and_sanitizer: Array = - ArrayTrait::new(); - flash_loan_data_decoder_and_sanitizer.append(Zero::zero()); - let mut flash_loan_data_target: Array = ArrayTrait::new(); - flash_loan_data_target.append(Zero::zero()); - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - flash_loan_data_selector.append(Zero::zero()); - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - flash_loan_data_calldata.append(array![Zero::zero()].span()); - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_data_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - transfer_asset(asset, get_contract_address(), receiver, amount, is_legacy); - IFlashloanReceiverDispatcher { contract_address: receiver } - .on_flash_loan( - get_caller_address(), asset, amount, serialized_flash_loan_data.span(), - ); - transfer_asset(asset, receiver, get_contract_address(), amount, is_legacy); - } - } - } - - fn approve(ref self: ContractState, token: ContractAddress, amount: u256) { - transfer_asset(token, get_caller_address(), get_contract_address(), amount, true); - transfer_asset(token, get_contract_address(), get_caller_address(), amount, true); - self.i_did_something.write(true) - } - - fn i_did_something(self: @ContractState) -> bool { - self.i_did_something.read() - } - - fn do_nothing(self: @ContractState) -> bool { - self.do_nothing.read() - } - - fn set_do_nothing(ref self: ContractState, do_nothing: bool) { - self.do_nothing.write(do_nothing); - } - } -} - -#[starknet::interface] -pub trait IFlashLoanSingletonMock { - fn flash_loan( - ref self: TContractState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ); - - fn approve(ref self: TContractState, token: ContractAddress, amount: u256); - - fn i_did_something(self: @TContractState) -> bool; - fn do_nothing(self: @TContractState) -> bool; - fn do_wrong_callback(self: @TContractState) -> bool; - fn set_do_nothing(ref self: TContractState, do_nothing: bool); - fn set_do_wrong_callback(ref self: TContractState, do_wrong_callback: bool); -} diff --git a/packages/vault_allocator/src/test/creator.cairo b/packages/vault_allocator/src/test/creator.cairo index b40b4796..ce31509d 100644 --- a/packages/vault_allocator/src/test/creator.cairo +++ b/packages/vault_allocator/src/test/creator.cairo @@ -5,8 +5,8 @@ use alexandria_math::i257::I257Impl; use starknet::ContractAddress; use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, wstETH}; use vault_allocator::test::utils::{ - ManageLeaf, _add_avnu_leafs, _add_vesu_flash_loan_leafs, _add_vesu_leafs, - _pad_leafs_to_power_of_two, generate_merkle_tree, get_leaf_hash, + ManageLeaf, _add_avnu_leafs, _add_vesu_leafs, _pad_leafs_to_power_of_two, generate_merkle_tree, + get_leaf_hash, }; use super::utils::DUMMY_ADDRESS; @@ -34,17 +34,6 @@ fn test_creator() { let router = DUMMY_ADDRESS(); // INTEGRATIONS - let flash_loan_asset = wstETH(); - let is_legacy = false; - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator, - decoder_and_sanitizer, - manager, - flash_loan_asset, - is_legacy, - ); let pool_id = GENESIS_POOL_ID; let mut assets_to_supply = ArrayTrait::new(); diff --git a/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo b/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo deleted file mode 100644 index 5fef2aa9..00000000 --- a/packages/vault_allocator/src/test/scenarios/leveraged_loop_staked_ether.cairo +++ /dev/null @@ -1,272 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2025 Starknet Vault Kit -// Licensed under the MIT License. See LICENSE file for details. -use alexandria_math::i257::{I257Impl, I257Trait}; -use core::num::traits::Zero; -use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use snforge_std::{map_entry_address, store}; -use starknet::ContractAddress; -use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - Amount, AmountDenomination, AmountType, Route, -}; -use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, VESU_SINGLETON, wstETH}; -use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _add_vesu_flash_loan_leafs, - _add_vesu_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, - deploy_avnu_middleware, deploy_manager, deploy_price_router, - deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, generate_merkle_tree, - initialize_price_router, -}; -use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; - -#[fork("SLLSE")] -#[test] -fn test_leveraged_loop_staked_ether() { - let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); - let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); - let price_router = deploy_price_router(); - initialize_price_router(price_router); - let avnu_middleware = deploy_avnu_middleware(price_router); - - let mut leafs: Array = ArrayTrait::new(); - let mut leaf_index: u256 = 0; - - let collateral_asset = wstETH(); - - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - manager.contract_address, - collateral_asset, - false, - ); - - _add_vesu_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - GENESIS_POOL_ID, - array![wstETH()].span(), - array![array![ETH()].span()].span(), - ); - - _add_avnu_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - avnu_middleware, - array![(ETH(), wstETH())], - ); - - _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); - - let tree = generate_merkle_tree(leafs.span()); - let root = *tree.at(tree.len() - 1).at(0); - cheat_caller_address_once(vault_allocator.contract_address, OWNER()); - vault_allocator.set_manager(manager.contract_address); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(STRATEGIST(), root); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(manager.contract_address, root); - - // config - let collateral_asset = wstETH(); - let initial_collateral_balance: u256 = WAD; - let collateral_to_flash_loan: u256 = WAD; - let debt_asset = ETH(); - let allowed_slippage: u256 = 10; // 0.1% - let required_debt_amount_to_refund_flash_loan = 1208100829164930048 - + 1208100829164930048 * allowed_slippage / 10000; - - let mut cheat_calldata = ArrayTrait::new(); - initial_collateral_balance.serialize(ref cheat_calldata); - store( - collateral_asset, - map_entry_address( - selector!("ERC20_balances"), array![vault_allocator.contract_address.into()].span(), - ), - cheat_calldata.span(), - ); - - let underlying_disp = ERC20ABIDispatcher { contract_address: collateral_asset }; - assert( - underlying_disp.balance_of(vault_allocator.contract_address) == initial_collateral_balance, - 'wsteth balance is not correct', - ); - - let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - - let mut array_of_targets = ArrayTrait::new(); - array_of_targets.append(manager.contract_address); - - let mut array_of_selectors = ArrayTrait::new(); - array_of_selectors.append(selector!("flash_loan")); - - let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldatas_flash_loan = ArrayTrait::new(); - manager.contract_address.serialize(ref array_of_calldatas_flash_loan); - collateral_asset.serialize(ref array_of_calldatas_flash_loan); - collateral_to_flash_loan.serialize(ref array_of_calldatas_flash_loan); - false.serialize(ref array_of_calldatas_flash_loan); - - let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - - let mut flash_loan_data_target: Array = ArrayTrait::new(); - flash_loan_data_target.append(collateral_asset); - flash_loan_data_target.append(VESU_SINGLETON()); - flash_loan_data_target.append(debt_asset); - flash_loan_data_target.append(avnu_middleware); - - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - flash_loan_data_selector.append(selector!("approve")); - flash_loan_data_selector.append(selector!("modify_position")); - flash_loan_data_selector.append(selector!("approve")); - flash_loan_data_selector.append(selector!("multi_route_swap")); - - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - - // approve wsteth initial collateral + flash loan amount - let mut flash_loan_data_calldata_approve = ArrayTrait::new(); - VESU_SINGLETON().serialize(ref flash_loan_data_calldata_approve); - (initial_collateral_balance + collateral_to_flash_loan) - .serialize(ref flash_loan_data_calldata_approve); - flash_loan_data_calldata.append(flash_loan_data_calldata_approve.span()); - - // modify position supplying wsteth initial collateral + flash loan amount and borrowing eth - // equivalent to refund flashloan - let mut flash_loan_data_calldata_modify_position = ArrayTrait::new(); - GENESIS_POOL_ID.serialize(ref flash_loan_data_calldata_modify_position); - wstETH().serialize(ref flash_loan_data_calldata_modify_position); - ETH().serialize(ref flash_loan_data_calldata_modify_position); - vault_allocator.contract_address.serialize(ref flash_loan_data_calldata_modify_position); - - let value_for_collateral_modify_position = I257Trait::new( - initial_collateral_balance + collateral_to_flash_loan, false, - ); - let collateral_modify_position: Amount = Amount { - amount_type: AmountType::Delta, - denomination: AmountDenomination::Assets, - value: value_for_collateral_modify_position, - }; - collateral_modify_position.serialize(ref flash_loan_data_calldata_modify_position); - - let value_for_debt_modify_position = I257Trait::new( - required_debt_amount_to_refund_flash_loan, false, - ); - let debt_modify_position: Amount = Amount { - amount_type: AmountType::Delta, - denomination: AmountDenomination::Assets, - value: value_for_debt_modify_position, - }; - debt_modify_position.serialize(ref flash_loan_data_calldata_modify_position); - - let data: Span = array![].span(); - data.serialize(ref flash_loan_data_calldata_modify_position); - - flash_loan_data_calldata.append(flash_loan_data_calldata_modify_position.span()); - - // approve debt asset to avnu middleware - let mut flash_loan_data_calldata_approve_debt_asset: Array = ArrayTrait::new(); - avnu_middleware.serialize(ref flash_loan_data_calldata_approve_debt_asset); - required_debt_amount_to_refund_flash_loan - .serialize(ref flash_loan_data_calldata_approve_debt_asset); - flash_loan_data_calldata.append(flash_loan_data_calldata_approve_debt_asset.span()); - - // multi route swap: sell debt asset to avnu middleware for collateral to refund flashloan - let mut array_of_calldata_multi_route_swap: Array = ArrayTrait::new(); - debt_asset.serialize(ref array_of_calldata_multi_route_swap); - required_debt_amount_to_refund_flash_loan.serialize(ref array_of_calldata_multi_route_swap); - collateral_asset.serialize(ref array_of_calldata_multi_route_swap); - // buy token amount is 0 because we are selling - let buy_token_amount: u256 = Zero::zero(); - buy_token_amount.serialize(ref array_of_calldata_multi_route_swap); - // buy_token_min_amount is set to 0 because we are protected by price router whatever - let buy_token_min_amount: u256 = Zero::zero(); - buy_token_min_amount.serialize(ref array_of_calldata_multi_route_swap); - vault_allocator.contract_address.serialize(ref array_of_calldata_multi_route_swap); - let integrator_fee_amount_bps: u128 = Zero::zero(); - integrator_fee_amount_bps.serialize(ref array_of_calldata_multi_route_swap); - let integrator_fee_recipient: ContractAddress = Zero::zero(); - integrator_fee_recipient.serialize(ref array_of_calldata_multi_route_swap); - - let mut routes: Array = ArrayTrait::new(); - // hardcode route to ekubo wsteth/eth - let mut additional_swap_params: Array = ArrayTrait::new(); - collateral_asset.serialize(ref additional_swap_params); - debt_asset.serialize(ref additional_swap_params); - - let fee: u128 = 0x68db8bac710cb4000000000000000; - fee.serialize(ref additional_swap_params); - let tick_spacing: u128 = 0xc8; - tick_spacing.serialize(ref additional_swap_params); - let extension: ContractAddress = Zero::zero(); - extension.serialize(ref additional_swap_params); - let sqrt_ratio_distance: felt252 = 0x290d5f61e20000000000000000000; - sqrt_ratio_distance.serialize(ref additional_swap_params); - - routes - .append( - Route { - sell_token: debt_asset, - buy_token: collateral_asset, - exchange_address: 0x5dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b - .try_into() - .unwrap(), // ekubo - percent: 1000000000000, - additional_swap_params, - }, - ); - - routes.serialize(ref array_of_calldata_multi_route_swap); - - flash_loan_data_calldata.append(array_of_calldata_multi_route_swap.span()); - - let mut flash_loan_manager_leafs: Array = ArrayTrait::new(); - flash_loan_manager_leafs.append(leafs.at(6).clone()); - flash_loan_manager_leafs.append(leafs.at(7).clone()); - flash_loan_manager_leafs.append(leafs.at(8).clone()); - flash_loan_manager_leafs.append(leafs.at(9).clone()); - - let mut flash_loan_proofs = _get_proofs_using_tree(flash_loan_manager_leafs, tree.clone()); - - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); - array_of_calldatas.append(array_of_calldatas_flash_loan.span()); - - let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - - let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); - - cheat_caller_address_once(manager.contract_address, STRATEGIST()); - manager - .manage_vault_with_merkle_verification( - proofs.span(), - array_of_decoders_and_sanitizers.span(), - array_of_targets.span(), - array_of_selectors.span(), - array_of_calldatas.span(), - ); -} diff --git a/packages/vault_allocator/src/test/units/manager.cairo b/packages/vault_allocator/src/test/units/manager.cairo index 3df3f8ee..99bcc2ab 100644 --- a/packages/vault_allocator/src/test/units/manager.cairo +++ b/packages/vault_allocator/src/test/units/manager.cairo @@ -9,21 +9,14 @@ use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrai use openzeppelin::interfaces::security::pausable::{IPausableDispatcher, IPausableDispatcherTrait}; use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use starknet::ContractAddress; -use vault_allocator::integration_interfaces::vesu::{ - IFlashloanReceiverDispatcher, IFlashloanReceiverDispatcherTrait, -}; use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; use vault_allocator::manager::manager::Manager::{OWNER_ROLE, PAUSER_ROLE}; use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; -use vault_allocator::mocks::flashloan::{ - IFlashLoanSingletonMockDispatcher, IFlashLoanSingletonMockDispatcherTrait, -}; use vault_allocator::test::register::VESU_SINGLETON; use vault_allocator::test::utils::{ - DUMMY_ADDRESS, ManageLeaf, OWNER, STRATEGIST, WAD, _add_erc4626_leafs, - _add_vesu_flash_loan_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, - cheat_caller_address_once, deploy_counter, deploy_erc20_mock, deploy_erc4626_mock, - deploy_flashloan_mock, deploy_manager, deploy_simple_decoder_and_sanitizer, + DUMMY_ADDRESS, ManageLeaf, OWNER, STRATEGIST, WAD, _add_erc4626_leafs, _get_proofs_using_tree, + _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_counter, deploy_erc20_mock, + deploy_erc4626_mock, deploy_manager, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, generate_merkle_tree, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; @@ -351,364 +344,3 @@ fn test_manage_vault_with_merkle_verification_valid_proof() { assert(underlying_balance == WAD * 9, 'Balance wrong'); } -#[test] -#[should_panic(expected: "Not vault allocator")] -fn test_flash_loan_not_vault_allocator() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let manager = deploy_manager(vault_allocator, flashloan_mock); - manager - .flash_loan( - manager.contract_address, manager.contract_address, WAD, false, array!['0xdead'].span(), - ); -} - -#[test] -#[should_panic(expected: "Not vesu singleton")] -fn test_on_flash_loan_not_vesu_singleton() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let flash_loan_recipient_dispatcher = IFlashloanReceiverDispatcher { - contract_address: manager.contract_address, - }; - flash_loan_recipient_dispatcher - .on_flash_loan(DUMMY_ADDRESS(), DUMMY_ADDRESS(), WAD, array!['0xdead'].span()); -} - -#[test] -#[should_panic(expected: "Flash loan not executed")] -fn test_flash_loan_not_executed() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let underlying = deploy_erc20_mock(); - let underlying_disp = ERC20ABIDispatcher { contract_address: underlying }; - cheat_caller_address_once(underlying, OWNER()); - underlying_disp.transfer(flashloan_mock, WAD * 10); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); - - let mut leafs: Array = ArrayTrait::new(); - let mut leaf_index: u256 = 0; - - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - manager.contract_address, - underlying, - false, - ); - - _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); - - let tree = generate_merkle_tree(leafs.span()); - - let root = *tree.at(tree.len() - 1).at(0); - cheat_caller_address_once(vault_allocator.contract_address, OWNER()); - vault_allocator.set_manager(manager.contract_address); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(STRATEGIST(), root); - - // Since the manager calls to itself to fulfill the flashloan, we need to set its root. - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(manager.contract_address, root); - - let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - - let mut array_of_targets = ArrayTrait::new(); - array_of_targets.append(manager.contract_address); - - let mut array_of_selectors = ArrayTrait::new(); - array_of_selectors.append(selector!("flash_loan")); - - let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldatas_flash_loan = ArrayTrait::new(); - manager.contract_address.serialize(ref array_of_calldatas_flash_loan); - underlying.serialize(ref array_of_calldatas_flash_loan); - WAD.serialize(ref array_of_calldatas_flash_loan); - false.serialize(ref array_of_calldatas_flash_loan); - - let mut flash_loan_data_proofs: Array> = ArrayTrait::new(); - let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); - let mut flash_loan_data_target: Array = ArrayTrait::new(); - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_data_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); - array_of_calldatas.append(array_of_calldatas_flash_loan.span()); - - let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - - let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); - - IFlashLoanSingletonMockDispatcher { contract_address: flashloan_mock }.set_do_nothing(true); - - cheat_caller_address_once(manager.contract_address, STRATEGIST()); - manager - .manage_vault_with_merkle_verification( - proofs.span(), - array_of_decoders_and_sanitizers.span(), - array_of_targets.span(), - array_of_selectors.span(), - array_of_calldatas.span(), - ); -} - -#[test] -#[should_panic(expected: "Bad flash loan intent hash")] -fn test_flash_loan_bad_flash_loan_intent_hash() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let underlying = deploy_erc20_mock(); - let underlying_disp = ERC20ABIDispatcher { contract_address: underlying }; - cheat_caller_address_once(underlying, OWNER()); - underlying_disp.transfer(flashloan_mock, WAD * 10); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); - - let mut leafs: Array = ArrayTrait::new(); - let mut leaf_index: u256 = 0; - - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - manager.contract_address, - underlying, - false, - ); - - _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); - - let tree = generate_merkle_tree(leafs.span()); - - let root = *tree.at(tree.len() - 1).at(0); - cheat_caller_address_once(vault_allocator.contract_address, OWNER()); - vault_allocator.set_manager(manager.contract_address); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(STRATEGIST(), root); - - // Since the manager calls to itself to fulfill the flashloan, we need to set its root. - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(manager.contract_address, root); - - let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - - let mut array_of_targets = ArrayTrait::new(); - array_of_targets.append(manager.contract_address); - - let mut array_of_selectors = ArrayTrait::new(); - array_of_selectors.append(selector!("flash_loan")); - - let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldatas_flash_loan = ArrayTrait::new(); - manager.contract_address.serialize(ref array_of_calldatas_flash_loan); - underlying.serialize(ref array_of_calldatas_flash_loan); - WAD.serialize(ref array_of_calldatas_flash_loan); - false.serialize(ref array_of_calldatas_flash_loan); - - let mut flash_loan_data_proofs: Array> = ArrayTrait::new(); - let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); - let mut flash_loan_data_target: Array = ArrayTrait::new(); - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_data_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); - array_of_calldatas.append(array_of_calldatas_flash_loan.span()); - - let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - - let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); - - IFlashLoanSingletonMockDispatcher { contract_address: flashloan_mock } - .set_do_wrong_callback(true); - - cheat_caller_address_once(manager.contract_address, STRATEGIST()); - manager - .manage_vault_with_merkle_verification( - proofs.span(), - array_of_decoders_and_sanitizers.span(), - array_of_targets.span(), - array_of_selectors.span(), - array_of_calldatas.span(), - ); -} - -#[test] -fn test_flash_loan_bad_flash_loan() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let underlying = deploy_erc20_mock(); - let underlying_disp = ERC20ABIDispatcher { contract_address: underlying }; - cheat_caller_address_once(underlying, OWNER()); - underlying_disp.transfer(flashloan_mock, WAD * 10); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); - - let mut leafs: Array = ArrayTrait::new(); - let mut leaf_index: u256 = 0; - - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - manager.contract_address, - underlying, - false, - ); - - let amount_flash_loan = WAD; - - // approve the mock flashloan token to spend underlying - - let mut argument_addresses_approve = ArrayTrait::new(); - flashloan_mock.serialize(ref argument_addresses_approve); - leafs - .append( - ManageLeaf { - decoder_and_sanitizer: simple_decoder_and_sanitizer, - target: underlying, - selector: selector!("approve"), - argument_addresses: argument_addresses_approve.span(), - description: "", - }, - ); - - leaf_index += 1; - - // do something function called approve from the mock flashloan - let mut argument_addresses_fake_approve_func = ArrayTrait::new(); - underlying.serialize(ref argument_addresses_fake_approve_func); - leafs - .append( - ManageLeaf { - decoder_and_sanitizer: simple_decoder_and_sanitizer, - target: flashloan_mock, - selector: selector!("approve"), - argument_addresses: argument_addresses_fake_approve_func.span(), - description: "Approve", - }, - ); - leaf_index += 1; - - _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); - - let tree = generate_merkle_tree(leafs.span()); - - let root = *tree.at(tree.len() - 1).at(0); - cheat_caller_address_once(vault_allocator.contract_address, OWNER()); - vault_allocator.set_manager(manager.contract_address); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(STRATEGIST(), root); - - // Since the manager calls to itself to fulfill the flashloan, we need to set its root. - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(manager.contract_address, root); - - let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - - let mut array_of_targets = ArrayTrait::new(); - array_of_targets.append(manager.contract_address); - - let mut array_of_selectors = ArrayTrait::new(); - array_of_selectors.append(selector!("flash_loan")); - - let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldatas_flash_loan = ArrayTrait::new(); - manager.contract_address.serialize(ref array_of_calldatas_flash_loan); - underlying.serialize(ref array_of_calldatas_flash_loan); - WAD.serialize(ref array_of_calldatas_flash_loan); - false.serialize(ref array_of_calldatas_flash_loan); - - /// construct the flash loan data to trigger do_something from the flashloan mock - let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - - let mut flash_loan_data_target: Array = ArrayTrait::new(); - flash_loan_data_target.append(underlying); - flash_loan_data_target.append(flashloan_mock); - - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - flash_loan_data_selector.append(selector!("approve")); - flash_loan_data_selector.append(selector!("approve")); - - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - let mut flash_loan_data_calldata_approve = ArrayTrait::new(); - flashloan_mock.serialize(ref flash_loan_data_calldata_approve); - amount_flash_loan.serialize(ref flash_loan_data_calldata_approve); - - let mut flash_loan_data_calldata_approve_fake = ArrayTrait::new(); - underlying.serialize(ref flash_loan_data_calldata_approve_fake); - amount_flash_loan.serialize(ref flash_loan_data_calldata_approve_fake); - flash_loan_data_calldata.append(flash_loan_data_calldata_approve.span()); - flash_loan_data_calldata.append(flash_loan_data_calldata_approve_fake.span()); - - let mut flash_loan_manager_leafs: Array = ArrayTrait::new(); - flash_loan_manager_leafs.append(leafs.at(1).clone()); - flash_loan_manager_leafs.append(leafs.at(2).clone()); - - let mut flash_loan_proofs = _get_proofs_using_tree(flash_loan_manager_leafs, tree.clone()); - - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); - array_of_calldatas.append(array_of_calldatas_flash_loan.span()); - - let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - - let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); - - cheat_caller_address_once(manager.contract_address, STRATEGIST()); - manager - .manage_vault_with_merkle_verification( - proofs.span(), - array_of_decoders_and_sanitizers.span(), - array_of_targets.span(), - array_of_selectors.span(), - array_of_calldatas.span(), - ); - assert( - IFlashLoanSingletonMockDispatcher { contract_address: flashloan_mock }.i_did_something(), - 'i_did_something is not true', - ); -} diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index 6d650813..b6aa6ee7 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -77,12 +77,6 @@ pub fn deploy_counter() -> (ICounterDispatcher, ClassHash) { (ICounterDispatcher { contract_address: counter_address }, *counter.class_hash) } -pub fn deploy_flashloan_mock() -> ContractAddress { - let flashloan = declare("FlashLoanSingletonMock").unwrap().contract_class(); - let mut calldata = ArrayTrait::new(); - let (flashloan_address, _) = flashloan.deploy(@calldata).unwrap(); - flashloan_address -} pub fn deploy_erc4626_mock(underlying: ContractAddress) -> ContractAddress { let erc4626 = declare("Erc4626Mock").unwrap().contract_class(); @@ -589,33 +583,6 @@ pub fn _add_vesu_leafs( } -pub fn _add_vesu_flash_loan_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - manager: ContractAddress, - asset: ContractAddress, - is_legacy: bool, -) { - let mut argument_addresses = ArrayTrait::new(); - manager.serialize(ref argument_addresses); - asset.serialize(ref argument_addresses); - is_legacy.serialize(ref argument_addresses); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: manager, - selector: selector!("flash_loan"), - argument_addresses: argument_addresses.span(), - description: "Flash loan" + " " + get_symbol(asset), - }, - ); - leaf_index += 1; -} - // ========================================= AVNU ========================================= pub fn _add_avnu_leafs( ref leafs: Array, From 2b3ddc8602f4fe51baef767e3cc2d05f6a898dd2 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:51:58 +0200 Subject: [PATCH 37/54] Remove Vesu singleton dependency and update vault event structure - Remove vesu_singleton parameter from manager constructor and tests - Add epoch field to BringLiquidity event in vault - Re-enable commented test modules in vault_allocator lib - Clean up unused imports and variables across test files --- packages/vault/src/vault/vault.cairo | 12 +++++-- packages/vault_allocator/src/lib.cairo | 19 +++++------ .../src/test/integrations/avnu.cairo | 4 +-- .../integrations/vault_bring_liquidity.cairo | 5 ++- .../src/test/integrations/vesu_v1.cairo | 9 ++--- .../test/scenarios/stable_carry_loop.cairo | 2 +- .../src/test/units/manager.cairo | 33 +++++++++---------- packages/vault_allocator/src/test/utils.cairo | 5 +-- 8 files changed, 42 insertions(+), 47 deletions(-) diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index e6e3f6ac..7a98e6e2 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -201,7 +201,8 @@ pub mod Vault { pub caller: ContractAddress, // Address that initiated the liquidity transfer pub amount: u256, // Amount of assets brought back to vault pub new_buffer: u256, // New buffer amount after the transfer - pub new_aum: u256 // New AUM amount after the transfer + pub new_aum: u256, // New AUM amount after the transfer + pub epoch: u256 // Epoch when the liquidity was brought back } /// Initialize the vault with configuration parameters @@ -792,8 +793,13 @@ pub mod Vault { let new_aum = self.aum.read() - amount; // Calculate new AUM self.buffer.write(new_buffer); // Increase buffer self.aum.write(new_aum); // Decrease deployed AUM - - self.emit(BringLiquidity { caller, amount, new_buffer, new_aum }); + + self + .emit( + BringLiquidity { + caller, amount, new_buffer, new_aum, epoch: self.epoch.read(), + }, + ); } // --- State Getter Functions --- diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 0cd4f363..7b3c83b2 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -77,18 +77,17 @@ pub mod test { // pub mod creator; pub mod register; pub mod utils; - // pub mod units { - // pub mod manager; - // pub mod vault_allocator; - // } + pub mod units { + pub mod manager; + pub mod vault_allocator; + } pub mod integrations { - // pub mod avnu; + pub mod avnu; pub mod vault_bring_liquidity; - // pub mod vesu_v1; + pub mod vesu_v1; + } + pub mod scenarios { + pub mod stable_carry_loop; } - // pub mod scenarios { -// pub mod leveraged_loop_staked_ether; -// pub mod stable_carry_loop; -// } } diff --git a/packages/vault_allocator/src/test/integrations/avnu.cairo b/packages/vault_allocator/src/test/integrations/avnu.cairo index 4e8b69a0..260a4709 100644 --- a/packages/vault_allocator/src/test/integrations/avnu.cairo +++ b/packages/vault_allocator/src/test/integrations/avnu.cairo @@ -12,7 +12,7 @@ use vault_allocator::manager::interface::IManagerDispatcherTrait; use vault_allocator::middlewares::avnu_middleware::interface::{ IAvnuMiddlewareDispatcher, IAvnuMiddlewareDispatcherTrait, }; -use vault_allocator::test::register::{ETH, VESU_SINGLETON, wstETH}; +use vault_allocator::test::register::{ETH, wstETH}; use vault_allocator::test::utils::{ ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, @@ -25,7 +25,7 @@ use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_manage_vault_with_merkle_verification_avnu() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let price_router = deploy_price_router(); initialize_price_router(price_router); diff --git a/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo index 2d652ffd..f29436d7 100644 --- a/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo +++ b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo @@ -3,11 +3,10 @@ // Licensed under the MIT License. See LICENSE file for details. use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; +use openzeppelin::interfaces::erc4626::IERC4626Dispatcher; use snforge_std::{map_entry_address, store}; use vault_allocator::manager::interface::IManagerDispatcherTrait; use vault_allocator::mocks::vault::MockVault::MockVaultTraitDispatcherTrait; -use vault_allocator::test::register::VESU_SINGLETON; use vault_allocator::test::utils::{ ManageLeaf, OWNER, STRATEGIST, WAD, _add_vault_allocator_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_erc20_mock, deploy_manager, @@ -19,7 +18,7 @@ use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_manage_vault_with_merkle_verification_bring_liquidity() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let underlying_token = deploy_erc20_mock(); diff --git a/packages/vault_allocator/src/test/integrations/vesu_v1.cairo b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo index b58c05cf..de7886fd 100644 --- a/packages/vault_allocator/src/test/integrations/vesu_v1.cairo +++ b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo @@ -26,7 +26,7 @@ use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_manage_vault_with_merkle_verification_earn_mode() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let mut leafs: Array = ArrayTrait::new(); @@ -277,7 +277,7 @@ fn test_manage_vault_with_merkle_verification_earn_mode() { #[test] fn test_manage_vault_with_merkle_verification_debt_mode() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let mut leafs: Array = ArrayTrait::new(); @@ -322,10 +322,7 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { // first scenario is depositing wsteth to vesu genesis pool, transfer the position and borrow // ETH let deposit_amount: u256 = WAD; - let extension = ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() } - .extension(GENESIS_POOL_ID); - let v_token = IDefaultExtensionPOV2Dispatcher { contract_address: extension } - .v_token_for_collateral_asset(GENESIS_POOL_ID, wstETH()); + let debt_amount: u256 = WAD / 40; // 2.5% of the deposit let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); diff --git a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo index 89924332..41a4f7f4 100644 --- a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo +++ b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo @@ -30,7 +30,7 @@ use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_stable_carry_loop() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let price_router = deploy_price_router(); initialize_price_router(price_router); diff --git a/packages/vault_allocator/src/test/units/manager.cairo b/packages/vault_allocator/src/test/units/manager.cairo index 99bcc2ab..bdce5849 100644 --- a/packages/vault_allocator/src/test/units/manager.cairo +++ b/packages/vault_allocator/src/test/units/manager.cairo @@ -8,11 +8,9 @@ use openzeppelin::interfaces::accesscontrol::{ use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use openzeppelin::interfaces::security::pausable::{IPausableDispatcher, IPausableDispatcherTrait}; use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; -use starknet::ContractAddress; use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; use vault_allocator::manager::manager::Manager::{OWNER_ROLE, PAUSER_ROLE}; use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; -use vault_allocator::test::register::VESU_SINGLETON; use vault_allocator::test::utils::{ DUMMY_ADDRESS, ManageLeaf, OWNER, STRATEGIST, WAD, _add_erc4626_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_counter, deploy_erc20_mock, @@ -24,7 +22,7 @@ use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_constructor() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let access_control_dispatcher = IAccessControlDispatcher { contract_address: manager.contract_address, }; @@ -37,7 +35,6 @@ fn test_constructor() { let has_role = access_control_dispatcher.has_role(PAUSER_ROLE, OWNER()); assert(has_role, 'Pauser is not set correctly'); - assert(manager.vesu_singleton() == VESU_SINGLETON(), ' singleton is not set correctly'); assert( manager.vault_allocator() == vault_allocator.contract_address, 'allocator is not set correctly', @@ -48,7 +45,7 @@ fn test_constructor() { #[should_panic(expected: ('Caller is missing role',))] fn test_upgrade_not_owner() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let (_, counter_class_hash) = deploy_counter(); IUpgradeableDispatcher { contract_address: manager.contract_address } .upgrade(counter_class_hash); @@ -57,7 +54,7 @@ fn test_upgrade_not_owner() { #[test] fn test_upgrade() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let (_, counter_class_hash) = deploy_counter(); cheat_caller_address_once(manager.contract_address, OWNER()); IUpgradeableDispatcher { contract_address: manager.contract_address } @@ -68,7 +65,7 @@ fn test_upgrade() { #[test] fn test_set_manage_root() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let target = 0x123.try_into().unwrap(); let root = 0x456; @@ -84,7 +81,7 @@ fn test_set_manage_root() { #[should_panic(expected: ('Caller is missing role',))] fn test_set_manage_root_not_owner() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let target = 0x123.try_into().unwrap(); let root = 0x456; @@ -95,7 +92,7 @@ fn test_set_manage_root_not_owner() { #[test] fn test_pause() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let manager_dispatcher = IManagerDispatcher { contract_address: manager.contract_address }; let pausable_dispatcher = IPausableDispatcher { contract_address: manager.contract_address }; @@ -111,14 +108,14 @@ fn test_pause() { #[should_panic(expected: ('Caller is missing role',))] fn test_pause_not_pauser() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager.pause(); } #[test] fn test_unpause() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let manager_dispatcher = IManagerDispatcher { contract_address: manager.contract_address }; let pausable_dispatcher = IPausableDispatcher { contract_address: manager.contract_address }; @@ -136,7 +133,7 @@ fn test_unpause() { #[should_panic(expected: ('Caller is missing role',))] fn test_unpause_not_owner() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); cheat_caller_address_once(manager.contract_address, OWNER()); manager.pause(); manager.unpause(); @@ -146,7 +143,7 @@ fn test_unpause_not_owner() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_1() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( array![array![].span()].span(), @@ -161,7 +158,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_1() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_2() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( array![array![].span()].span(), @@ -176,7 +173,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_2() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_3() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( @@ -192,7 +189,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_3() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_4() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( @@ -208,7 +205,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_4() { #[should_panic(expected: "Invalid manage proof")] fn test_manage_vault_with_merkle_verification_invalid_proof() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let underlying = deploy_erc20_mock(); let erc4626 = deploy_erc4626_mock(underlying); @@ -265,7 +262,7 @@ fn test_manage_vault_with_merkle_verification_invalid_proof() { #[test] fn test_manage_vault_with_merkle_verification_valid_proof() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let underlying = deploy_erc20_mock(); let erc4626 = deploy_erc4626_mock(underlying); diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index b6aa6ee7..dbcb7be7 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -56,14 +56,11 @@ pub fn deploy_vault_allocator() -> IVaultAllocatorDispatcher { IVaultAllocatorDispatcher { contract_address: vault_allocator_address } } -pub fn deploy_manager( - vault_allocator: IVaultAllocatorDispatcher, vesu_singleton: ContractAddress, -) -> IManagerDispatcher { +pub fn deploy_manager(vault_allocator: IVaultAllocatorDispatcher) -> IManagerDispatcher { let manager = declare("Manager").unwrap().contract_class(); let mut calldata = ArrayTrait::new(); OWNER().serialize(ref calldata); vault_allocator.contract_address.serialize(ref calldata); - vesu_singleton.serialize(ref calldata); let (manager_address, _) = manager.deploy(@calldata).unwrap(); IManagerDispatcher { contract_address: manager_address } } From 87d6821f5cc6633106a4a38449be60cb9a8c7b45 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 11 Sep 2025 21:08:42 +0100 Subject: [PATCH 38/54] Add ERC721Enumerable support and new merkle tree management system - Add ERC721Enumerable component to redeem request contract - Implement due_assets_from_owner function for tracking user redemptions - Add new starknet_vault_kit decoder and sanitizer components - Introduce comprehensive merkle tree system with base functionality - Add integration modules for AVNU, ERC4626, and Vesu protocols - Clean up decoder components by removing unused dependencies - Remove obsolete register.cairo test file --- .../src/redeem_request/redeem_request.cairo | 11 + packages/vault/src/vault/interface.cairo | 1 + packages/vault/src/vault/vault.cairo | 24 +- .../interface.cairo | 15 + ...knet_vault_kit_decoder_and_sanitizer.cairo | 42 ++ .../vesu_decoder_and_sanitizer.cairo | 6 +- .../vesu_v2_decoder_and_sanitizer.cairo | 6 +- packages/vault_allocator/src/lib.cairo | 18 +- .../src/merkle_tree/base.cairo | 319 ++++++++++ .../src/merkle_tree/integrations/avnu.cairo | 72 +++ .../merkle_tree/integrations/erc4626.cairo | 114 ++++ .../starknet_vault_kit_strategies.cairo | 102 ++++ .../merkle_tree/integrations/vesu_v1.cairo | 91 +++ .../merkle_tree/integrations/vesu_v2.cairo | 95 +++ .../registery.cairo} | 11 + .../avnu_middleware/avnu_middleware.cairo | 115 ++-- .../middlewares/avnu_middleware/errors.cairo | 6 +- .../avnu_middleware/interface.cairo | 9 +- .../vault_allocator/src/test/creator.cairo | 30 +- .../src/test/integrations/avnu.cairo | 23 +- .../integrations/vault_bring_liquidity.cairo | 12 +- .../src/test/integrations/vesu_v1.cairo | 47 +- .../test/scenarios/stable_carry_loop.cairo | 71 ++- .../src/test/units/manager.cairo | 11 +- packages/vault_allocator/src/test/utils.cairo | 559 +----------------- 25 files changed, 1101 insertions(+), 709 deletions(-) create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/interface.cairo create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/starknet_vault_kit_decoder_and_sanitizer.cairo create mode 100644 packages/vault_allocator/src/merkle_tree/base.cairo create mode 100644 packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo create mode 100644 packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo create mode 100644 packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo create mode 100644 packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo create mode 100644 packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo rename packages/vault_allocator/src/{test/register.cairo => merkle_tree/registery.cairo} (86%) diff --git a/packages/vault/src/redeem_request/redeem_request.cairo b/packages/vault/src/redeem_request/redeem_request.cairo index a30d2bab..acabc1e0 100644 --- a/packages/vault/src/redeem_request/redeem_request.cairo +++ b/packages/vault/src/redeem_request/redeem_request.cairo @@ -9,6 +9,7 @@ mod RedeemRequest { }; use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::storage::{ @@ -22,10 +23,16 @@ mod RedeemRequest { component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!( + path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent, + ); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); #[abi(embed_v0)] impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + #[abi(embed_v0)] + impl ERC721EnumerableImpl = + ERC721EnumerableComponent::ERC721EnumerableImpl; impl ERC721InternalImpl = ERC721Component::InternalImpl; impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; @@ -37,6 +44,8 @@ mod RedeemRequest { #[substorage(v0)] erc721: ERC721Component::Storage, #[substorage(v0)] + erc721_enumerable: ERC721EnumerableComponent::Storage, + #[substorage(v0)] upgradeable: UpgradeableComponent::Storage, id_len: u256, id_to_info: Map, @@ -49,6 +58,8 @@ mod RedeemRequest { #[flat] ERC721Event: ERC721Component::Event, #[flat] + ERC721EnumerableEvent: ERC721EnumerableComponent::Event, + #[flat] SRC5Event: SRC5Component::Event, #[flat] UpgradeableEvent: UpgradeableComponent::Event, diff --git a/packages/vault/src/vault/interface.cairo b/packages/vault/src/vault/interface.cairo index b2287f74..d10dbfd6 100644 --- a/packages/vault/src/vault/interface.cairo +++ b/packages/vault/src/vault/interface.cairo @@ -45,5 +45,6 @@ pub trait IVault { fn last_report_timestamp(self: @TContractState) -> u64; fn max_delta(self: @TContractState) -> u256; fn due_assets_from_id(self: @TContractState, id: u256) -> u256; + fn due_assets_from_owner(self: @TContractState, owner: ContractAddress) -> u256; } diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index 7a98e6e2..de20f094 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -24,7 +24,10 @@ pub mod Vault { use openzeppelin::interfaces::erc20::{ ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata, }; - use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; + use openzeppelin::interfaces::erc721::{ + ERC721ABIDispatcher, ERC721ABIDispatcherTrait, IERC721EnumerableDispatcher, + IERC721EnumerableDispatcherTrait, + }; use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::security::pausable::PausableComponent; @@ -881,6 +884,25 @@ pub mod Vault { self.max_delta.read() } + fn due_assets_from_owner(self: @ContractState, owner: ContractAddress) -> u256 { + let balance = ERC721ABIDispatcher { + contract_address: self.redeem_request.read().contract_address, + } + .balance_of(owner); + let mut total_due_assets = 0; + for i in 0..balance { + total_due_assets += self + .due_assets_from_id( + IERC721EnumerableDispatcher { + contract_address: self.redeem_request.read().contract_address, + } + .token_of_owner_by_index(owner, i), + ); + } + total_due_assets + } + + fn due_assets_from_id(self: @ContractState, id: u256) -> u256 { let redeem_request_info = self.redeem_request.read().id_to_info(id); let redeem_request_nominal = redeem_request_info diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..837d592b --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IStarknetVaultKitDecoderAndSanitizer { + fn request_redeem( + self: @T, shares: u256, receiver: ContractAddress, owner: ContractAddress, + ) -> Span; + + fn claim_redeem(self: @T, id: u256) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/starknet_vault_kit_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/starknet_vault_kit_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..0abf72ce --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/starknet_vault_kit_decoder_and_sanitizer.cairo @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod StarknetVaultKitDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; + use vault_allocator::decoders_and_sanitizers::starknet_vault_kit_decoder_and_sanitizer::interface::IStarknetVaultKitDecoderAndSanitizer; + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(VesuDecoderAndSanitizerImpl)] + impl StarknetVaultKitDecoderAndSanitizer< + TContractState, + +HasComponent, + +Erc4626DecoderAndSanitizerComponent::HasComponent, + > of IStarknetVaultKitDecoderAndSanitizer> { + fn request_redeem( + self: @ComponentState, + shares: u256, + receiver: ContractAddress, + owner: ContractAddress, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + receiver.serialize(ref serialized_struct); + owner.serialize(ref serialized_struct); + serialized_struct.span() + } + + fn claim_redeem(self: @ComponentState, id: u256) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + id.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo index 91747a65..5dc1e578 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo @@ -5,8 +5,6 @@ #[starknet::component] pub mod VesuDecoderAndSanitizerComponent { use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParams; - use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; - use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; use vault_allocator::decoders_and_sanitizers::vesu_decoder_and_sanitizer::interface::IVesuDecoderAndSanitizer; #[storage] @@ -18,9 +16,7 @@ pub mod VesuDecoderAndSanitizerComponent { #[embeddable_as(VesuDecoderAndSanitizerImpl)] impl VesuDecoderAndSanitizer< - TContractState, - +HasComponent, - +Erc4626DecoderAndSanitizerComponent::HasComponent, + TContractState, +HasComponent, > of IVesuDecoderAndSanitizer> { fn modify_position( self: @ComponentState, params: ModifyPositionParams, diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo index 996c50a1..e60485c8 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo @@ -5,8 +5,6 @@ #[starknet::component] pub mod VesuV2DecoderAndSanitizerComponent { use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParamsV2; - use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; - use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; use vault_allocator::decoders_and_sanitizers::vesu_v2_decoder_and_sanitizer::interface::IVesuV2DecoderAndSanitizer; #[storage] @@ -18,9 +16,7 @@ pub mod VesuV2DecoderAndSanitizerComponent { #[embeddable_as(VesuDecoderAndSanitizerImpl)] impl VesuDecoderAndSanitizer< - TContractState, - +HasComponent, - +Erc4626DecoderAndSanitizerComponent::HasComponent, + TContractState, +HasComponent, > of IVesuV2DecoderAndSanitizer> { fn modify_position( self: @ComponentState, params: ModifyPositionParamsV2, diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 7b3c83b2..6c3a4f67 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -53,7 +53,10 @@ pub mod decoders_and_sanitizers { pub mod interface; pub mod vesu_decoder_and_sanitizer; } - + pub mod starknet_vault_kit_decoder_and_sanitizer { + pub mod interface; + pub mod starknet_vault_kit_decoder_and_sanitizer; + } pub mod vesu_v2_decoder_and_sanitizer { pub mod interface; pub mod vesu_v2_decoder_and_sanitizer; @@ -75,7 +78,6 @@ pub mod mocks { #[cfg(test)] pub mod test { // pub mod creator; - pub mod register; pub mod utils; pub mod units { pub mod manager; @@ -91,3 +93,15 @@ pub mod test { } } + +pub mod merkle_tree { + pub mod base; + pub mod registery; + pub mod integrations { + pub mod avnu; + pub mod erc4626; + pub mod starknet_vault_kit_strategies; + pub mod vesu_v1; + pub mod vesu_v2; + } +} diff --git a/packages/vault_allocator/src/merkle_tree/base.cairo b/packages/vault_allocator/src/merkle_tree/base.cairo new file mode 100644 index 00000000..7e0d681f --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/base.cairo @@ -0,0 +1,319 @@ +use core::hash::HashStateTrait; +use core::num::traits::Zero; +use core::pedersen::PedersenTrait; +use openzeppelin::interfaces::erc4626::{ERC4626ABIDispatcher, ERC4626ABIDispatcherTrait}; +use openzeppelin::merkle_tree::hashes::PedersenCHasher; +use starknet::ContractAddress; +use starknet::syscalls::call_contract_syscall; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct ManageLeaf { + pub decoder_and_sanitizer: ContractAddress, + pub target: ContractAddress, + pub selector: felt252, + pub argument_addresses: Span, + pub description: ByteArray, +} + + +pub fn get_leaf_hash(leaf: ManageLeaf) -> felt252 { + let mut serialized_struct: Array = ArrayTrait::new(); + leaf.decoder_and_sanitizer.serialize(ref serialized_struct); + leaf.target.serialize(ref serialized_struct); + leaf.selector.serialize(ref serialized_struct); + leaf.argument_addresses.serialize(ref serialized_struct); + let first_element = serialized_struct.pop_front().unwrap(); + let mut state = PedersenTrait::new(first_element); + while let Some(value) = serialized_struct.pop_front() { + state = state.update(value); + } + state.finalize() +} + +pub fn generate_merkle_tree(manage_leafs: Span) -> Array> { + let mut first_layer = ArrayTrait::new(); + let leafs_length = manage_leafs.len(); + for i in 0..leafs_length { + first_layer.append(get_leaf_hash(manage_leafs.at(i).clone())); + } + let mut leafs = ArrayTrait::new(); + leafs.append(first_layer); + _build_tree(leafs) +} + +pub fn _build_tree(merkle_tree_in: Array>) -> Array> { + let merkle_tree_in_length = merkle_tree_in.len(); + + let mut current_layer_index = merkle_tree_in_length - 1; + let current_layer_length = merkle_tree_in[current_layer_index].len(); + let mut next_layer_length = 0; + if (current_layer_length % 2 != 0) { + next_layer_length = (current_layer_length + 1) / 2; + } else { + next_layer_length = current_layer_length / 2; + } + let mut current_layer = ArrayTrait::new(); + let mut count = 0; + let mut i = 0; + while i < current_layer_length { + current_layer + .append( + PedersenCHasher::commutative_hash( + *merkle_tree_in[current_layer_index].at(i), + *merkle_tree_in[current_layer_index].at(i + 1), + ), + ); + count += 1; + i += 2; + } + + let mut merkle_tree_out = merkle_tree_in.clone(); + merkle_tree_out.append(current_layer); + + if (next_layer_length > 1) { + _build_tree(merkle_tree_out) + } else { + merkle_tree_out + } +} + +fn _next_power_of_two(x: u256) -> u256 { + let mut power = 1_u256; + while power < x { + power = power * 2_u256; + } + power +} + + +pub fn _pad_leafs_to_power_of_two(ref leafs: Array, ref leaf_index: u256) { + let target_len = if leaf_index < 4_u256 { + 4_u256 + } else { + _next_power_of_two(leaf_index) + }; + let padding_needed = target_len - leaf_index; + + let mut i: u256 = 0_u256; + while i < padding_needed { + leafs + .append( + ManageLeaf { + decoder_and_sanitizer: Zero::zero(), + target: Zero::zero(), + selector: Zero::zero(), + argument_addresses: ArrayTrait::new().span(), + description: "", + }, + ); + leaf_index += 1_u256; + i += 1_u256; + } +} + + +pub fn _get_proofs_using_tree( + leafs: Array, tree: Array>, +) -> Array> { + let mut proofs = ArrayTrait::new(); + for i in 0..leafs.len() { + let leaf = leafs.at(i); + let mut serialized_struct: Array = ArrayTrait::new(); + leaf.decoder_and_sanitizer.serialize(ref serialized_struct); + leaf.target.serialize(ref serialized_struct); + leaf.selector.serialize(ref serialized_struct); + leaf.argument_addresses.serialize(ref serialized_struct); + let first_element = serialized_struct.pop_front().unwrap(); + let mut state = PedersenTrait::new(first_element); + + while let Some(value) = serialized_struct.pop_front() { + state = state.update(value); + } + let leaf_hash = state.finalize(); + let proof = _generate_proof(leaf_hash, tree.clone()); + proofs.append(proof); + } + proofs +} + +pub fn _generate_proof(mut leaf: felt252, tree: Array>) -> Span { + let tree_length = tree.len(); + let mut proof = ArrayTrait::new(); + for i in 0..tree_length - 1 { + let tree_current_layer = tree.at(i); + let tree_current_layer_length = tree_current_layer.len(); + for j in 0..tree_current_layer_length { + if leaf == *tree_current_layer.at(j) { + let element_to_append = if j % 2 == 0 { + *tree_current_layer.at(j + 1) + } else { + *tree_current_layer.at(j - 1) + }; + leaf = PedersenCHasher::commutative_hash(leaf, element_to_append); + proof.append(element_to_append); + break; + } else { + assert(j != tree_current_layer_length - 1, 'leaf not found in tree'); + } + } + } + proof.span() +} + + +pub fn _add_vault_allocator_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + vault: ContractAddress, +) { + let underlying_asset = ERC4626ABIDispatcher { contract_address: vault }.asset(); + + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: underlying_asset, + selector: selector!("approve"), + argument_addresses: array![vault.into()].span(), + description: "Approve" + + " " + + get_symbol(vault) + + " " + + "to spend" + + " " + + get_symbol(underlying_asset), + }, + ); + leaf_index += 1; + + // Bring liquidity + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: vault, + selector: selector!("bring_liquidity"), + argument_addresses: array![].span(), + description: "Bring liquidity" + " " + get_symbol(vault), + }, + ); + leaf_index += 1; +} + + +pub fn _add_vesu_flash_loan_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault: ContractAddress, + decoder_and_sanitizer: ContractAddress, + manager: ContractAddress, + asset: ContractAddress, + is_legacy: bool, +) { + let mut argument_addresses = ArrayTrait::new(); + manager.serialize(ref argument_addresses); + asset.serialize(ref argument_addresses); + is_legacy.serialize(ref argument_addresses); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: manager, + selector: selector!("flash_loan"), + argument_addresses: argument_addresses.span(), + description: "Flash loan" + " " + get_symbol(asset), + }, + ); + leaf_index += 1; +} + + +pub fn _generate_trading_pairs_from_unique_assets( + ref unique_assets: Array, +) -> Array<(ContractAddress, ContractAddress)> { + let mut trading_pairs: Array<(ContractAddress, ContractAddress)> = ArrayTrait::new(); + loop { + match unique_assets.pop_front() { + Option::Some(asset) => { + for other_asset in @unique_assets { + trading_pairs.append((asset, *other_asset)) + } + }, + Option::None(_) => { break; }, + }; + } + trading_pairs +} + + +pub fn _append_unique_assets( + ref array_of_unique_assets: Array, + assets: Span, + consider_underlying: bool, +) { + for asset in assets { + let mut asset_to_consider = *asset; + if (consider_underlying) { + asset_to_consider = ERC4626ABIDispatcher { contract_address: *asset }.asset(); + } + if (!_contains_address(array_of_unique_assets.span(), asset_to_consider)) { + array_of_unique_assets.append(asset_to_consider); + } + } +} + + +pub fn _contains_address(span: Span, addr: ContractAddress) -> bool { + let mut i = 0; + while i < span.len() { + if *span.at(i) == addr { + return true; + } + i += 1; + } + false +} + + +pub fn get_symbol(contract_address: ContractAddress) -> ByteArray { + let ret_data = call_contract_syscall(contract_address, selector!("symbol"), array![].span()); + match ret_data { + Ok(res) => { + let res_len: u32 = res.len(); + if (res_len == 1) { + let symbol_felt = *res.at(0); + let mut symbol_byte_array: ByteArray = ""; + symbol_byte_array.append_word(symbol_felt, bytes_in_felt(symbol_felt)); + symbol_byte_array + } else { + let mut res_span = res; + Serde::::deserialize(ref res_span).unwrap() + } + }, + Err(revert_reason) => { panic!("revert_reason: {:?}", revert_reason); }, + } +} + + +fn bytes_in_felt(word: felt252) -> usize { + if word == 0 { + return 0; + } + let x: u256 = word.try_into().unwrap(); + + let mut p: u256 = 1_u256; + let mut bytes: usize = 0; + + while p <= x && bytes < 31 { + p = p * 256_u256; + bytes += 1; + } + + bytes +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo new file mode 100644 index 00000000..851f3503 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo @@ -0,0 +1,72 @@ +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, _contains_address, get_symbol}; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone, starknet::Store)] +pub struct AvnuConfig { + pub sell_token: ContractAddress, + pub buy_token: ContractAddress, +} + + +pub fn _add_avnu_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault: ContractAddress, + decoder_and_sanitizer: ContractAddress, + router: ContractAddress, + avnu_configs: Span, +) { + let mut seen_sells: Array = ArrayTrait::new(); + + for i in 0..avnu_configs.len() { + let avnu_config_elem = avnu_configs.at(i); + let sell_token_address = *avnu_config_elem.sell_token; + let buy_token_address = *avnu_config_elem.buy_token; + + if !_contains_address(seen_sells.span(), sell_token_address) { + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: sell_token_address, + selector: selector!("approve"), + argument_addresses: array![router.into()].span(), + description: "Approve" + + " " + + "avnu_router" + + " " + + "to spend" + + " " + + get_symbol(sell_token_address), + }, + ); + leaf_index += 1; + seen_sells.append(sell_token_address); + } + + // swap leaf à chaque paire + let mut argument_addresses = ArrayTrait::new(); + sell_token_address.serialize(ref argument_addresses); + buy_token_address.serialize(ref argument_addresses); + vault.serialize(ref argument_addresses); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: router, + selector: selector!("multi_route_swap"), + argument_addresses: argument_addresses.span(), + description: "Multi route swap" + + " " + + get_symbol(sell_token_address) + + " " + + "for" + + " " + + get_symbol(buy_token_address), + }, + ); + leaf_index += 1; + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo new file mode 100644 index 00000000..1575dfc0 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo @@ -0,0 +1,114 @@ +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +pub fn _add_erc4626_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault: ContractAddress, + decoder_and_sanitizer: ContractAddress, + erc4626: ContractAddress, +) { + let erc4626_erc4646_disp = IERC4626Dispatcher { contract_address: erc4626 }; + let asset = erc4626_erc4646_disp.asset(); + + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: asset, + selector: selector!("approve"), + argument_addresses: array![erc4626.into()].span(), + description: "Approve" + + " " + + get_symbol(erc4626) + + " " + + "to spend" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; + + // Deposits + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("deposit"), + argument_addresses: array![vault.into()].span(), + description: "Deposit" + + " " + + get_symbol(asset) + + " " + + "for" + + " " + + get_symbol(erc4626), + }, + ); + leaf_index += 1; + + // Withdrawals + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("withdraw"), + argument_addresses: array![vault.into(), vault.into()].span(), + description: "Withdraw" + + " " + + get_symbol(asset) + + " " + + "from" + + " " + + get_symbol(erc4626), + }, + ); + leaf_index += 1; + + // Minting + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("mint"), + argument_addresses: array![vault.into()].span(), + description: "Mint" + + " " + + get_symbol(erc4626) + + " " + + "from" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; + + // Redeeming + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("redeem"), + argument_addresses: array![vault.into(), vault.into()].span(), + description: "Redeem" + + " " + + get_symbol(erc4626) + + " " + + "for" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo new file mode 100644 index 00000000..aa2f3a86 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo @@ -0,0 +1,102 @@ +use openzeppelin::interfaces::erc4626::{ERC4626ABIDispatcher, ERC4626ABIDispatcherTrait}; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +pub fn _add_starknet_vault_kit_strategies( + ref leafs: Array, + ref leaf_index: u256, + vault: ContractAddress, + decoder_and_sanitizer: ContractAddress, + starknet_vault_kit_strategy: ContractAddress, +) { + let starknet_vault_kit_strategy_erc4646_disp = ERC4626ABIDispatcher { + contract_address: starknet_vault_kit_strategy, + }; + let asset = starknet_vault_kit_strategy_erc4646_disp.asset(); + + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: asset, + selector: selector!("approve"), + argument_addresses: array![starknet_vault_kit_strategy.into()].span(), + description: "Approve" + + " " + + get_symbol(starknet_vault_kit_strategy) + + " " + + "to spend" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; + + // Deposits + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("deposit"), + argument_addresses: array![vault.into()].span(), + description: "Deposit" + + " " + + get_symbol(asset) + + " " + + "for" + + " " + + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; + + // Withdrawals + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("withdraw"), + argument_addresses: array![vault.into(), vault.into()].span(), + description: "Withdraw" + + " " + + get_symbol(asset) + + " " + + "from" + + " " + + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; + + // Request Redeen + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("request_redeem"), + argument_addresses: array![vault.into(), vault.into()].span(), + description: "Request Redeem" + " " + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; + + // Claim Redeem + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("claim_redeem"), + argument_addresses: array![].span(), + description: "Claim Redeem" + " " + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo new file mode 100644 index 00000000..01c1d1d6 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo @@ -0,0 +1,91 @@ +use core::to_byte_array::FormatAsByteArray; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::registery::VESU_SINGLETON; + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct VesuV1Config { + pub pool_id: felt252, + pub collateral_asset: ContractAddress, + pub debt_assets: Span, +} + +pub fn _add_vesu_v1_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + vesu_v1_configs: Span, +) { + for i in 0..vesu_v1_configs.len() { + let vesu_v1_config_elem = vesu_v1_configs.at(i); + let pool_id = *vesu_v1_config_elem.pool_id; + let collateral_asset = *vesu_v1_config_elem.collateral_asset; + let debt_assets = *vesu_v1_config_elem.debt_assets; + let mut pool_id_str: ByteArray = FormatAsByteArray::format_as_byte_array(@pool_id, 16); + + // APPROVAL of collateral asset to the singleton + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: collateral_asset, + selector: selector!("approve"), + argument_addresses: array![VESU_SINGLETON().into()].span(), + description: "Approve" + + " " + + "singleton" + + "_" + + pool_id_str.clone() + + " " + + "to spend" + + " " + + get_symbol(collateral_asset), + }, + ); + leaf_index += 1; + + for j in 0..debt_assets.len() { + let debt_asset_elem = *debt_assets.at(j); + + // MODIFY POSITION + let mut argument_addresses_modify_position = ArrayTrait::new(); + + // pool_id + pool_id.serialize(ref argument_addresses_modify_position); + + // collateral_asset + collateral_asset.serialize(ref argument_addresses_modify_position); + + // debt_asset + debt_asset_elem.serialize(ref argument_addresses_modify_position); + + // user + vault_allocator.serialize(ref argument_addresses_modify_position); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: VESU_SINGLETON(), + selector: selector!("modify_position"), + argument_addresses: argument_addresses_modify_position.span(), + description: "Modify position" + + " " + + "extension_pid" + + "_" + + pool_id_str.clone() + + " " + + "with collateral" + + " " + + get_symbol(collateral_asset) + + " " + + "and debt" + + " " + + get_symbol(debt_asset_elem), + }, + ); + leaf_index += 1; + } + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo new file mode 100644 index 00000000..1335e298 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo @@ -0,0 +1,95 @@ +use core::to_byte_array::FormatAsByteArray; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct VesuV2Config { + pub pool_contract: ContractAddress, + pub collateral_asset: ContractAddress, + pub debt_assets: Span, +} + + +pub fn _add_vesu_v2_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + vesu_v2_configs: Span, +) { + for i in 0..vesu_v2_configs.len() { + let vesu_v2_config_elem = vesu_v2_configs.at(i); + let pool_contract = *vesu_v2_config_elem.pool_contract; + let collateral_asset = *vesu_v2_config_elem.collateral_asset; + let debt_assets = *vesu_v2_config_elem.debt_assets; + let pool_contract_felt: felt252 = pool_contract.into(); + let mut pool_contract_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @pool_contract_felt, 16, + ); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: collateral_asset, + selector: selector!("approve"), + argument_addresses: array![pool_contract.into()].span(), + description: "Approve" + + " " + + "pool contract" + + "_" + + pool_contract_str.clone() + + " " + + "to spend" + + " " + + get_symbol(collateral_asset), + }, + ); + leaf_index += 1; + + // debt mode + let debt_assets_len = debt_assets.len(); + for j in 0..debt_assets_len { + // APPROVAL of collateral asset to the pool contract + + let debt_asset = *debt_assets.at(j); + + // MODIFY POSITION + let mut argument_addresses_modify_position = ArrayTrait::new(); + + // collateral_asset + collateral_asset.serialize(ref argument_addresses_modify_position); + + // debt_asset + debt_asset.serialize(ref argument_addresses_modify_position); + + // user + vault_allocator.serialize(ref argument_addresses_modify_position); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: pool_contract, + selector: selector!("modify_position"), + argument_addresses: argument_addresses_modify_position.span(), + description: "Modify position" + + " " + + "extension_pid" + + "_" + + pool_contract_str.clone() + + " " + + "with collateral" + + " " + + get_symbol(collateral_asset) + + " " + + "and debt" + + " " + + get_symbol(debt_asset), + }, + ); + leaf_index += 1; + } + } +} diff --git a/packages/vault_allocator/src/test/register.cairo b/packages/vault_allocator/src/merkle_tree/registery.cairo similarity index 86% rename from packages/vault_allocator/src/test/register.cairo rename to packages/vault_allocator/src/merkle_tree/registery.cairo index 5a8cf769..16819ecf 100644 --- a/packages/vault_allocator/src/test/register.cairo +++ b/packages/vault_allocator/src/merkle_tree/registery.cairo @@ -43,6 +43,17 @@ pub fn VESU_SINGLETON() -> ContractAddress { pub const GENESIS_POOL_ID: felt252 = 2198503327643286920898110335698706244522220458610657370981979460625005526824; +// genesis pool v-tokens + +pub fn VESU_GENESIS_POOL_V_TOKEN_WSTETH() -> ContractAddress { + 0x7cb1a46709214b94f51655be696a4ff6f9bdbbb6edb19418b6a55d190536048.try_into().unwrap() +} + +pub fn VESU_GENESIS_POOL_V_TOKEN_USDT() -> ContractAddress { + 0x40e480d202b47eb9335c31fc328ecda216231425dae74f87d1a97e6e7901dce.try_into().unwrap() +} + + // PRAGMA pub fn PRAGMA() -> ContractAddress { diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo index 6272c225..7ce06964 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -4,7 +4,7 @@ #[starknet::contract] pub mod AvnuMiddleware { - const BPS_SCALE: u256 = 10_000_u256; + const BPS_SCALE: u16 = 10_000; use core::num::traits::Zero; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; @@ -18,6 +18,7 @@ pub mod AvnuMiddleware { use vault_allocator::integration_interfaces::avnu::{ IAvnuExchangeDispatcher, IAvnuExchangeDispatcherTrait, }; + use vault_allocator::merkle_tree::registery::AVNU_ROUTER; use vault_allocator::middlewares::avnu_middleware::errors::Errors; use vault_allocator::middlewares::avnu_middleware::interface::IAvnuMiddleware; use vault_allocator::periphery::price_router::interface::{ @@ -31,22 +32,17 @@ pub mod AvnuMiddleware { impl OwnableImpl = OwnableComponent::OwnableImpl; impl InternalImpl = OwnableComponent::InternalImpl; - #[derive(Copy, Drop, Serde, starknet::Store)] - struct Config { - period: u64, - allowed_calls_per_period: u64, - } - #[storage] struct Storage { #[substorage(v0)] ownable: OwnableComponent::Storage, - avnu_router: IAvnuExchangeDispatcher, price_router: IPriceRouterDispatcher, - slippage_tolerance_bps: u256, - config: Option, - call_count: Map<(ContractAddress, u64), u64>, + vault_allocator: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + call_count: Map, } #[event] @@ -54,17 +50,12 @@ pub mod AvnuMiddleware { pub enum Event { #[flat] OwnableEvent: OwnableComponent::Event, - SlippageUpdated: SlippageUpdated, ConfigUpdated: ConfigUpdated, } - #[derive(Drop, starknet::Event)] - pub struct SlippageUpdated { - pub slippage: u256, - } - #[derive(Drop, starknet::Event)] struct ConfigUpdated { + pub slippage: u16, period: u64, allowed_calls_per_period: u64, } @@ -74,52 +65,45 @@ pub mod AvnuMiddleware { fn constructor( ref self: ContractState, owner: ContractAddress, - avnu_router: ContractAddress, + vault_allocator: ContractAddress, price_router: ContractAddress, - initial_slippage_bps: u256, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, ) { self.ownable.initializer(owner); - self.avnu_router.write(IAvnuExchangeDispatcher { contract_address: avnu_router }); self.price_router.write(IPriceRouterDispatcher { contract_address: price_router }); - self.slippage_tolerance_bps.write(initial_slippage_bps); + self.vault_allocator.write(vault_allocator); + self._set_config(slippage, period, allowed_calls_per_period) } #[abi(embed_v0)] impl AvnuMiddlewareViewImpl of IAvnuMiddleware { fn avnu_router(self: @ContractState) -> ContractAddress { - self.avnu_router.read().contract_address + AVNU_ROUTER() } fn price_router(self: @ContractState) -> ContractAddress { self.price_router.read().contract_address } - fn slippage_tolerance_bps(self: @ContractState) -> u256 { - self.slippage_tolerance_bps.read() + fn vault_allocator(self: @ContractState) -> ContractAddress { + self.vault_allocator.read() } - fn set_config(ref self: ContractState, period: u64, allowed_calls_per_period: u64) { - self.ownable.assert_only_owner(); - if (period.is_zero()) { - Errors::period_zero(); - } - if (allowed_calls_per_period.is_zero()) { - Errors::allowed_calls_per_period_zero(); - } - self.config.write(Option::Some(Config { period, allowed_calls_per_period })); - self.emit(ConfigUpdated { period, allowed_calls_per_period }); + fn config(self: @ContractState) -> (u16, u64, u64) { + (self.slippage.read(), self.period.read(), self.allowed_calls_per_period.read()) } - - fn set_slippage_tolerance_bps(ref self: ContractState, new_slippage_bps: u256) { + fn set_config( + ref self: ContractState, slippage: u16, period: u64, allowed_calls_per_period: u64, + ) { self.ownable.assert_only_owner(); - if (new_slippage_bps >= BPS_SCALE) { - Errors::slippage_exceeds_max(new_slippage_bps); - } - self.slippage_tolerance_bps.write(new_slippage_bps); - self.emit(SlippageUpdated { slippage: new_slippage_bps }); + self._set_config(slippage, period, allowed_calls_per_period); + self.emit(ConfigUpdated { slippage, period, allowed_calls_per_period }); } + fn multi_route_swap( ref self: ContractState, sell_token_address: ContractAddress, @@ -147,7 +131,7 @@ pub mod AvnuMiddleware { } let sell = ERC20ABIDispatcher { contract_address: sell_token_address }; let buy = ERC20ABIDispatcher { contract_address: buy_token_address }; - let avnu = self.avnu_router.read(); + let avnu = IAvnuExchangeDispatcher { contract_address: AVNU_ROUTER() }; sell.transfer_from(caller, this, sell_token_amount); sell.approve(avnu.contract_address, sell_token_amount); let quote_out = self @@ -157,8 +141,8 @@ pub mod AvnuMiddleware { let computed_min = math::u256_mul_div( quote_out, - BPS_SCALE - self.slippage_tolerance_bps.read(), - BPS_SCALE, + (BPS_SCALE - self.slippage.read()).into(), + BPS_SCALE.into(), math::Rounding::Ceil, ); @@ -194,19 +178,38 @@ pub mod AvnuMiddleware { #[generate_trait] pub impl InternalFunctions of InternalFunctionsTrait { - fn enforce_rate_limit(ref self: ContractState, vault: ContractAddress) { - if (self.config.read().is_some()) { - let cfg = self.config.read().unwrap(); - let ts: u64 = get_block_timestamp(); - let slot = ts % cfg.period; - let key = (vault, slot); - let current = self.call_count.read(key); - let next = current + 1; - if (next > cfg.allowed_calls_per_period) { - Errors::rate_limit_exceeded(next, cfg.allowed_calls_per_period); - } - self.call_count.write(key, next); + fn enforce_rate_limit(ref self: ContractState, caller: ContractAddress) { + if (caller != self.vault_allocator.read()) { + Errors::caller_not_vault_allocator(); + } + let ts: u64 = get_block_timestamp(); + let slot = ts % self.period.read(); + let current = self.call_count.read(slot); + let next = current + 1; + let allowed_calls_per_period = self.allowed_calls_per_period.read(); + if (next > allowed_calls_per_period) { + Errors::rate_limit_exceeded(next, allowed_calls_per_period); + } + self.call_count.write(slot, next); + } + + fn _set_config( + ref self: ContractState, slippage: u16, period: u64, allowed_calls_per_period: u64, + ) { + if (slippage >= BPS_SCALE) { + Errors::slippage_exceeds_max(slippage); + } + + self.slippage.write(slippage); + + if (period.is_zero()) { + Errors::period_zero(); + } + self.period.write(period); + if (allowed_calls_per_period.is_zero()) { + Errors::allowed_calls_per_period_zero(); } + self.allowed_calls_per_period.write(allowed_calls_per_period); } } } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo index 1c90966a..cef66fda 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo @@ -7,7 +7,7 @@ pub mod Errors { panic!("Insufficient output: {} < {}", out, min); } - pub fn slippage_exceeds_max(slippage: u256) { + pub fn slippage_exceeds_max(slippage: u16) { panic!("Slippage exceeds max: {}", slippage); } @@ -22,4 +22,8 @@ pub mod Errors { pub fn allowed_calls_per_period_zero() { panic!("Allowed calls per period is zero"); } + + pub fn caller_not_vault_allocator() { + panic!("Caller not vault allocator"); + } } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo index 9fbb8bb5..7b424486 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo @@ -9,11 +9,9 @@ use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; pub trait IAvnuMiddleware { fn avnu_router(self: @T) -> ContractAddress; fn price_router(self: @T) -> ContractAddress; - fn slippage_tolerance_bps(self: @T) -> u256; - - fn set_config(ref self: T, period: u64, allowed_calls_per_period: u64); - - + fn vault_allocator(self: @T) -> ContractAddress; + fn config(self: @T) -> (u16, u64, u64); + fn set_config(ref self: T, slippage: u16, period: u64, allowed_calls_per_period: u64); fn multi_route_swap( ref self: T, sell_token_address: ContractAddress, @@ -26,5 +24,4 @@ pub trait IAvnuMiddleware { integrator_fee_recipient: ContractAddress, routes: Array, ) -> u256; - fn set_slippage_tolerance_bps(ref self: T, new_slippage_bps: u256); } diff --git a/packages/vault_allocator/src/test/creator.cairo b/packages/vault_allocator/src/test/creator.cairo index ce31509d..9f01c5c0 100644 --- a/packages/vault_allocator/src/test/creator.cairo +++ b/packages/vault_allocator/src/test/creator.cairo @@ -3,13 +3,15 @@ // Licensed under the MIT License. See LICENSE file for details. use alexandria_math::i257::I257Impl; use starknet::ContractAddress; -use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, wstETH}; -use vault_allocator::test::utils::{ - ManageLeaf, _add_avnu_leafs, _add_vesu_leafs, _pad_leafs_to_power_of_two, generate_merkle_tree, - get_leaf_hash, +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _pad_leafs_to_power_of_two, generate_merkle_tree, get_leaf_hash, }; +use vault_allocator::merkle_tree::integrations::avnu::{AvnuConfig, _add_avnu_leafs}; +use vault_allocator::merkle_tree::integrations::vesu_v1::{VesuV1Config, _add_vesu_v1_leafs}; +use vault_allocator::merkle_tree::registery::{ETH, GENESIS_POOL_ID, wstETH}; use super::utils::DUMMY_ADDRESS; + #[derive(PartialEq, Drop, Serde, Debug, Clone)] pub struct ManageLeafAdditionalData { pub decoder_and_sanitizer: ContractAddress, @@ -36,26 +38,28 @@ fn test_creator() { // INTEGRATIONS let pool_id = GENESIS_POOL_ID; - let mut assets_to_supply = ArrayTrait::new(); - assets_to_supply.append(wstETH()); - let mut assets_to_borrow_per_assets_to_supply = ArrayTrait::new(); - assets_to_borrow_per_assets_to_supply.append(array![ETH()].span()); - _add_vesu_leafs( + _add_vesu_v1_leafs( ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, - pool_id, - assets_to_supply.span(), - assets_to_borrow_per_assets_to_supply.span(), + array![ + VesuV1Config { pool_id, collateral_asset: wstETH(), debt_assets: array![ETH()].span() }, + ] + .span(), ); let mut pairs_to_swap = ArrayTrait::new(); pairs_to_swap.append((ETH(), wstETH())); _add_avnu_leafs( - ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, router, pairs_to_swap, + ref leafs, + ref leaf_index, + vault_allocator, + decoder_and_sanitizer, + router, + array![AvnuConfig { sell_token: ETH(), buy_token: wstETH() }].span(), ); let leaf_used = leafs.len(); diff --git a/packages/vault_allocator/src/test/integrations/avnu.cairo b/packages/vault_allocator/src/test/integrations/avnu.cairo index 260a4709..57360edc 100644 --- a/packages/vault_allocator/src/test/integrations/avnu.cairo +++ b/packages/vault_allocator/src/test/integrations/avnu.cairo @@ -9,18 +9,19 @@ use snforge_std::{map_entry_address, store}; use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::middlewares::avnu_middleware::interface::{ - IAvnuMiddlewareDispatcher, IAvnuMiddlewareDispatcherTrait, +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, }; -use vault_allocator::test::register::{ETH, wstETH}; +use vault_allocator::merkle_tree::integrations::avnu::{AvnuConfig, _add_avnu_leafs}; +use vault_allocator::merkle_tree::registery::{ETH, wstETH}; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _get_proofs_using_tree, - _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, deploy_price_router, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, - generate_merkle_tree, initialize_price_router, + initialize_price_router, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; + #[fork("AVNU")] #[test] fn test_manage_vault_with_merkle_verification_avnu() { @@ -29,7 +30,9 @@ fn test_manage_vault_with_merkle_verification_avnu() { let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let price_router = deploy_price_router(); initialize_price_router(price_router); - let avnu_middleware = deploy_avnu_middleware(price_router); + let avnu_middleware = deploy_avnu_middleware( + vault_allocator.contract_address, price_router, 100, 100000, 1000000, + ); // 1% slippage let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; @@ -40,7 +43,7 @@ fn test_manage_vault_with_merkle_verification_avnu() { vault_allocator.contract_address, simple_decoder_and_sanitizer, avnu_middleware, - array![(wstETH(), ETH())], + array![AvnuConfig { sell_token: wstETH(), buy_token: ETH() }].span(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -143,10 +146,6 @@ fn test_manage_vault_with_merkle_verification_avnu() { manage_leafs.append(leafs.at(0).clone()); manage_leafs.append(leafs.at(1).clone()); - cheat_caller_address_once(avnu_middleware, OWNER()); - IAvnuMiddlewareDispatcher { contract_address: avnu_middleware } - .set_slippage_tolerance_bps(100); // 1% slippage - let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); cheat_caller_address_once(manager.contract_address, STRATEGIST()); diff --git a/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo index f29436d7..acd56489 100644 --- a/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo +++ b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo @@ -6,12 +6,14 @@ use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrai use openzeppelin::interfaces::erc4626::IERC4626Dispatcher; use snforge_std::{map_entry_address, store}; use vault_allocator::manager::interface::IManagerDispatcherTrait; +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _add_vault_allocator_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, + generate_merkle_tree, +}; use vault_allocator::mocks::vault::MockVault::MockVaultTraitDispatcherTrait; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_vault_allocator_leafs, _get_proofs_using_tree, - _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_erc20_mock, deploy_manager, + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_erc20_mock, deploy_manager, deploy_mock_vault, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, - generate_merkle_tree, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; @@ -24,8 +26,6 @@ fn test_manage_vault_with_merkle_verification_bring_liquidity() { let underlying_token = deploy_erc20_mock(); let mock_vault = deploy_mock_vault(underlying_token); - let mock_vault_erc4626 = IERC4626Dispatcher { contract_address: mock_vault.contract_address }; - let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; @@ -34,7 +34,7 @@ fn test_manage_vault_with_merkle_verification_bring_liquidity() { ref leaf_index, vault_allocator.contract_address, simple_decoder_and_sanitizer, - mock_vault_erc4626, + mock_vault.contract_address, ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); diff --git a/packages/vault_allocator/src/test/integrations/vesu_v1.cairo b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo index de7886fd..4e72a03c 100644 --- a/packages/vault_allocator/src/test/integrations/vesu_v1.cairo +++ b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo @@ -9,16 +9,18 @@ use snforge_std::{map_entry_address, store}; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ Amount, AmountDenomination, AmountType, }; -use vault_allocator::integration_interfaces::vesu::{ - IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, - ISingletonV2DispatcherTrait, -}; use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, VESU_SINGLETON, wstETH}; +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, +}; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; +use vault_allocator::merkle_tree::integrations::vesu_v1::{VesuV1Config, _add_vesu_v1_leafs}; +use vault_allocator::merkle_tree::registery::{ + ETH, GENESIS_POOL_ID, VESU_GENESIS_POOL_V_TOKEN_WSTETH, VESU_SINGLETON, wstETH, +}; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_vesu_leafs, _get_proofs_using_tree, - _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_manager, - deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, generate_merkle_tree, + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_manager, + deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; @@ -32,14 +34,12 @@ fn test_manage_vault_with_merkle_verification_earn_mode() { let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; - _add_vesu_leafs( + _add_erc4626_leafs( ref leafs, ref leaf_index, vault_allocator.contract_address, simple_decoder_and_sanitizer, - GENESIS_POOL_ID, - array![wstETH()].span(), - array![array![ETH()].span()].span(), + VESU_GENESIS_POOL_V_TOKEN_WSTETH(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -78,11 +78,7 @@ fn test_manage_vault_with_merkle_verification_earn_mode() { let mut array_of_targets = ArrayTrait::new(); array_of_targets.append(wstETH()); - let v_token = IDefaultExtensionPOV2Dispatcher { - contract_address: ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() } - .extension(GENESIS_POOL_ID), - } - .v_token_for_collateral_asset(GENESIS_POOL_ID, wstETH()); + let v_token = VESU_GENESIS_POOL_V_TOKEN_WSTETH(); array_of_targets.append(v_token); let mut array_of_selectors = ArrayTrait::new(); @@ -283,14 +279,19 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; - _add_vesu_leafs( + _add_vesu_v1_leafs( ref leafs, ref leaf_index, vault_allocator.contract_address, simple_decoder_and_sanitizer, - GENESIS_POOL_ID, - array![wstETH()].span(), - array![array![ETH()].span()].span(), + array![ + VesuV1Config { + pool_id: GENESIS_POOL_ID, + collateral_asset: wstETH(), + debt_assets: array![ETH()].span(), + }, + ] + .span(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -382,8 +383,8 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { array_of_calldatas.append(array_of_calldata_modify_position.span()); let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(5).clone()); - manage_leafs.append(leafs.at(6).clone()); + manage_leafs.append(leafs.at(0).clone()); + manage_leafs.append(leafs.at(1).clone()); let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); cheat_caller_address_once(manager.contract_address, STRATEGIST()); diff --git a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo index 41a4f7f4..47e6f9f9 100644 --- a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo +++ b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo @@ -9,20 +9,20 @@ use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ Amount, AmountDenomination, AmountType, Route, }; -use vault_allocator::integration_interfaces::vesu::{ - IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, - ISingletonV2DispatcherTrait, -}; use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::middlewares::avnu_middleware::interface::{ - IAvnuMiddlewareDispatcher, IAvnuMiddlewareDispatcherTrait, +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, +}; +use vault_allocator::merkle_tree::integrations::avnu::{AvnuConfig, _add_avnu_leafs}; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; +use vault_allocator::merkle_tree::integrations::vesu_v1::{VesuV1Config, _add_vesu_v1_leafs}; +use vault_allocator::merkle_tree::registery::{ + ETH, GENESIS_POOL_ID, USDC, USDT, VESU_GENESIS_POOL_V_TOKEN_USDT, VESU_SINGLETON, }; -use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, USDC, USDT, VESU_SINGLETON}; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _add_vesu_leafs, _get_proofs_using_tree, - _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, deploy_price_router, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, - generate_merkle_tree, initialize_price_router, + initialize_price_router, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; @@ -34,18 +34,36 @@ fn test_stable_carry_loop() { let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let price_router = deploy_price_router(); initialize_price_router(price_router); - let avnu_middleware = deploy_avnu_middleware(price_router); + let avnu_middleware = deploy_avnu_middleware( + vault_allocator.contract_address, price_router, 100, 100000, 1000000, + ); let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; - _add_vesu_leafs( + + // add vesu v1 leafs + _add_vesu_v1_leafs( + ref leafs, + ref leaf_index, + vault_allocator.contract_address, + simple_decoder_and_sanitizer, + array![ + VesuV1Config { + pool_id: GENESIS_POOL_ID, + collateral_asset: ETH(), + debt_assets: array![USDC()].span(), + }, + ] + .span(), + ); + + // add erc4626 leafs for usdt v-token + _add_erc4626_leafs( ref leafs, ref leaf_index, vault_allocator.contract_address, simple_decoder_and_sanitizer, - GENESIS_POOL_ID, - array![ETH(), USDT()].span(), - array![array![USDC()].span(), array![].span()].span(), + VESU_GENESIS_POOL_V_TOKEN_USDT(), ); _add_avnu_leafs( @@ -54,7 +72,7 @@ fn test_stable_carry_loop() { vault_allocator.contract_address, simple_decoder_and_sanitizer, avnu_middleware, - array![(USDC(), USDT())], + array![AvnuConfig { sell_token: USDC(), buy_token: USDT() }].span(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -109,12 +127,7 @@ fn test_stable_carry_loop() { array_of_targets.append(USDC()); array_of_targets.append(avnu_middleware); array_of_targets.append(USDT()); - let v_token = IDefaultExtensionPOV2Dispatcher { - contract_address: ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() } - .extension(GENESIS_POOL_ID), - } - .v_token_for_collateral_asset(GENESIS_POOL_ID, USDT()); - array_of_targets.append(v_token); + array_of_targets.append(VESU_GENESIS_POOL_V_TOKEN_USDT()); let mut array_of_selectors = ArrayTrait::new(); array_of_selectors.append(selector!("approve")); @@ -214,7 +227,7 @@ fn test_stable_carry_loop() { // approve usdt to the v-token let mut array_of_calldata_approve_vtoken: Array = ArrayTrait::new(); - v_token.serialize(ref array_of_calldata_approve_vtoken); + VESU_GENESIS_POOL_V_TOKEN_USDT().serialize(ref array_of_calldata_approve_vtoken); earn_amount_from_swap.serialize(ref array_of_calldata_approve_vtoken); array_of_calldatas.append(array_of_calldata_approve_vtoken.span()); @@ -225,16 +238,12 @@ fn test_stable_carry_loop() { array_of_calldatas.append(array_of_calldata_deposit.span()); let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(5).clone()); - manage_leafs.append(leafs.at(6).clone()); - manage_leafs.append(leafs.at(12).clone()); - manage_leafs.append(leafs.at(13).clone()); + manage_leafs.append(leafs.at(0).clone()); + manage_leafs.append(leafs.at(1).clone()); manage_leafs.append(leafs.at(7).clone()); manage_leafs.append(leafs.at(8).clone()); - - cheat_caller_address_once(avnu_middleware, OWNER()); - IAvnuMiddlewareDispatcher { contract_address: avnu_middleware } - .set_slippage_tolerance_bps(100); // 1% slippage + manage_leafs.append(leafs.at(2).clone()); + manage_leafs.append(leafs.at(3).clone()); let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); diff --git a/packages/vault_allocator/src/test/units/manager.cairo b/packages/vault_allocator/src/test/units/manager.cairo index bdce5849..4fc4c653 100644 --- a/packages/vault_allocator/src/test/units/manager.cairo +++ b/packages/vault_allocator/src/test/units/manager.cairo @@ -10,12 +10,15 @@ use openzeppelin::interfaces::security::pausable::{IPausableDispatcher, IPausabl use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; use vault_allocator::manager::manager::Manager::{OWNER_ROLE, PAUSER_ROLE}; +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, +}; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; use vault_allocator::test::utils::{ - DUMMY_ADDRESS, ManageLeaf, OWNER, STRATEGIST, WAD, _add_erc4626_leafs, _get_proofs_using_tree, - _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_counter, deploy_erc20_mock, - deploy_erc4626_mock, deploy_manager, deploy_simple_decoder_and_sanitizer, - deploy_vault_allocator, generate_merkle_tree, + DUMMY_ADDRESS, OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_counter, + deploy_erc20_mock, deploy_erc4626_mock, deploy_manager, deploy_simple_decoder_and_sanitizer, + deploy_vault_allocator, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index dbcb7be7..050deec2 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -2,33 +2,22 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use core::hash::HashStateTrait; -use core::num::traits::Zero; -use core::pedersen::PedersenTrait; -use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use openzeppelin::merkle_tree::hashes::PedersenCHasher; use snforge_std::{CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare}; -use starknet::syscalls::call_contract_syscall; use starknet::{ClassHash, ContractAddress}; -use vault_allocator::integration_interfaces::vesu::{ - IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, - ISingletonV2DispatcherTrait, -}; use vault_allocator::manager::interface::IManagerDispatcher; +use vault_allocator::merkle_tree::registery::{ + DAI, DAI_PRAGMA_ID, ETH, ETH_PRAGMA_ID, PRAGMA, STRK, STRK_PRAGMA_ID, USDC, USDC_PRAGMA_ID, + USDT, USDT_PRAGMA_ID, WBTC, WBTC_PRAGMA_ID, wstETH, wstETH_PRAGMA_ID, +}; use vault_allocator::mocks::counter::ICounterDispatcher; use vault_allocator::mocks::vault::MockVault::MockVaultTraitDispatcher; use vault_allocator::periphery::price_router::interface::{ IPriceRouterDispatcher, IPriceRouterDispatcherTrait, }; -use vault_allocator::test::register::{ - AVNU_ROUTER, DAI, DAI_PRAGMA_ID, ETH, ETH_PRAGMA_ID, PRAGMA, STRK, STRK_PRAGMA_ID, USDC, - USDC_PRAGMA_ID, USDT, USDT_PRAGMA_ID, VESU_SINGLETON, WBTC, WBTC_PRAGMA_ID, wstETH, - wstETH_PRAGMA_ID, -}; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcher; pub const WAD: u256 = 1_000_000_000_000_000_000; pub const INITIAL_SLIPPAGE_BPS: u256 = 100; // 1% -use core::to_byte_array::FormatAsByteArray; pub fn OWNER() -> ContractAddress { @@ -145,13 +134,21 @@ pub fn initialize_price_router(price_router: ContractAddress) { price_router.set_asset_to_id(DAI(), DAI_PRAGMA_ID()); } -pub fn deploy_avnu_middleware(price_router: ContractAddress) -> ContractAddress { +pub fn deploy_avnu_middleware( + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, +) -> ContractAddress { let avnu_middleware = declare("AvnuMiddleware").unwrap().contract_class(); let mut calldata = ArrayTrait::new(); OWNER().serialize(ref calldata); - AVNU_ROUTER().serialize(ref calldata); + vault_allocator.serialize(ref calldata); price_router.serialize(ref calldata); - INITIAL_SLIPPAGE_BPS.serialize(ref calldata); + slippage.serialize(ref calldata); + period.serialize(ref calldata); + allowed_calls_per_period.serialize(ref calldata); let (avnu_middleware_address, _) = avnu_middleware.deploy(@calldata).unwrap(); avnu_middleware_address } @@ -163,529 +160,3 @@ pub fn cheat_caller_address_once( cheat_caller_address(:contract_address, :caller_address, span: CheatSpan::TargetCalls(1)); } - -#[derive(PartialEq, Drop, Serde, Debug, Clone)] -pub struct ManageLeaf { - pub decoder_and_sanitizer: ContractAddress, - pub target: ContractAddress, - pub selector: felt252, - pub argument_addresses: Span, - pub description: ByteArray, -} - -pub fn get_leaf_hash(leaf: ManageLeaf) -> felt252 { - let mut serialized_struct: Array = ArrayTrait::new(); - leaf.decoder_and_sanitizer.serialize(ref serialized_struct); - leaf.target.serialize(ref serialized_struct); - leaf.selector.serialize(ref serialized_struct); - leaf.argument_addresses.serialize(ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } - state.finalize() -} - -pub fn generate_merkle_tree(manage_leafs: Span) -> Array> { - let mut first_layer = ArrayTrait::new(); - let leafs_length = manage_leafs.len(); - for i in 0..leafs_length { - first_layer.append(get_leaf_hash(manage_leafs.at(i).clone())); - } - let mut leafs = ArrayTrait::new(); - leafs.append(first_layer); - _build_tree(leafs) -} - -pub fn _build_tree(merkle_tree_in: Array>) -> Array> { - let merkle_tree_in_length = merkle_tree_in.len(); - - let mut current_layer_index = merkle_tree_in_length - 1; - let current_layer_length = merkle_tree_in[current_layer_index].len(); - let mut next_layer_length = 0; - if (current_layer_length % 2 != 0) { - next_layer_length = (current_layer_length + 1) / 2; - } else { - next_layer_length = current_layer_length / 2; - } - let mut current_layer = ArrayTrait::new(); - let mut count = 0; - let mut i = 0; - while i < current_layer_length { - current_layer - .append( - PedersenCHasher::commutative_hash( - *merkle_tree_in[current_layer_index].at(i), - *merkle_tree_in[current_layer_index].at(i + 1), - ), - ); - count += 1; - i += 2; - } - - let mut merkle_tree_out = merkle_tree_in.clone(); - merkle_tree_out.append(current_layer); - - if (next_layer_length > 1) { - _build_tree(merkle_tree_out) - } else { - merkle_tree_out - } -} - -fn _next_power_of_two(x: u256) -> u256 { - let mut power = 1_u256; - while power < x { - power = power * 2_u256; - } - power -} - - -pub fn _pad_leafs_to_power_of_two(ref leafs: Array, ref leaf_index: u256) { - let target_len = if leaf_index < 4_u256 { - 4_u256 - } else { - _next_power_of_two(leaf_index) - }; - let padding_needed = target_len - leaf_index; - - let mut i: u256 = 0_u256; - while i < padding_needed { - leafs - .append( - ManageLeaf { - decoder_and_sanitizer: Zero::zero(), - target: Zero::zero(), - selector: Zero::zero(), - argument_addresses: ArrayTrait::new().span(), - description: "", - }, - ); - leaf_index += 1_u256; - i += 1_u256; - } -} - - -pub fn _get_proofs_using_tree( - leafs: Array, tree: Array>, -) -> Array> { - let mut proofs = ArrayTrait::new(); - for i in 0..leafs.len() { - let leaf = leafs.at(i); - let mut serialized_struct: Array = ArrayTrait::new(); - leaf.decoder_and_sanitizer.serialize(ref serialized_struct); - leaf.target.serialize(ref serialized_struct); - leaf.selector.serialize(ref serialized_struct); - leaf.argument_addresses.serialize(ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); - - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } - let leaf_hash = state.finalize(); - let proof = _generate_proof(leaf_hash, tree.clone()); - proofs.append(proof); - } - proofs -} - -pub fn _generate_proof(mut leaf: felt252, tree: Array>) -> Span { - let tree_length = tree.len(); - let mut proof = ArrayTrait::new(); - for i in 0..tree_length - 1 { - let tree_current_layer = tree.at(i); - let tree_current_layer_length = tree_current_layer.len(); - for j in 0..tree_current_layer_length { - if leaf == *tree_current_layer.at(j) { - let element_to_append = if j % 2 == 0 { - *tree_current_layer.at(j + 1) - } else { - *tree_current_layer.at(j - 1) - }; - leaf = PedersenCHasher::commutative_hash(leaf, element_to_append); - proof.append(element_to_append); - break; - } else { - assert(j != tree_current_layer_length - 1, 'leaf not found in tree'); - } - } - } - proof.span() -} - - -// ========================================= VaultAllocator -// ========================================= - -pub fn _add_vault_allocator_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault_allocator: ContractAddress, - decoder_and_sanitizer: ContractAddress, - vault: IERC4626Dispatcher, -) { - let underlying_asset = vault.asset(); - - // Approvals - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: underlying_asset, - selector: selector!("approve"), - argument_addresses: array![vault.contract_address.into()].span(), - description: "Approve" - + " " - + get_symbol(vault.contract_address) - + " " - + "to spend" - + " " - + get_symbol(underlying_asset), - }, - ); - leaf_index += 1; - - // Bring liquidity - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: vault.contract_address, - selector: selector!("bring_liquidity"), - argument_addresses: array![].span(), - description: "Bring liquidity" + " " + get_symbol(vault.contract_address), - }, - ); - leaf_index += 1; -} - - -// ========================================= ERC4626 ========================================= - -pub fn _add_erc4626_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - erc4626: ContractAddress, -) { - let erc4626_erc4646_disp = IERC4626Dispatcher { contract_address: erc4626 }; - let asset = erc4626_erc4646_disp.asset(); - - // Approvals - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: asset, - selector: selector!("approve"), - argument_addresses: array![erc4626.into()].span(), - description: "Approve" - + " " - + get_symbol(erc4626) - + " " - + "to spend" - + " " - + get_symbol(asset), - }, - ); - leaf_index += 1; - - // Deposits - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("deposit"), - argument_addresses: array![vault.into()].span(), - description: "Deposit" - + " " - + get_symbol(asset) - + " " - + "for" - + " " - + get_symbol(erc4626), - }, - ); - leaf_index += 1; - - // Withdrawals - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("withdraw"), - argument_addresses: array![vault.into(), vault.into()].span(), - description: "Withdraw" - + " " - + get_symbol(asset) - + " " - + "from" - + " " - + get_symbol(erc4626), - }, - ); - leaf_index += 1; - - // Minting - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("mint"), - argument_addresses: array![vault.into()].span(), - description: "Mint" - + " " - + get_symbol(erc4626) - + " " - + "from" - + " " - + get_symbol(asset), - }, - ); - leaf_index += 1; - - // Redeeming - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("redeem"), - argument_addresses: array![vault.into(), vault.into()].span(), - description: "Redeem" - + " " - + get_symbol(erc4626) - + " " - + "for" - + " " - + get_symbol(asset), - }, - ); - leaf_index += 1; -} - - -// ========================================= VESU ========================================= - -pub fn _add_vesu_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - pool_id: felt252, - collateral_assets: Span, - debt_assets_per_collateral_asset: Span>, -) { - assert(collateral_assets.len() == debt_assets_per_collateral_asset.len(), 'inconsistent len'); - let singleton = ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() }; - let pool_extension = singleton.extension(pool_id); - assert(pool_extension != Zero::zero(), 'pool extension not found'); - let pool_extension = IDefaultExtensionPOV2Dispatcher { contract_address: pool_extension }; - - for i in 0..collateral_assets.len() { - let collateral_asset = *collateral_assets.at(i); - let debt_assets = *debt_assets_per_collateral_asset.at(i); - - let v_token = pool_extension.v_token_for_collateral_asset(pool_id, collateral_asset); - assert(v_token != Zero::zero(), 'v token not found'); - - // earn mode - _add_erc4626_leafs(ref leafs, ref leaf_index, vault, decoder_and_sanitizer, v_token); - - // debt mode - let debt_assets_len = debt_assets.len(); - if debt_assets_len > 0 { - for j in 0..debt_assets_len { - let mut pool_id_str: ByteArray = FormatAsByteArray::format_as_byte_array( - @pool_id, 16, - ); - - // APPROVAL of collateral asset to the singleton - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: collateral_asset, - selector: selector!("approve"), - argument_addresses: array![VESU_SINGLETON().into()].span(), - description: "Approve" - + " " - + "singleton" - + "_" - + pool_id_str.clone() - + " " - + "to spend" - + " " - + get_symbol(collateral_asset), - }, - ); - leaf_index += 1; - - let debt_asset = *debt_assets.at(j); - - // MODIFY POSITION - let mut argument_addresses_modify_position = ArrayTrait::new(); - - // pool_id - pool_id.serialize(ref argument_addresses_modify_position); - - // collateral_asset - collateral_asset.serialize(ref argument_addresses_modify_position); - - // debt_asset - debt_asset.serialize(ref argument_addresses_modify_position); - - // user - vault.serialize(ref argument_addresses_modify_position); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: singleton.contract_address, - selector: selector!("modify_position"), - argument_addresses: argument_addresses_modify_position.span(), - description: "Modify position" - + " " - + "extension_pid" - + "_" - + pool_id_str - + " " - + "with collateral" - + " " - + get_symbol(collateral_asset) - + " " - + "and debt" - + " " - + get_symbol(debt_asset), - }, - ); - leaf_index += 1; - } - } - } -} - - -// ========================================= AVNU ========================================= -pub fn _add_avnu_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - router: ContractAddress, - sell_and_buy_token_address: Array<(ContractAddress, ContractAddress)>, -) { - let mut seen_sells: Array = ArrayTrait::new(); - - for i in 0..sell_and_buy_token_address.len() { - let (sell_token_address, buy_token_address) = *sell_and_buy_token_address.at(i); - - if !_contains_address(seen_sells.span(), sell_token_address) { - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: sell_token_address, - selector: selector!("approve"), - argument_addresses: array![router.into()].span(), - description: "Approve" - + " " - + "avnu_router" - + " " - + "to spend" - + " " - + get_symbol(sell_token_address), - }, - ); - leaf_index += 1; - seen_sells.append(sell_token_address); - } - - // swap leaf à chaque paire - let mut argument_addresses = ArrayTrait::new(); - sell_token_address.serialize(ref argument_addresses); - buy_token_address.serialize(ref argument_addresses); - vault.serialize(ref argument_addresses); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: router, - selector: selector!("multi_route_swap"), - argument_addresses: argument_addresses.span(), - description: "Multi route swap" - + " " - + get_symbol(sell_token_address) - + " " - + "for" - + " " - + get_symbol(buy_token_address), - }, - ); - leaf_index += 1; - } -} - -fn _contains_address(span: Span, addr: ContractAddress) -> bool { - let mut i = 0; - while i < span.len() { - if *span.at(i) == addr { - return true; - } - i += 1; - } - false -} - - -fn get_symbol(contract_address: ContractAddress) -> ByteArray { - let ret_data = call_contract_syscall(contract_address, selector!("symbol"), array![].span()); - match ret_data { - Ok(res) => { - let res_len: u32 = res.len(); - if (res_len == 1) { - let symbol_felt = *res.at(0); - let mut symbol_byte_array: ByteArray = ""; - symbol_byte_array.append_word(symbol_felt, bytes_in_felt(symbol_felt)); - symbol_byte_array - } else { - let mut res_span = res; - Serde::::deserialize(ref res_span).unwrap() - } - }, - Err(revert_reason) => { panic!("revert_reason: {:?}", revert_reason); }, - } -} - - -fn bytes_in_felt(word: felt252) -> usize { - if word == 0 { - return 0; - } - let x: u256 = word.try_into().unwrap(); - - let mut p: u256 = 1_u256; - let mut bytes: usize = 0; - - while p <= x && bytes < 31 { - p = p * 256_u256; - bytes += 1; - } - - bytes -} From dd3d34b36667332af2b28d143537627dce0f9c9b Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 11 Sep 2025 21:56:18 +0100 Subject: [PATCH 39/54] Update Vesu integration interface to support versioning - Remove legacy vesu.cairo interface - Split into vesu_v1.cairo and vesu_v2.cairo for version-specific implementations - Add price_router_vesu module for Vesu-specific price routing - Update lib.cairo to reflect new module structure --- .../src/integration_interfaces/vesu.cairo | 187 ------------------ .../src/integration_interfaces/vesu_v1.cairo | 23 +++ .../src/integration_interfaces/vesu_v2.cairo | 28 +++ packages/vault_allocator/src/lib.cairo | 8 +- .../periphery/price_router_vesu/errors.cairo | 11 ++ .../price_router_vesu/interface.cairo | 15 ++ .../price_router_vesu/price_router_vesu.cairo | 93 +++++++++ 7 files changed, 177 insertions(+), 188 deletions(-) delete mode 100644 packages/vault_allocator/src/integration_interfaces/vesu.cairo create mode 100644 packages/vault_allocator/src/integration_interfaces/vesu_v1.cairo create mode 100644 packages/vault_allocator/src/integration_interfaces/vesu_v2.cairo create mode 100644 packages/vault_allocator/src/periphery/price_router_vesu/errors.cairo create mode 100644 packages/vault_allocator/src/periphery/price_router_vesu/interface.cairo create mode 100644 packages/vault_allocator/src/periphery/price_router_vesu/price_router_vesu.cairo diff --git a/packages/vault_allocator/src/integration_interfaces/vesu.cairo b/packages/vault_allocator/src/integration_interfaces/vesu.cairo deleted file mode 100644 index 47ee1fce..00000000 --- a/packages/vault_allocator/src/integration_interfaces/vesu.cairo +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2025 Starknet Vault Kit -// Licensed under the MIT License. See LICENSE file for details. - -use starknet::ContractAddress; - -#[starknet::interface] -pub trait ISingletonV2 { - fn extension(self: @TContractState, pool_id: felt252) -> ContractAddress; -} - -#[starknet::interface] -pub trait IDefaultExtensionPOV2< - TContractState, -> { // fn pool_name(self: @TContractState, pool_id: felt252) -> felt252; - // fn pool_owner(self: @TContractState, pool_id: felt252) -> ContractAddress; - // fn shutdown_mode_agent(self: @TContractState, pool_id: felt252) -> ContractAddress; - // fn pragma_oracle(self: @TContractState) -> ContractAddress; - // fn pragma_summary(self: @TContractState) -> ContractAddress; - // fn oracle_config( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> OracleConfig; - // fn fee_config(self: @TContractState, pool_id: felt252) -> FeeConfig; - // fn debt_caps( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> u256; - // fn interest_rate_config( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> InterestRateConfig; - // fn liquidation_config( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> LiquidationConfig; - // fn shutdown_config(self: @TContractState, pool_id: felt252) -> ShutdownConfig; - // fn shutdown_ltv_config( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> LTVConfig; - // fn shutdown_status( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> ShutdownStatus; - // fn pairs( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> Pair; - // fn violation_timestamp_for_pair( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> u64; - // fn violation_timestamp_count( - // self: @TContractState, pool_id: felt252, violation_timestamp: u64, - // ) -> u128; - // fn oldest_violation_timestamp(self: @TContractState, pool_id: felt252) -> u64; - // fn next_violation_timestamp( - // self: @TContractState, pool_id: felt252, violation_timestamp: u64, - // ) -> u64; - fn v_token_for_collateral_asset( - self: @TContractState, pool_id: felt252, collateral_asset: ContractAddress, - ) -> ContractAddress; - // fn collateral_asset_for_v_token( -// self: @TContractState, pool_id: felt252, v_token: ContractAddress, -// ) -> ContractAddress; -// fn create_pool( -// ref self: TContractState, -// name: felt252, -// asset_params: Span, -// v_token_params: Span, -// ltv_params: Span, -// interest_rate_configs: Span, -// pragma_oracle_params: Span, -// liquidation_params: Span, -// debt_caps: Span, -// shutdown_params: ShutdownParams, -// fee_params: FeeParams, -// owner: ContractAddress, -// ) -> felt252; -// fn add_asset( -// ref self: TContractState, -// pool_id: felt252, -// asset_params: AssetParams, -// v_token_params: VTokenParams, -// interest_rate_config: InterestRateConfig, -// pragma_oracle_params: PragmaOracleParams, -// ); -// fn set_asset_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: u256, -// ); -// fn set_debt_cap( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// debt_cap: u256, -// ); -// fn set_interest_rate_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: u256, -// ); -// fn set_oracle_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: felt252, -// ); -// fn set_liquidation_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// liquidation_config: LiquidationConfig, -// ); -// fn set_ltv_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// ltv_config: LTVConfig, -// ); -// fn set_shutdown_config( -// ref self: TContractState, pool_id: felt252, shutdown_config: ShutdownConfig, -// ); -// fn set_shutdown_ltv_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// shutdown_ltv_config: LTVConfig, -// ); -// fn set_shutdown_mode(ref self: TContractState, pool_id: felt252, shutdown_mode: -// ShutdownMode); -// fn set_pool_owner(ref self: TContractState, pool_id: felt252, owner: ContractAddress); -// fn set_shutdown_mode_agent( -// ref self: TContractState, pool_id: felt252, shutdown_mode_agent: ContractAddress, -// ); -// fn update_shutdown_status( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// ) -> ShutdownMode; -// fn set_fee_config(ref self: TContractState, pool_id: felt252, fee_config: FeeConfig); -// fn claim_fees(ref self: TContractState, pool_id: felt252, collateral_asset: ContractAddress); - - // fn migrate_pool( -// ref self: TContractState, -// pool_id: felt252, -// name: felt252, -// v_token_configs: Span<(felt252, felt252, ContractAddress, ContractAddress)>, -// interest_rate_configs: Span<(ContractAddress, InterestRateConfig)>, -// pragma_oracle_configs: Span<(ContractAddress, OracleConfig)>, -// liquidation_configs: Span<(ContractAddress, ContractAddress, LiquidationConfig)>, -// pairs: Span<(ContractAddress, ContractAddress, Pair)>, -// debt_caps: Span<(ContractAddress, ContractAddress, u256)>, -// shutdown_ltv_configs: Span<(ContractAddress, ContractAddress, LTVConfig)>, -// shutdown_config: ShutdownConfig, -// fee_config: FeeConfig, -// owner: ContractAddress, -// ); -// fn set_migrator(ref self: TContractState, migrator: ContractAddress); - - // fn set_extension_utils_class_hash(ref self: TContractState, extension: felt252); -// // Upgrade -// fn upgrade_name(self: @TContractState) -> felt252; -// fn upgrade(ref self: TContractState, new_implementation: ClassHash); -} diff --git a/packages/vault_allocator/src/integration_interfaces/vesu_v1.cairo b/packages/vault_allocator/src/integration_interfaces/vesu_v1.cairo new file mode 100644 index 00000000..ffe96a60 --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/vesu_v1.cairo @@ -0,0 +1,23 @@ +use starknet::ContractAddress; + +#[derive(PartialEq, Copy, Drop, Serde)] +pub struct Position { + collateral_shares: u256, + nominal_debt: u256, +} + +#[starknet::interface] +pub trait IV1Token { + fn pool_id(self: @TContractState) -> felt252; +} + +#[starknet::interface] +pub trait ISingletonV2 { + fn position( + ref self: TContractState, + pool_id: felt252, + collateral_asset: ContractAddress, + debt_asset: ContractAddress, + user: ContractAddress, + ) -> (Position, u256, u256); +} diff --git a/packages/vault_allocator/src/integration_interfaces/vesu_v2.cairo b/packages/vault_allocator/src/integration_interfaces/vesu_v2.cairo new file mode 100644 index 00000000..2ce1275c --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/vesu_v2.cairo @@ -0,0 +1,28 @@ +use starknet::ContractAddress; +use vault_allocator::integration_interfaces::vesu_v1::Position; + +#[starknet::interface] +pub trait IV2Token { + fn pool_contract(self: @TContractState) -> ContractAddress; +} + +#[starknet::interface] +pub trait IPool { + fn position( + self: @TContractState, + collateral_asset: ContractAddress, + debt_asset: ContractAddress, + user: ContractAddress, + ) -> (Position, u256, u256); +} + + +#[starknet::interface] +pub trait IOracle { + fn price(self: @TContractState, asset: ContractAddress) -> AssetPrice; +} +#[derive(PartialEq, Copy, Drop, Serde, Default)] +pub struct AssetPrice { + pub value: u256, + pub is_valid: bool, +} diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 6c3a4f67..5134fe06 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -17,7 +17,8 @@ pub mod manager { pub mod integration_interfaces { pub mod avnu; pub mod pragma; - pub mod vesu; + pub mod vesu_v1; + pub mod vesu_v2; } pub mod periphery { @@ -26,6 +27,11 @@ pub mod periphery { pub mod interface; pub mod price_router; } + pub mod price_router_vesu { + pub mod errors; + pub mod interface; + pub mod price_router_vesu; + } } pub mod middlewares { diff --git a/packages/vault_allocator/src/periphery/price_router_vesu/errors.cairo b/packages/vault_allocator/src/periphery/price_router_vesu/errors.cairo new file mode 100644 index 00000000..0be8a340 --- /dev/null +++ b/packages/vault_allocator/src/periphery/price_router_vesu/errors.cairo @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +pub mod Errors { + use starknet::ContractAddress; + + pub fn invalid_price(asset: ContractAddress) { + panic!("Invalid price for asset: {:?}", asset); + } +} diff --git a/packages/vault_allocator/src/periphery/price_router_vesu/interface.cairo b/packages/vault_allocator/src/periphery/price_router_vesu/interface.cairo new file mode 100644 index 00000000..775165cf --- /dev/null +++ b/packages/vault_allocator/src/periphery/price_router_vesu/interface.cairo @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + + +#[starknet::interface] +pub trait IPriceRouter { + fn get_value( + self: @T, base_asset: ContractAddress, amount: u256, quote_asset: ContractAddress, + ) -> u256; + fn vesu_oracle_contract(self: @T) -> ContractAddress; +} + diff --git a/packages/vault_allocator/src/periphery/price_router_vesu/price_router_vesu.cairo b/packages/vault_allocator/src/periphery/price_router_vesu/price_router_vesu.cairo new file mode 100644 index 00000000..4651b32c --- /dev/null +++ b/packages/vault_allocator/src/periphery/price_router_vesu/price_router_vesu.cairo @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod PriceRouter { + use core::num::traits::Pow; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::utils::math; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use vault_allocator::integration_interfaces::vesu_v2::{ + IOracleDispatcher, IOracleDispatcherTrait, + }; + use vault_allocator::periphery::price_router_vesu::errors::Errors; + use vault_allocator::periphery::price_router_vesu::interface::IPriceRouter; + + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl InternalImpl = OwnableComponent::InternalImpl; + + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + vesu_oracle: IOracleDispatcher, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + } + + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress, vesu_oracle: ContractAddress) { + self.ownable.initializer(owner); + self.vesu_oracle.write(IOracleDispatcher { contract_address: vesu_oracle }); + } + + + #[abi(embed_v0)] + impl PriceRouterImpl of IPriceRouter { + fn get_value( + self: @ContractState, + base_asset: ContractAddress, + amount: u256, + quote_asset: ContractAddress, + ) -> u256 { + if base_asset == quote_asset { + return amount; + } + + let base_resp = self + .vesu_oracle + .read() + .price(base_asset); // USD price with its own decimals + if !base_resp.is_valid { + Errors::invalid_price(base_asset); + } + let quote_resp = self.vesu_oracle.read().price(quote_asset); + if !quote_resp.is_valid { + Errors::invalid_price(quote_asset); + } + + let base_price: u256 = base_resp.value; + let quote_price: u256 = quote_resp.value; + + let asset_decimals_base: u8 = ERC20ABIDispatcher { contract_address: base_asset } + .decimals(); + let asset_decimals_quote: u8 = ERC20ABIDispatcher { contract_address: quote_asset } + .decimals(); + + let scale_base: u256 = 10_u256.pow(asset_decimals_base.into()); + let scale_quote: u256 = 10_u256.pow(asset_decimals_quote.into()); + + let num: u256 = amount * base_price * scale_quote; + let den: u256 = quote_price * scale_base; + math::u256_mul_div(num, 1, den, math::Rounding::Ceil) + } + + fn vesu_oracle_contract(self: @ContractState) -> ContractAddress { + self.vesu_oracle.read().contract_address + } + } +} From c7cd700453a1d229010fc1f50e3df2f05bc62dea Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Fri, 12 Sep 2025 12:35:48 +0100 Subject: [PATCH 40/54] fix merkle tree - rename vault to vault_allocator - fix svk replacing withdraw by mint --- .../src/merkle_tree/integrations/avnu.cairo | 6 +++--- .../src/merkle_tree/integrations/erc4626.cairo | 12 ++++++------ .../starknet_vault_kit_strategies.cairo | 18 +++++++++--------- .../src/merkle_tree/integrations/vesu_v1.cairo | 4 ++-- .../src/merkle_tree/integrations/vesu_v2.cairo | 2 +- .../src/merkle_tree/registery.cairo | 11 ----------- 6 files changed, 21 insertions(+), 32 deletions(-) diff --git a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo index 851f3503..c9f54055 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo @@ -1,5 +1,5 @@ use starknet::ContractAddress; -use vault_allocator::merkle_tree::base::{ManageLeaf, _contains_address, get_symbol}; +use vesu_vaults::merkle_tree::base::{ManageLeaf, _contains_address, get_symbol}; #[derive(PartialEq, Drop, Serde, Debug, Clone, starknet::Store)] @@ -12,7 +12,7 @@ pub struct AvnuConfig { pub fn _add_avnu_leafs( ref leafs: Array, ref leaf_index: u256, - vault: ContractAddress, + vault_allocator: ContractAddress, decoder_and_sanitizer: ContractAddress, router: ContractAddress, avnu_configs: Span, @@ -49,7 +49,7 @@ pub fn _add_avnu_leafs( let mut argument_addresses = ArrayTrait::new(); sell_token_address.serialize(ref argument_addresses); buy_token_address.serialize(ref argument_addresses); - vault.serialize(ref argument_addresses); + vault_allocator.serialize(ref argument_addresses); leafs .append( diff --git a/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo index 1575dfc0..4e4351c0 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo @@ -1,12 +1,12 @@ use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use starknet::ContractAddress; -use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; pub fn _add_erc4626_leafs( ref leafs: Array, ref leaf_index: u256, - vault: ContractAddress, + vault_allocator: ContractAddress, decoder_and_sanitizer: ContractAddress, erc4626: ContractAddress, ) { @@ -40,7 +40,7 @@ pub fn _add_erc4626_leafs( decoder_and_sanitizer, target: erc4626, selector: selector!("deposit"), - argument_addresses: array![vault.into()].span(), + argument_addresses: array![vault_allocator.into()].span(), description: "Deposit" + " " + get_symbol(asset) @@ -60,7 +60,7 @@ pub fn _add_erc4626_leafs( decoder_and_sanitizer, target: erc4626, selector: selector!("withdraw"), - argument_addresses: array![vault.into(), vault.into()].span(), + argument_addresses: array![vault_allocator.into(), vault_allocator.into()].span(), description: "Withdraw" + " " + get_symbol(asset) @@ -80,7 +80,7 @@ pub fn _add_erc4626_leafs( decoder_and_sanitizer, target: erc4626, selector: selector!("mint"), - argument_addresses: array![vault.into()].span(), + argument_addresses: array![vault_allocator.into()].span(), description: "Mint" + " " + get_symbol(erc4626) @@ -100,7 +100,7 @@ pub fn _add_erc4626_leafs( decoder_and_sanitizer, target: erc4626, selector: selector!("redeem"), - argument_addresses: array![vault.into(), vault.into()].span(), + argument_addresses: array![vault_allocator.into(), vault_allocator.into()].span(), description: "Redeem" + " " + get_symbol(erc4626) diff --git a/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo index aa2f3a86..3842f60c 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo @@ -1,12 +1,12 @@ use openzeppelin::interfaces::erc4626::{ERC4626ABIDispatcher, ERC4626ABIDispatcherTrait}; use starknet::ContractAddress; -use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; pub fn _add_starknet_vault_kit_strategies( ref leafs: Array, ref leaf_index: u256, - vault: ContractAddress, + vault_allocator: ContractAddress, decoder_and_sanitizer: ContractAddress, starknet_vault_kit_strategy: ContractAddress, ) { @@ -42,7 +42,7 @@ pub fn _add_starknet_vault_kit_strategies( decoder_and_sanitizer, target: starknet_vault_kit_strategy, selector: selector!("deposit"), - argument_addresses: array![vault.into()].span(), + argument_addresses: array![vault_allocator.into()].span(), description: "Deposit" + " " + get_symbol(asset) @@ -54,18 +54,18 @@ pub fn _add_starknet_vault_kit_strategies( ); leaf_index += 1; - // Withdrawals + // Minting leafs .append( ManageLeaf { decoder_and_sanitizer, target: starknet_vault_kit_strategy, - selector: selector!("withdraw"), - argument_addresses: array![vault.into(), vault.into()].span(), - description: "Withdraw" + selector: selector!("mint"), + argument_addresses: array![vault_allocator.into()].span(), + description: "Mint" + " " - + get_symbol(asset) + + get_symbol(starknet_vault_kit_strategy) + " " + "from" + " " @@ -81,7 +81,7 @@ pub fn _add_starknet_vault_kit_strategies( decoder_and_sanitizer, target: starknet_vault_kit_strategy, selector: selector!("request_redeem"), - argument_addresses: array![vault.into(), vault.into()].span(), + argument_addresses: array![vault_allocator.into(), vault_allocator.into()].span(), description: "Request Redeem" + " " + get_symbol(starknet_vault_kit_strategy), }, ); diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo index 01c1d1d6..d095fb68 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo @@ -1,7 +1,7 @@ use core::to_byte_array::FormatAsByteArray; use starknet::ContractAddress; -use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; -use vault_allocator::merkle_tree::registery::VESU_SINGLETON; +use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; +use vesu_vaults::merkle_tree::registery::VESU_SINGLETON; #[derive(PartialEq, Drop, Serde, Debug, Clone)] pub struct VesuV1Config { diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo index 1335e298..6eb6436a 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo @@ -1,6 +1,6 @@ use core::to_byte_array::FormatAsByteArray; use starknet::ContractAddress; -use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; #[derive(PartialEq, Drop, Serde, Debug, Clone)] diff --git a/packages/vault_allocator/src/merkle_tree/registery.cairo b/packages/vault_allocator/src/merkle_tree/registery.cairo index 16819ecf..5a8cf769 100644 --- a/packages/vault_allocator/src/merkle_tree/registery.cairo +++ b/packages/vault_allocator/src/merkle_tree/registery.cairo @@ -43,17 +43,6 @@ pub fn VESU_SINGLETON() -> ContractAddress { pub const GENESIS_POOL_ID: felt252 = 2198503327643286920898110335698706244522220458610657370981979460625005526824; -// genesis pool v-tokens - -pub fn VESU_GENESIS_POOL_V_TOKEN_WSTETH() -> ContractAddress { - 0x7cb1a46709214b94f51655be696a4ff6f9bdbbb6edb19418b6a55d190536048.try_into().unwrap() -} - -pub fn VESU_GENESIS_POOL_V_TOKEN_USDT() -> ContractAddress { - 0x40e480d202b47eb9335c31fc328ecda216231425dae74f87d1a97e6e7901dce.try_into().unwrap() -} - - // PRAGMA pub fn PRAGMA() -> ContractAddress { From 1d8dad80337b46ec19b9f3c0c630152ec898ae19 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:10:57 +0100 Subject: [PATCH 41/54] Add upgrade functionality to AVNU middleware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements OpenZeppelin's UpgradeableComponent to enable contract upgrades: - Added IUpgradeable interface and UpgradeableComponent - Added upgradeable storage and events - Owner-only upgrade function following existing pattern 🤖 Generated with [Claude Code](https://claude.ai/code) --- .../avnu_middleware/avnu_middleware.cairo | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo index 7ce06964..98454eda 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -8,6 +8,8 @@ pub mod AvnuMiddleware { use core::num::traits::Zero; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::upgrades::IUpgradeable; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use openzeppelin::utils::math; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, @@ -27,16 +29,20 @@ pub mod AvnuMiddleware { component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; - impl InternalImpl = OwnableComponent::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; #[storage] struct Storage { #[substorage(v0)] ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, price_router: IPriceRouterDispatcher, vault_allocator: ContractAddress, slippage: u16, @@ -50,6 +56,8 @@ pub mod AvnuMiddleware { pub enum Event { #[flat] OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, ConfigUpdated: ConfigUpdated, } @@ -77,6 +85,13 @@ pub mod AvnuMiddleware { self._set_config(slippage, period, allowed_calls_per_period) } + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); + } + } #[abi(embed_v0)] impl AvnuMiddlewareViewImpl of IAvnuMiddleware { From 642714d4bad547985567da57070ddf5aa9005cc7 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Mon, 15 Sep 2025 16:37:29 +0100 Subject: [PATCH 42/54] fix: high risk audit finding for report --- packages/vault/src/test/units/vault.cairo | 76 ++++++++++--------- packages/vault/src/vault/vault.cairo | 2 +- .../src/merkle_tree/integrations/avnu.cairo | 2 +- .../merkle_tree/integrations/erc4626.cairo | 2 +- .../starknet_vault_kit_strategies.cairo | 2 +- .../merkle_tree/integrations/vesu_v1.cairo | 4 +- .../merkle_tree/integrations/vesu_v2.cairo | 2 +- 7 files changed, 46 insertions(+), 44 deletions(-) diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index b0052e9e..036bc874 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -1559,7 +1559,7 @@ fn setup_report_simple_deposit_epoch_0() -> ( let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_buffer + expected_aum - (expected_management_fees_assets) + 1, + expected_buffer + expected_aum - (expected_management_fees_assets + 0) + 1, Rounding::Floor, ); @@ -1663,16 +1663,15 @@ fn setup_report_simple_deposit_with_profit_epoch_1() -> ( * MANAGEMENT_FEES() * REPORT_DELAY().into()) / (Vault::WAD * Vault::YEAR.into()); + let net_profit_after_mgmt = profit_amount - expected_management_fees_assets; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let net_profit_after_mgmt = profit_amount - expected_management_fees_assets; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -1776,7 +1775,7 @@ fn setup_report_simple_deposit_with_loss_epoch_1() -> ( let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - (expected_total_assets - expected_management_fees_assets) + 1, + (expected_total_assets - (expected_management_fees_assets + 0)) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -1893,19 +1892,19 @@ fn setup_report_simple_redeem_unhandled_with_profit_epoch_1() -> ( let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2044,19 +2043,19 @@ fn setup_report_simple_redeem_matched_with_profit_epoch_1() -> ( let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2190,19 +2189,19 @@ fn setup_report_simple_redeem_handled_with_bring_liquidity_with_profit_epoch_1() let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2336,19 +2335,19 @@ fn setup_report_simple_redeem_not_handled_with_bring_liquidity_with_profit_epoch let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2486,11 +2485,12 @@ fn setup_report_simple_redeem_unhandled_with_loss_epoch_1() -> ( - cut; let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let expected_performance_fee_assets = 0; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2635,11 +2635,12 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_1() -> - cut; let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let expected_performance_fee_assets = 0; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2820,11 +2821,12 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_hand let expected_total_assets = liqudity_after - (expected_redeem_assets_after_cut_epoch_1 + expected_redeem_assets_after_cut_epoch_2); + let expected_performance_fee_assets = 0; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index de20f094..5c955177 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -750,7 +750,7 @@ pub mod Vault { let management_fee_shares = math::u256_mul_div( management_fee_assets, total_supply + 1, - (total_assets - management_fee_assets) + 1, + (total_assets - (management_fee_assets + performance_fee_assets)) + 1, Rounding::Floor, ); if (management_fee_shares.is_non_zero()) { diff --git a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo index c9f54055..f0182752 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo @@ -1,5 +1,5 @@ use starknet::ContractAddress; -use vesu_vaults::merkle_tree::base::{ManageLeaf, _contains_address, get_symbol}; +use vault_allocator::merkle_tree::base::{ManageLeaf, _contains_address, get_symbol}; #[derive(PartialEq, Drop, Serde, Debug, Clone, starknet::Store)] diff --git a/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo index 4e4351c0..48917eb4 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo @@ -1,6 +1,6 @@ use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use starknet::ContractAddress; -use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; pub fn _add_erc4626_leafs( diff --git a/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo index 3842f60c..4b3fa276 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo @@ -1,6 +1,6 @@ use openzeppelin::interfaces::erc4626::{ERC4626ABIDispatcher, ERC4626ABIDispatcherTrait}; use starknet::ContractAddress; -use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; pub fn _add_starknet_vault_kit_strategies( diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo index d095fb68..01c1d1d6 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo @@ -1,7 +1,7 @@ use core::to_byte_array::FormatAsByteArray; use starknet::ContractAddress; -use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; -use vesu_vaults::merkle_tree::registery::VESU_SINGLETON; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::registery::VESU_SINGLETON; #[derive(PartialEq, Drop, Serde, Debug, Clone)] pub struct VesuV1Config { diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo index 6eb6436a..1335e298 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo @@ -1,6 +1,6 @@ use core::to_byte_array::FormatAsByteArray; use starknet::ContractAddress; -use vesu_vaults::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; #[derive(PartialEq, Drop, Serde, Debug, Clone)] From c620410eff84414e8cce4afd1333713cde0af6a5 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:03:16 +0100 Subject: [PATCH 43/54] update hash configs --- .../multiply_decoder_and_sanitizer.cairo | 4 +- .../simple_decoder_and_sanitizer.cairo | 43 +++++++++++++++++++ .../vesu_v2_decoder_and_sanitizer.cairo | 4 +- scripts/configs/config.json | 12 +++--- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo index 6d4a8ff6..f096cd45 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo @@ -16,8 +16,8 @@ pub mod MultiplyDecoderAndSanitizerComponent { #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event {} - #[embeddable_as(VesuDecoderAndSanitizerImpl)] - impl VesuDecoderAndSanitizer< + #[embeddable_as(MultiplyDecoderAndSanitizerImpl)] + impl MultiplyDecoderAndSanitizer< TContractState, +HasComponent, > of IMultiplyDecoderAndSanitizer> { fn modify_lever( diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo index 857e206a..382f7de9 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo @@ -7,7 +7,11 @@ pub mod SimpleDecoderAndSanitizer { use vault_allocator::decoders_and_sanitizers::avnu_exchange_decoder_and_sanitizer::avnu_exchange_decoder_and_sanitizer::AvnuExchangeDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::multiply_decoder_and_sanitizer::multiply_decoder_and_sanitizer::MultiplyDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::starknet_vault_kit_decoder_and_sanitizer::starknet_vault_kit_decoder_and_sanitizer::StarknetVaultKitDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::vesu_decoder_and_sanitizer::vesu_decoder_and_sanitizer::VesuDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::vesu_v2_decoder_and_sanitizer::vesu_v2_decoder_and_sanitizer::VesuV2DecoderAndSanitizerComponent; + component!( path: BaseDecoderAndSanitizerComponent, @@ -20,6 +24,12 @@ pub mod SimpleDecoderAndSanitizer { event: Erc4626DecoderAndSanitizerEvent, ); + component!( + path: StarknetVaultKitDecoderAndSanitizerComponent, + storage: starknet_vault_kit_decoder_and_sanitizer, + event: StarknetVaultKitDecoderAndSanitizerEvent, + ); + component!( path: VesuDecoderAndSanitizerComponent, storage: vesu_decoder_and_sanitizer, @@ -32,6 +42,18 @@ pub mod SimpleDecoderAndSanitizer { event: AvnuExchangeDecoderAndSanitizerEvent, ); + component!( + path: MultiplyDecoderAndSanitizerComponent, + storage: multiply_decoder_and_sanitizer, + event: MultiplyDecoderAndSanitizerEvent, + ); + + component!( + path: VesuV2DecoderAndSanitizerComponent, + storage: vesu_v2_decoder_and_sanitizer, + event: VesuV2DecoderAndSanitizerEvent, + ); + #[abi(embed_v0)] impl BaseDecoderAndSanitizerImpl = BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; @@ -50,6 +72,15 @@ pub mod SimpleDecoderAndSanitizer { ContractState, >; + #[abi(embed_v0)] + impl MultiplyDecoderAndSanitizerImpl = + MultiplyDecoderAndSanitizerComponent::MultiplyDecoderAndSanitizerImpl; + + // TODO: duplicate selector not supported, find a way to fix this + // #[abi(embed_v0)] + // impl VesuV2DecoderAndSanitizerImpl = + // VesuV2DecoderAndSanitizerComponent::VesuV2DecoderAndSanitizerImpl; + #[storage] pub struct Storage { #[substorage(v0)] @@ -60,6 +91,12 @@ pub mod SimpleDecoderAndSanitizer { pub vesu_decoder_and_sanitizer: VesuDecoderAndSanitizerComponent::Storage, #[substorage(v0)] pub avnu_exchange_decoder_and_sanitizer: AvnuExchangeDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub starknet_vault_kit_decoder_and_sanitizer: StarknetVaultKitDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub multiply_decoder_and_sanitizer: MultiplyDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub vesu_v2_decoder_and_sanitizer: VesuV2DecoderAndSanitizerComponent::Storage, } #[event] @@ -73,5 +110,11 @@ pub mod SimpleDecoderAndSanitizer { VesuDecoderAndSanitizerEvent: VesuDecoderAndSanitizerComponent::Event, #[flat] AvnuExchangeDecoderAndSanitizerEvent: AvnuExchangeDecoderAndSanitizerComponent::Event, + #[flat] + StarknetVaultKitDecoderAndSanitizerEvent: StarknetVaultKitDecoderAndSanitizerComponent::Event, + #[flat] + MultiplyDecoderAndSanitizerEvent: MultiplyDecoderAndSanitizerComponent::Event, + #[flat] + VesuV2DecoderAndSanitizerEvent: VesuV2DecoderAndSanitizerComponent::Event, } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo index e60485c8..f4d10f31 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo @@ -14,8 +14,8 @@ pub mod VesuV2DecoderAndSanitizerComponent { #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event {} - #[embeddable_as(VesuDecoderAndSanitizerImpl)] - impl VesuDecoderAndSanitizer< + #[embeddable_as(VesuV2DecoderAndSanitizerImpl)] + impl VesuV2DecoderAndSanitizer< TContractState, +HasComponent, > of IVesuV2DecoderAndSanitizer> { fn modify_position( diff --git a/scripts/configs/config.json b/scripts/configs/config.json index a3189f5f..eb9d244f 100644 --- a/scripts/configs/config.json +++ b/scripts/configs/config.json @@ -5,13 +5,13 @@ }, "mainnet": { "hash": { - "Vault": "0x516d937281e70cc858ddc2a5a2c98fe97782d290564c6e94d2e8c91dd70cc2f", - "VaultAllocator": "0x7f2faf80d197cbfd7286fa0b8328e9d15eff219138611573beb8280a92712e5", - "RedeemRequest": "0x213cf96be799ccd7d4775e3798c880c093197ac19313d6032f43c8d22a6346a", - "AvnuMiddleware": "0x142dff46d17eb94e9c77a771968ccd8257d8eed5d6a84c3e37f50e15dc39a9f", - "Manager": "0x67a612fc0c9f0d9a84e425cd7e531e408fa3234fa5e1050eef1905b4ac35a61", + "Vault": "0x3c06f3c8a6a3f9f9bfdf67c4e3ff45613dabb6c5e4c84baca4b923361ff66fc", + "VaultAllocator": "0xbf475be37c67c2b2b400dc4433f162ea516c40bd6ac4fc0bc5452a6a61539f", + "RedeemRequest": "0x76b42bba1d387b1d4a91f4b3e660365b25e5e6f1a0a27b9417283e45e7034ed", + "AvnuMiddleware": "0x3203bdc3f9011677cab5ce2eac757a31ff9694c7d5b78cc7d1de045a0201b28", + "Manager": "0x743ae018195fc5208d05471999a8ee519a4aac9a715a049b367389bac24214f", "PriceRouter": "0x677bd52be1ce091b133c5bd637cec20df7f1390adfa64de98791e2928d5d3c1", - "SimpleDecoderAndSanitizer": "0x5e9c41a7fd764459fc91f3712a7bb3e31a02ed97cf4954b6dd84feb5034e6bc", + "SimpleDecoderAndSanitizer": "0x7dd686936bc70cbdca3aa394b215850a0f51d46a9ba9f4e71fe49e992d7fc7f", "AumProvider4626": "0x21aa3fcc7be5d6078d8551838e297e5e29f7e594c2f1338e3a27dbb11e127eb" }, "periphery": { From bb06ebcbabd9322fa559c13ede3bb080333c970c Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:59:17 +0100 Subject: [PATCH 44/54] Add Vesu V2 specific decoder and sanitizer implementation - Remove VesuV2 component from SimpleDecoderAndSanitizer to avoid selector conflicts - Add new standalone VesuV2SpecificDecoderAndSanitizer contract - Update config with new contract hash - Add declaration script support for new contract - Update package.json scripts for separate decoder contracts --- .../simple_decoder_and_sanitizer.cairo | 15 ------ ...su_v2_specific_decoder_and_sanitizer.cairo | 46 +++++++++++++++++++ packages/vault_allocator/src/lib.cairo | 1 + scripts/configs/config.json | 5 +- scripts/declareContract.ts | 7 +++ scripts/package.json | 3 +- 6 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_specific_decoder_and_sanitizer.cairo diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo index 382f7de9..f92ced6a 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo @@ -10,8 +10,6 @@ pub mod SimpleDecoderAndSanitizer { use vault_allocator::decoders_and_sanitizers::multiply_decoder_and_sanitizer::multiply_decoder_and_sanitizer::MultiplyDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::starknet_vault_kit_decoder_and_sanitizer::starknet_vault_kit_decoder_and_sanitizer::StarknetVaultKitDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::vesu_decoder_and_sanitizer::vesu_decoder_and_sanitizer::VesuDecoderAndSanitizerComponent; - use vault_allocator::decoders_and_sanitizers::vesu_v2_decoder_and_sanitizer::vesu_v2_decoder_and_sanitizer::VesuV2DecoderAndSanitizerComponent; - component!( path: BaseDecoderAndSanitizerComponent, @@ -48,11 +46,6 @@ pub mod SimpleDecoderAndSanitizer { event: MultiplyDecoderAndSanitizerEvent, ); - component!( - path: VesuV2DecoderAndSanitizerComponent, - storage: vesu_v2_decoder_and_sanitizer, - event: VesuV2DecoderAndSanitizerEvent, - ); #[abi(embed_v0)] impl BaseDecoderAndSanitizerImpl = @@ -76,10 +69,6 @@ pub mod SimpleDecoderAndSanitizer { impl MultiplyDecoderAndSanitizerImpl = MultiplyDecoderAndSanitizerComponent::MultiplyDecoderAndSanitizerImpl; - // TODO: duplicate selector not supported, find a way to fix this - // #[abi(embed_v0)] - // impl VesuV2DecoderAndSanitizerImpl = - // VesuV2DecoderAndSanitizerComponent::VesuV2DecoderAndSanitizerImpl; #[storage] pub struct Storage { @@ -95,8 +84,6 @@ pub mod SimpleDecoderAndSanitizer { pub starknet_vault_kit_decoder_and_sanitizer: StarknetVaultKitDecoderAndSanitizerComponent::Storage, #[substorage(v0)] pub multiply_decoder_and_sanitizer: MultiplyDecoderAndSanitizerComponent::Storage, - #[substorage(v0)] - pub vesu_v2_decoder_and_sanitizer: VesuV2DecoderAndSanitizerComponent::Storage, } #[event] @@ -114,7 +101,5 @@ pub mod SimpleDecoderAndSanitizer { StarknetVaultKitDecoderAndSanitizerEvent: StarknetVaultKitDecoderAndSanitizerComponent::Event, #[flat] MultiplyDecoderAndSanitizerEvent: MultiplyDecoderAndSanitizerComponent::Event, - #[flat] - VesuV2DecoderAndSanitizerEvent: VesuV2DecoderAndSanitizerComponent::Event, } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_specific_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_specific_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..3e36004d --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_specific_decoder_and_sanitizer.cairo @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod VesuV2SpecificDecoderAndSanitizer { + use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::vesu_v2_decoder_and_sanitizer::vesu_v2_decoder_and_sanitizer::VesuV2DecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: VesuV2DecoderAndSanitizerComponent, + storage: vesu_v2_decoder_and_sanitizer, + event: VesuV2DecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl VesuV2DecoderAndSanitizerImpl = + VesuV2DecoderAndSanitizerComponent::VesuV2DecoderAndSanitizerImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub vesu_v2_decoder_and_sanitizer: VesuV2DecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + VesuV2DecoderAndSanitizerEvent: VesuV2DecoderAndSanitizerComponent::Event, + } +} diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 5134fe06..5701d8b5 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -47,6 +47,7 @@ pub mod decoders_and_sanitizers { pub mod decoder_custom_types; pub mod interface; pub mod simple_decoder_and_sanitizer; + pub mod vesu_v2_specific_decoder_and_sanitizer; pub mod avnu_exchange_decoder_and_sanitizer { pub mod avnu_exchange_decoder_and_sanitizer; pub mod interface; diff --git a/scripts/configs/config.json b/scripts/configs/config.json index eb9d244f..6d971624 100644 --- a/scripts/configs/config.json +++ b/scripts/configs/config.json @@ -11,8 +11,9 @@ "AvnuMiddleware": "0x3203bdc3f9011677cab5ce2eac757a31ff9694c7d5b78cc7d1de045a0201b28", "Manager": "0x743ae018195fc5208d05471999a8ee519a4aac9a715a049b367389bac24214f", "PriceRouter": "0x677bd52be1ce091b133c5bd637cec20df7f1390adfa64de98791e2928d5d3c1", - "SimpleDecoderAndSanitizer": "0x7dd686936bc70cbdca3aa394b215850a0f51d46a9ba9f4e71fe49e992d7fc7f", - "AumProvider4626": "0x21aa3fcc7be5d6078d8551838e297e5e29f7e594c2f1338e3a27dbb11e127eb" + "SimpleDecoderAndSanitizer": "0x36c29d91a5f1cf1eb62acad4c39803061fd184b0d16a78721b7fcd725756625", + "AumProvider4626": "0x21aa3fcc7be5d6078d8551838e297e5e29f7e594c2f1338e3a27dbb11e127eb", + "VesuV2SpecificDecoderAndSanitizer": "0x2da0e885beb8200e0c1e2793cd5afecd4567c41b4f19940a962d77cd0b34f29" }, "periphery": { "vesuSingleton": "0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160", diff --git a/scripts/declareContract.ts b/scripts/declareContract.ts index 09ed82fc..a4299ea7 100644 --- a/scripts/declareContract.ts +++ b/scripts/declareContract.ts @@ -93,6 +93,13 @@ async function main() { "SimpleDecoderAndSanitizer" ); break; + case "VesuV2SpecificDecoderAndSanitizer": + await declareContract( + envNetwork, + "vault_allocator", + "VesuV2SpecificDecoderAndSanitizer" + ); + break; case "AumProvider4626": await declareContract(envNetwork, "vault", "AumProvider4626"); break; diff --git a/scripts/package.json b/scripts/package.json index 1bef18d2..c7d3a157 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -12,7 +12,8 @@ "declare:avnu-middleware": "tsx declareContract.ts --contract AvnuMiddleware", "declare:manager": "tsx declareContract.ts --contract Manager", "declare:price-router": "tsx declareContract.ts --contract PriceRouter", - "declare:decoder-sanitizer": "tsx declareContract.ts --contract SimpleDecoderAndSanitizer", + "declare:simple-decoder-sanitizer": "tsx declareContract.ts --contract SimpleDecoderAndSanitizer", + "declare:vesu-v2-specific-decoder-sanitizer": "tsx declareContract.ts --contract VesuV2SpecificDecoderAndSanitizer", "declare:aum-provider-4626": "tsx declareContract.ts --contract AumProvider4626", "deploy:contract": "tsx deployContract.ts --contract", "deploy:vault": "tsx deployVault.ts", From 223156dd1e159fc94d6f32a7e821b10ddc59ec24 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Tue, 16 Sep 2025 18:56:24 +0200 Subject: [PATCH 45/54] feat: updated ERC4626LimitConfigImpl --- packages/vault/src/vault/vault.cairo | 110 ++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index 5c955177..d7f16d1a 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -33,7 +33,7 @@ pub mod Vault { use openzeppelin::security::pausable::PausableComponent; use openzeppelin::token::erc20::extensions::erc4626::ERC4626Component::Fee; use openzeppelin::token::erc20::extensions::erc4626::{ - DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, ERC4626DefaultNoLimits, + DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, }; use openzeppelin::token::erc20::{ DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, @@ -87,6 +87,62 @@ pub mod Vault { impl ERC4626Impl = ERC4626Component::ERC4626Impl; impl ERC4626InternalImpl = ERC4626Component::InternalImpl; + // --- Custom ERC4626 Limits Implementation --- + // Custom implementation of deposit/withdraw limits + // Uses max u256 as sentinel value for "unlimited" + impl ERC4626LimitConfigImpl of ERC4626Component::LimitConfigTrait { + /// The max deposit allowed. + /// Returns None for no limit (when limit equals max u256) + fn deposit_limit( + self: @ERC4626Component::ComponentState, receiver: ContractAddress, + ) -> Option { + let contract_state = self.get_contract(); + let limit = contract_state.deposit_limit.read(); + if limit == core::integer::BoundedInt::max() { + Option::None + } else { + Option::Some(limit) + } + } + + /// The max mint allowed. + /// Returns None for no limit (when limit equals max u256) + fn mint_limit( + self: @ERC4626Component::ComponentState, receiver: ContractAddress, + ) -> Option { + let contract_state = self.get_contract(); + let limit = contract_state.mint_limit.read(); + if limit == core::integer::BoundedInt::max() { + Option::None + } else { + Option::Some(limit) + } + } + + /// The max withdraw allowed. + /// Always returns Some(0) since withdrawals are disabled + /// Withdrawals are disabled in this vault - use request_redeem instead + fn withdraw_limit( + self: @ERC4626Component::ComponentState, owner: ContractAddress, + ) -> Option { + Option::Some(0) + } + + /// The max redeem allowed. + /// Returns None for no limit (when limit equals max u256) + fn redeem_limit( + self: @ERC4626Component::ComponentState, owner: ContractAddress, + ) -> Option { + let contract_state = self.get_contract(); + let limit = contract_state.redeem_limit.read(); + if limit == core::integer::BoundedInt::max() { + Option::None + } else { + Option::Some(limit) + } + } + } + // --- ERC20 Implementation --- // Share token functionality with standard and camelCase interfaces #[abi(embed_v0)] @@ -145,7 +201,12 @@ pub mod Vault { management_fees: u256, // Annual management fee rate (in WAD) performance_fees: u256, // Performance fee on profits (in WAD) // --- Redemption System --- - redeem_request: IRedeemRequestDispatcher // NFT contract for tracking redemption requests + redeem_request: IRedeemRequestDispatcher, // NFT contract for tracking redemption requests + // --- ERC4626 Limits --- + // Note: max u256 means unlimited, any other value (including 0) sets a specific limit + deposit_limit: u256, // Maximum deposit amount + mint_limit: u256, // Maximum mint amount + redeem_limit: u256 // Maximum redeem amount } // --- Events --- @@ -254,6 +315,13 @@ pub mod Vault { // Initialize timestamp for fee calculations self.last_report_timestamp.write(get_block_timestamp()); + // Initialize limits to max u256 (unlimited) + // max u256 is used as sentinel value for "no limit" + let max_limit: u256 = core::integer::BoundedInt::max(); + self.deposit_limit.write(max_limit); + self.mint_limit.write(max_limit); + self.redeem_limit.write(max_limit); + self .emit( Report { @@ -884,6 +952,44 @@ pub mod Vault { self.max_delta.read() } + // --- Limit Configuration Functions --- + + /// Set the deposit limit (max u256 for unlimited, any other value including 0 for specific limit) + /// Only callable by owner + fn set_deposit_limit(ref self: ContractState, limit: u256) { + self.access_control.assert_only_role(OWNER_ROLE); + self.deposit_limit.write(limit); + } + + /// Set the mint limit (max u256 for unlimited, any other value including 0 for specific limit) + /// Only callable by owner + fn set_mint_limit(ref self: ContractState, limit: u256) { + self.access_control.assert_only_role(OWNER_ROLE); + self.mint_limit.write(limit); + } + + /// Set the redeem limit (max u256 for unlimited, any other value including 0 for specific limit) + /// Only callable by owner + fn set_redeem_limit(ref self: ContractState, limit: u256) { + self.access_control.assert_only_role(OWNER_ROLE); + self.redeem_limit.write(limit); + } + + /// Get the current deposit limit (max u256 means unlimited) + fn get_deposit_limit(self: @ContractState) -> u256 { + self.deposit_limit.read() + } + + /// Get the current mint limit (max u256 means unlimited) + fn get_mint_limit(self: @ContractState) -> u256 { + self.mint_limit.read() + } + + /// Get the current redeem limit (max u256 means unlimited) + fn get_redeem_limit(self: @ContractState) -> u256 { + self.redeem_limit.read() + } + fn due_assets_from_owner(self: @ContractState, owner: ContractAddress) -> u256 { let balance = ERC721ABIDispatcher { contract_address: self.redeem_request.read().contract_address, From f598774869ecf34e916a8382e2c0bd692bd07a8d Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Tue, 16 Sep 2025 19:04:25 +0200 Subject: [PATCH 46/54] feat: interface --- packages/vault/src/vault/interface.cairo | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/vault/src/vault/interface.cairo b/packages/vault/src/vault/interface.cairo index d10dbfd6..300c4bcd 100644 --- a/packages/vault/src/vault/interface.cairo +++ b/packages/vault/src/vault/interface.cairo @@ -46,5 +46,13 @@ pub trait IVault { fn max_delta(self: @TContractState) -> u256; fn due_assets_from_id(self: @TContractState, id: u256) -> u256; fn due_assets_from_owner(self: @TContractState, owner: ContractAddress) -> u256; + + // Limit configuration functions + fn set_deposit_limit(ref self: TContractState, limit: u256); + fn set_mint_limit(ref self: TContractState, limit: u256); + fn set_redeem_limit(ref self: TContractState, limit: u256); + fn get_deposit_limit(self: @TContractState) -> u256; + fn get_mint_limit(self: @TContractState) -> u256; + fn get_redeem_limit(self: @TContractState) -> u256; } From 39bf01286ddb308eff5a1eb38e2523fb5ab6e676 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Tue, 16 Sep 2025 19:15:59 +0200 Subject: [PATCH 47/54] feat: update test error --- packages/vault/src/test/units/vault.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index 036bc874..e458e592 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -290,7 +290,7 @@ fn test_redeem_not_implemented() { } #[test] -#[should_panic(expected: "Not implemented")] +#[should_panic(expected: ('ERC4626: exceeds max withdraw',))] fn test_withdraw_not_implemented() { let (underlying, vault, _) = set_up(); From ce5f9bba961ce83f45437a443c3d860e6202367a Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Tue, 16 Sep 2025 19:35:03 +0200 Subject: [PATCH 48/54] feat: some tests --- packages/vault/src/test/units/vault.cairo | 140 ++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index e458e592..88ba2343 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -2880,3 +2880,143 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_hand fn test_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_handled_epoch_1() { setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_handled_epoch_1(); } + +#[test] +fn test_deposit_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + + let deposit_limit = Vault::WAD * 100; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(deposit_limit); + + assert(vault.get_deposit_limit() == deposit_limit, 'Deposit limit not set'); + assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == deposit_limit, 'Max deposit mismatch'); + + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(core::integer::BoundedInt::max()); + + assert(vault.get_deposit_limit() == core::integer::BoundedInt::max(), 'Unlimited not set'); + assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == core::integer::BoundedInt::max(), 'Max deposit not unlimited'); +} + +#[test] +fn test_mint_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + + let mint_limit = Vault::WAD * 50; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_mint_limit(mint_limit); + + assert(vault.get_mint_limit() == mint_limit, 'Mint limit not set'); + assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == mint_limit, 'Max mint mismatch'); + + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_mint_limit(core::integer::BoundedInt::max()); + + assert(vault.get_mint_limit() == core::integer::BoundedInt::max(), 'Unlimited not set'); + assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == core::integer::BoundedInt::max(), 'Max mint not unlimited'); +} + +#[test] +fn test_redeem_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + + let redeem_limit = Vault::WAD * 25; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_redeem_limit(redeem_limit); + + assert(vault.get_redeem_limit() == redeem_limit, 'Redeem limit not set'); + // DUMMY_ADDRESS has no shares yet, so max_redeem should be 0 even with limit set + assert(erc4626_dispatcher.max_redeem(DUMMY_ADDRESS()) == 0, 'Max redeem should be 0'); + + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_redeem_limit(core::integer::BoundedInt::max()); + + assert(vault.get_redeem_limit() == core::integer::BoundedInt::max(), 'Unlimited not set'); + + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 100); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, Vault::WAD * 100); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + let shares = erc4626_dispatcher.deposit(Vault::WAD * 100, DUMMY_ADDRESS()); + + assert(erc4626_dispatcher.max_redeem(DUMMY_ADDRESS()) == shares, 'Max redeem not balance'); + + // Now test with a limit lower than balance + let lower_limit = shares / 2; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_redeem_limit(lower_limit); + + assert(erc4626_dispatcher.max_redeem(DUMMY_ADDRESS()) == lower_limit, 'Should be limited'); +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_deposit_limit_unauthorized() { + let (_, vault, _) = set_up(); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + vault.set_deposit_limit(Vault::WAD); +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_mint_limit_unauthorized() { + let (_, vault, _) = set_up(); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + vault.set_mint_limit(Vault::WAD); +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_redeem_limit_unauthorized() { + let (_, vault, _) = set_up(); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + vault.set_redeem_limit(Vault::WAD); +} + +#[test] +fn test_deposit_with_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + + let deposit_limit = Vault::WAD; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(deposit_limit); + + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 2); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, Vault::WAD * 2); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(deposit_limit, DUMMY_ADDRESS()); + + assert(ERC20ABIDispatcher { contract_address: vault.contract_address }.balance_of(DUMMY_ADDRESS()) > 0, 'Deposit failed'); +} + +#[test] +#[should_panic(expected: 'ERC4626: exceeds max deposit',)] +fn test_deposit_exceeds_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + + let deposit_limit = Vault::WAD; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(deposit_limit); + + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 2); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, Vault::WAD * 2); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(deposit_limit + 1, DUMMY_ADDRESS()); +} From 2aa7d43e604d9202077ac82969a7adf5654aedf1 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 17 Sep 2025 14:38:38 +0200 Subject: [PATCH 49/54] fix: review --- packages/vault/src/test/units/vault.cairo | 101 ++++++++-------------- packages/vault/src/vault/interface.cairo | 2 - packages/vault/src/vault/vault.cairo | 63 +++++++------- 3 files changed, 67 insertions(+), 99 deletions(-) diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index 88ba2343..0df36f69 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -2,7 +2,7 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use core::num::traits::Zero; +use core::num::traits::{Zero, Bounded}; use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; @@ -290,7 +290,7 @@ fn test_redeem_not_implemented() { } #[test] -#[should_panic(expected: ('ERC4626: exceeds max withdraw',))] +#[should_panic(expected: "Not implemented")] fn test_withdraw_not_implemented() { let (underlying, vault, _) = set_up(); @@ -2885,76 +2885,59 @@ fn test_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_handl fn test_deposit_limit() { let (underlying, vault, _) = set_up(); let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; - - let deposit_limit = Vault::WAD * 100; + let deposit_cap = Vault::WAD * 100; cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_deposit_limit(deposit_limit); - - assert(vault.get_deposit_limit() == deposit_limit, 'Deposit limit not set'); - assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == deposit_limit, 'Max deposit mismatch'); - + vault.set_deposit_limit(deposit_cap); + assert(vault.get_deposit_limit() == deposit_cap, 'Deposit limit not set'); + assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == deposit_cap, 'Initial max deposit wrong'); + let first_deposit = Vault::WAD * 30; + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), first_deposit); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, first_deposit); + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(first_deposit, DUMMY_ADDRESS()); + let expected_remaining = deposit_cap - first_deposit; + assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == expected_remaining, 'Max deposit not reduced'); cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_deposit_limit(core::integer::BoundedInt::max()); - - assert(vault.get_deposit_limit() == core::integer::BoundedInt::max(), 'Unlimited not set'); - assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == core::integer::BoundedInt::max(), 'Max deposit not unlimited'); + vault.set_deposit_limit(Bounded::MAX); + assert(vault.get_deposit_limit() == Bounded::MAX, 'Unlimited not set'); + assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == Bounded::MAX, 'Max deposit not unlimited'); } #[test] fn test_mint_limit() { let (underlying, vault, _) = set_up(); let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; - - let mint_limit = Vault::WAD * 50; - cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_mint_limit(mint_limit); - - assert(vault.get_mint_limit() == mint_limit, 'Mint limit not set'); - assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == mint_limit, 'Max mint mismatch'); - - cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_mint_limit(core::integer::BoundedInt::max()); - - assert(vault.get_mint_limit() == core::integer::BoundedInt::max(), 'Unlimited not set'); - assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == core::integer::BoundedInt::max(), 'Max mint not unlimited'); -} - -#[test] -fn test_redeem_limit() { - let (underlying, vault, _) = set_up(); - let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; - - let redeem_limit = Vault::WAD * 25; + let deposit_cap = Vault::WAD * 100; cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_redeem_limit(redeem_limit); - - assert(vault.get_redeem_limit() == redeem_limit, 'Redeem limit not set'); - // DUMMY_ADDRESS has no shares yet, so max_redeem should be 0 even with limit set - assert(erc4626_dispatcher.max_redeem(DUMMY_ADDRESS()) == 0, 'Max redeem should be 0'); - + vault.set_deposit_limit(deposit_cap); + let mint_limit_config = Vault::WAD * 50; cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_redeem_limit(core::integer::BoundedInt::max()); - - assert(vault.get_redeem_limit() == core::integer::BoundedInt::max(), 'Unlimited not set'); - + vault.set_mint_limit(mint_limit_config); + assert(vault.get_mint_limit() == mint_limit_config, 'Mint limit not set'); + let initial_max_mint = erc4626_dispatcher.max_mint(DUMMY_ADDRESS()); + assert(initial_max_mint == deposit_cap, 'Initial max mint wrong'); + let first_deposit = Vault::WAD * 30; cheat_caller_address_once(underlying, OWNER()); - ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 100); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), first_deposit); cheat_caller_address_once(underlying, DUMMY_ADDRESS()); - ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, Vault::WAD * 100); - + ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, first_deposit); cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); - let shares = erc4626_dispatcher.deposit(Vault::WAD * 100, DUMMY_ADDRESS()); - - assert(erc4626_dispatcher.max_redeem(DUMMY_ADDRESS()) == shares, 'Max redeem not balance'); - - // Now test with a limit lower than balance - let lower_limit = shares / 2; + let shares_minted = erc4626_dispatcher.deposit(first_deposit, DUMMY_ADDRESS()); + let remaining_deposit_cap = deposit_cap - first_deposit; // 70 WAD remaining + let expected_max_mint = erc4626_dispatcher.convert_to_shares(remaining_deposit_cap); + let actual_max_mint = erc4626_dispatcher.max_mint(DUMMY_ADDRESS()); + assert(actual_max_mint == expected_max_mint, 'Max mint not adjusted'); + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(Bounded::MAX); cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_redeem_limit(lower_limit); + vault.set_mint_limit(Bounded::MAX); - assert(erc4626_dispatcher.max_redeem(DUMMY_ADDRESS()) == lower_limit, 'Should be limited'); + assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == Bounded::MAX, 'Max mint not unlimited'); } + #[test] #[should_panic(expected: ('Caller is missing role',))] fn test_set_deposit_limit_unauthorized() { @@ -2973,14 +2956,6 @@ fn test_set_mint_limit_unauthorized() { vault.set_mint_limit(Vault::WAD); } -#[test] -#[should_panic(expected: ('Caller is missing role',))] -fn test_set_redeem_limit_unauthorized() { - let (_, vault, _) = set_up(); - - cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); - vault.set_redeem_limit(Vault::WAD); -} #[test] fn test_deposit_with_limit() { diff --git a/packages/vault/src/vault/interface.cairo b/packages/vault/src/vault/interface.cairo index 300c4bcd..0afcf2cc 100644 --- a/packages/vault/src/vault/interface.cairo +++ b/packages/vault/src/vault/interface.cairo @@ -50,9 +50,7 @@ pub trait IVault { // Limit configuration functions fn set_deposit_limit(ref self: TContractState, limit: u256); fn set_mint_limit(ref self: TContractState, limit: u256); - fn set_redeem_limit(ref self: TContractState, limit: u256); fn get_deposit_limit(self: @TContractState) -> u256; fn get_mint_limit(self: @TContractState) -> u256; - fn get_redeem_limit(self: @TContractState) -> u256; } diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index d7f16d1a..af6e9441 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -19,7 +19,7 @@ #[starknet::contract] pub mod Vault { - use core::num::traits::Zero; + use core::num::traits::{Zero, Bounded}; use openzeppelin::access::accesscontrol::AccessControlComponent; use openzeppelin::interfaces::erc20::{ ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata, @@ -91,55 +91,65 @@ pub mod Vault { // Custom implementation of deposit/withdraw limits // Uses max u256 as sentinel value for "unlimited" impl ERC4626LimitConfigImpl of ERC4626Component::LimitConfigTrait { - /// The max deposit allowed. + /// The max deposit allowed based on cap. + /// Returns remaining capacity: cap - total_assets /// Returns None for no limit (when limit equals max u256) fn deposit_limit( self: @ERC4626Component::ComponentState, receiver: ContractAddress, ) -> Option { let contract_state = self.get_contract(); let limit = contract_state.deposit_limit.read(); - if limit == core::integer::BoundedInt::max() { + if limit == Bounded::MAX { Option::None } else { - Option::Some(limit) + let total_assets = self.get_total_assets(); + if total_assets >= limit { + Option::Some(0) + } else { + Option::Some(limit - total_assets) + } } } - /// The max mint allowed. + /// The max mint allowed based on cap. + /// Returns shares equivalent of remaining deposit capacity /// Returns None for no limit (when limit equals max u256) fn mint_limit( self: @ERC4626Component::ComponentState, receiver: ContractAddress, ) -> Option { let contract_state = self.get_contract(); let limit = contract_state.mint_limit.read(); - if limit == core::integer::BoundedInt::max() { + if limit == Bounded::MAX { Option::None } else { - Option::Some(limit) + let deposit_limit_opt = self.deposit_limit(receiver); + match deposit_limit_opt { + Option::None => Option::None, + Option::Some(deposit_remaining) => { + if deposit_remaining == 0 { + Option::Some(0) + } else { + let shares = self._convert_to_shares(deposit_remaining, Rounding::Floor); + Option::Some(shares) + } + } + } } } /// The max withdraw allowed. - /// Always returns Some(0) since withdrawals are disabled - /// Withdrawals are disabled in this vault - use request_redeem instead + /// Not implemented - withdrawals are disabled in this vault fn withdraw_limit( self: @ERC4626Component::ComponentState, owner: ContractAddress, ) -> Option { - Option::Some(0) + Option::None } /// The max redeem allowed. - /// Returns None for no limit (when limit equals max u256) fn redeem_limit( self: @ERC4626Component::ComponentState, owner: ContractAddress, ) -> Option { - let contract_state = self.get_contract(); - let limit = contract_state.redeem_limit.read(); - if limit == core::integer::BoundedInt::max() { - Option::None - } else { - Option::Some(limit) - } + Option::None } } @@ -206,7 +216,6 @@ pub mod Vault { // Note: max u256 means unlimited, any other value (including 0) sets a specific limit deposit_limit: u256, // Maximum deposit amount mint_limit: u256, // Maximum mint amount - redeem_limit: u256 // Maximum redeem amount } // --- Events --- @@ -317,11 +326,9 @@ pub mod Vault { // Initialize limits to max u256 (unlimited) // max u256 is used as sentinel value for "no limit" - let max_limit: u256 = core::integer::BoundedInt::max(); + let max_limit: u256 = Bounded::MAX; self.deposit_limit.write(max_limit); self.mint_limit.write(max_limit); - self.redeem_limit.write(max_limit); - self .emit( Report { @@ -968,13 +975,6 @@ pub mod Vault { self.mint_limit.write(limit); } - /// Set the redeem limit (max u256 for unlimited, any other value including 0 for specific limit) - /// Only callable by owner - fn set_redeem_limit(ref self: ContractState, limit: u256) { - self.access_control.assert_only_role(OWNER_ROLE); - self.redeem_limit.write(limit); - } - /// Get the current deposit limit (max u256 means unlimited) fn get_deposit_limit(self: @ContractState) -> u256 { self.deposit_limit.read() @@ -985,11 +985,6 @@ pub mod Vault { self.mint_limit.read() } - /// Get the current redeem limit (max u256 means unlimited) - fn get_redeem_limit(self: @ContractState) -> u256 { - self.redeem_limit.read() - } - fn due_assets_from_owner(self: @ContractState, owner: ContractAddress) -> u256 { let balance = ERC721ABIDispatcher { contract_address: self.redeem_request.read().contract_address, From a9c1fe6083b37a4c7b8876a9130b8d6a4e0de3bf Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:52:37 +0100 Subject: [PATCH 50/54] Update registery.cairo --- packages/vault_allocator/src/merkle_tree/registery.cairo | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/vault_allocator/src/merkle_tree/registery.cairo b/packages/vault_allocator/src/merkle_tree/registery.cairo index 5a8cf769..2e385a05 100644 --- a/packages/vault_allocator/src/merkle_tree/registery.cairo +++ b/packages/vault_allocator/src/merkle_tree/registery.cairo @@ -43,6 +43,15 @@ pub fn VESU_SINGLETON() -> ContractAddress { pub const GENESIS_POOL_ID: felt252 = 2198503327643286920898110335698706244522220458610657370981979460625005526824; +pub fn VESU_GENESIS_POOL_V_TOKEN_WSTETH() -> ContractAddress { + 0x7cb1a46709214b94f51655be696a4ff6f9bdbbb6edb19418b6a55d190536048.try_into().unwrap() +} + +pub fn VESU_GENESIS_POOL_V_TOKEN_USDT() -> ContractAddress { + 0x40e480d202b47eb9335c31fc328ecda216231425dae74f87d1a97e6e7901dce.try_into().unwrap() +} + + // PRAGMA pub fn PRAGMA() -> ContractAddress { From f180fb0f5d4a498eedbca7223bc06f735b4e389d Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:32:16 +0100 Subject: [PATCH 51/54] refactor: improve code formatting and add vault allocator authorization - Fix import ordering and code formatting consistency - Add caller_is_not_vault_allocator error function - Implement authorization check in bring_liquidity function - Add test for unauthorized bring_liquidity access - Clean up whitespace and line formatting throughout test file --- packages/vault/src/test/units/vault.cairo | 108 +++++++++++++++------- packages/vault/src/vault/errors.cairo | 4 + packages/vault/src/vault/vault.cairo | 4 + 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index 0df36f69..c02ad023 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -2,7 +2,7 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use core::num::traits::{Zero, Bounded}; +use core::num::traits::{Bounded, Zero}; use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; @@ -1668,7 +1668,9 @@ fn setup_report_simple_deposit_with_profit_epoch_1() -> ( let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -1897,11 +1899,13 @@ fn setup_report_simple_redeem_unhandled_with_profit_epoch_1() -> ( let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; - + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2048,11 +2052,13 @@ fn setup_report_simple_redeem_matched_with_profit_epoch_1() -> ( let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; - + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2194,11 +2200,13 @@ fn setup_report_simple_redeem_handled_with_bring_liquidity_with_profit_epoch_1() let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; - + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2340,11 +2348,13 @@ fn setup_report_simple_redeem_not_handled_with_bring_liquidity_with_profit_epoch let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; - + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2490,7 +2500,9 @@ fn setup_report_simple_redeem_unhandled_with_loss_epoch_1() -> ( let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2640,7 +2652,9 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_1() -> let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2826,7 +2840,9 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_hand let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets + expected_performance_fee_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2889,20 +2905,29 @@ fn test_deposit_limit() { cheat_caller_address_once(vault.contract_address, OWNER()); vault.set_deposit_limit(deposit_cap); assert(vault.get_deposit_limit() == deposit_cap, 'Deposit limit not set'); - assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == deposit_cap, 'Initial max deposit wrong'); + assert( + erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == deposit_cap, 'Initial max deposit wrong', + ); let first_deposit = Vault::WAD * 30; cheat_caller_address_once(underlying, OWNER()); ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), first_deposit); cheat_caller_address_once(underlying, DUMMY_ADDRESS()); - ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, first_deposit); + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, first_deposit); cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); erc4626_dispatcher.deposit(first_deposit, DUMMY_ADDRESS()); let expected_remaining = deposit_cap - first_deposit; - assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == expected_remaining, 'Max deposit not reduced'); + assert( + erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == expected_remaining, + 'Max deposit not reduced', + ); cheat_caller_address_once(vault.contract_address, OWNER()); vault.set_deposit_limit(Bounded::MAX); assert(vault.get_deposit_limit() == Bounded::MAX, 'Unlimited not set'); - assert(erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == Bounded::MAX, 'Max deposit not unlimited'); + assert( + erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == Bounded::MAX, + 'Max deposit not unlimited', + ); } #[test] @@ -2922,9 +2947,10 @@ fn test_mint_limit() { cheat_caller_address_once(underlying, OWNER()); ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), first_deposit); cheat_caller_address_once(underlying, DUMMY_ADDRESS()); - ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, first_deposit); + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, first_deposit); cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); - let shares_minted = erc4626_dispatcher.deposit(first_deposit, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(first_deposit, DUMMY_ADDRESS()); let remaining_deposit_cap = deposit_cap - first_deposit; // 70 WAD remaining let expected_max_mint = erc4626_dispatcher.convert_to_shares(remaining_deposit_cap); let actual_max_mint = erc4626_dispatcher.max_mint(DUMMY_ADDRESS()); @@ -2933,7 +2959,7 @@ fn test_mint_limit() { vault.set_deposit_limit(Bounded::MAX); cheat_caller_address_once(vault.contract_address, OWNER()); vault.set_mint_limit(Bounded::MAX); - + assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == Bounded::MAX, 'Max mint not unlimited'); } @@ -2942,7 +2968,7 @@ fn test_mint_limit() { #[should_panic(expected: ('Caller is missing role',))] fn test_set_deposit_limit_unauthorized() { let (_, vault, _) = set_up(); - + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); vault.set_deposit_limit(Vault::WAD); } @@ -2951,7 +2977,7 @@ fn test_set_deposit_limit_unauthorized() { #[should_panic(expected: ('Caller is missing role',))] fn test_set_mint_limit_unauthorized() { let (_, vault, _) = set_up(); - + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); vault.set_mint_limit(Vault::WAD); } @@ -2961,37 +2987,53 @@ fn test_set_mint_limit_unauthorized() { fn test_deposit_with_limit() { let (underlying, vault, _) = set_up(); let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; - + let deposit_limit = Vault::WAD; cheat_caller_address_once(vault.contract_address, OWNER()); vault.set_deposit_limit(deposit_limit); - + cheat_caller_address_once(underlying, OWNER()); ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 2); cheat_caller_address_once(underlying, DUMMY_ADDRESS()); - ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, Vault::WAD * 2); - + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, Vault::WAD * 2); + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); erc4626_dispatcher.deposit(deposit_limit, DUMMY_ADDRESS()); - - assert(ERC20ABIDispatcher { contract_address: vault.contract_address }.balance_of(DUMMY_ADDRESS()) > 0, 'Deposit failed'); + + assert( + ERC20ABIDispatcher { contract_address: vault.contract_address } + .balance_of(DUMMY_ADDRESS()) > 0, + 'Deposit failed', + ); } #[test] -#[should_panic(expected: 'ERC4626: exceeds max deposit',)] +#[should_panic(expected: 'ERC4626: exceeds max deposit')] fn test_deposit_exceeds_limit() { let (underlying, vault, _) = set_up(); let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; - + let deposit_limit = Vault::WAD; cheat_caller_address_once(vault.contract_address, OWNER()); vault.set_deposit_limit(deposit_limit); - + cheat_caller_address_once(underlying, OWNER()); ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 2); cheat_caller_address_once(underlying, DUMMY_ADDRESS()); - ERC20ABIDispatcher { contract_address: underlying }.approve(vault.contract_address, Vault::WAD * 2); - + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, Vault::WAD * 2); + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); erc4626_dispatcher.deposit(deposit_limit + 1, DUMMY_ADDRESS()); } + +#[test] +#[should_panic(expected: "Caller is not the vault allocator")] +fn test_bring_liquidity_unauthorized() { + let (_underlying, vault, _) = set_up(); + + // Try to call bring_liquidity from unauthorized address + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + vault.bring_liquidity(1000); +} diff --git a/packages/vault/src/vault/errors.cairo b/packages/vault/src/vault/errors.cairo index 01ee5f24..4d9d9bf9 100644 --- a/packages/vault/src/vault/errors.cairo +++ b/packages/vault/src/vault/errors.cairo @@ -66,4 +66,8 @@ pub mod Errors { pub fn invalid_new_aum(new_aum: u256) { panic!("Invalid new AUM: {}", new_aum); } + + pub fn caller_is_not_vault_allocator() { + panic!("Caller is not the vault allocator"); + } } diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index af6e9441..3963c0d5 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -865,6 +865,10 @@ pub mod Vault { ref self: ContractState, amount: u256, ) { // Amount of assets to bring back let caller = get_caller_address(); + // Only the registered vault allocator can bring liquidity + if (caller != self.vault_allocator.read()) { + Errors::caller_is_not_vault_allocator(); + } ERC20ABIDispatcher { contract_address: self.erc4626.asset() } .transfer_from(caller, starknet::get_contract_address(), amount); let new_buffer = self.buffer.read() + amount; // Calculate new buffer From 0ccbf4ce0f3131fdced947b7c7b4406fea110481 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:49:52 +0100 Subject: [PATCH 52/54] refactor: improve rate limiting implementation in AVNU middleware - Replace slot-based call tracking with time window approach - Simplify storage by using single window counter instead of map - Clean up parameter naming and code organization - Reset counter when window changes for accurate rate limiting --- .../avnu_middleware/avnu_middleware.cairo | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo index 98454eda..5f662976 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -48,7 +48,8 @@ pub mod AvnuMiddleware { slippage: u16, period: u64, allowed_calls_per_period: u64, - call_count: Map, + current_window_id: u64, + window_call_count: u64, } #[event] @@ -197,34 +198,42 @@ pub mod AvnuMiddleware { if (caller != self.vault_allocator.read()) { Errors::caller_not_vault_allocator(); } + + let period = self.period.read(); let ts: u64 = get_block_timestamp(); - let slot = ts % self.period.read(); - let current = self.call_count.read(slot); + let window_id: u64 = ts / period; + + if (window_id != self.current_window_id.read()) { + self.current_window_id.write(window_id); + self.window_call_count.write(0); + } + + let current = self.window_call_count.read(); let next = current + 1; - let allowed_calls_per_period = self.allowed_calls_per_period.read(); - if (next > allowed_calls_per_period) { - Errors::rate_limit_exceeded(next, allowed_calls_per_period); + let allowed = self.allowed_calls_per_period.read(); + + if (next > allowed) { + Errors::rate_limit_exceeded(next, allowed); } - self.call_count.write(slot, next); + self.window_call_count.write(next); } - fn _set_config( - ref self: ContractState, slippage: u16, period: u64, allowed_calls_per_period: u64, - ) { + fn _set_config(ref self: ContractState, slippage: u16, period: u64, allowed: u64) { if (slippage >= BPS_SCALE) { Errors::slippage_exceeds_max(slippage); } - - self.slippage.write(slippage); - if (period.is_zero()) { Errors::period_zero(); } - self.period.write(period); - if (allowed_calls_per_period.is_zero()) { + if (allowed.is_zero()) { Errors::allowed_calls_per_period_zero(); } - self.allowed_calls_per_period.write(allowed_calls_per_period); + + self.slippage.write(slippage); + self.period.write(period); + self.allowed_calls_per_period.write(allowed); + self.current_window_id.write(0); + self.window_call_count.write(0); } } } From 43c8fe4ce5c5c0c664102dee0f8ea560b7209c20 Mon Sep 17 00:00:00 2001 From: 0xSacha <90143060+0xSacha@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:51:35 +0100 Subject: [PATCH 53/54] refactor: update contract hashes and SDK package name - Clean up unused imports in AVNU middleware - Update mainnet contract hashes in config.json - Rename SDK package to @forge_yields/starknet_vault_kit_sdk - Add yarn.lock for SDK dependencies --- .../avnu_middleware/avnu_middleware.cairo | 5 +- scripts/configs/config.json | 16 +- sdk/package.json | 2 +- sdk/yarn.lock | 2984 +++++++++++++++++ 4 files changed, 2994 insertions(+), 13 deletions(-) create mode 100644 sdk/yarn.lock diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo index 5f662976..13ee69ab 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -11,10 +11,7 @@ pub mod AvnuMiddleware { use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use openzeppelin::utils::math; - use starknet::storage::{ - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - StoragePointerWriteAccess, - }; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use starknet::{ContractAddress, get_block_timestamp, get_caller_address, get_contract_address}; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; use vault_allocator::integration_interfaces::avnu::{ diff --git a/scripts/configs/config.json b/scripts/configs/config.json index 6d971624..916eb6e7 100644 --- a/scripts/configs/config.json +++ b/scripts/configs/config.json @@ -5,15 +5,15 @@ }, "mainnet": { "hash": { - "Vault": "0x3c06f3c8a6a3f9f9bfdf67c4e3ff45613dabb6c5e4c84baca4b923361ff66fc", - "VaultAllocator": "0xbf475be37c67c2b2b400dc4433f162ea516c40bd6ac4fc0bc5452a6a61539f", - "RedeemRequest": "0x76b42bba1d387b1d4a91f4b3e660365b25e5e6f1a0a27b9417283e45e7034ed", - "AvnuMiddleware": "0x3203bdc3f9011677cab5ce2eac757a31ff9694c7d5b78cc7d1de045a0201b28", - "Manager": "0x743ae018195fc5208d05471999a8ee519a4aac9a715a049b367389bac24214f", - "PriceRouter": "0x677bd52be1ce091b133c5bd637cec20df7f1390adfa64de98791e2928d5d3c1", - "SimpleDecoderAndSanitizer": "0x36c29d91a5f1cf1eb62acad4c39803061fd184b0d16a78721b7fcd725756625", + "Vault": "0x02c9453c0d1e52c38b9273801db7d44a3dd718f7d2152667f76ccf479afe854c", + "VaultAllocator": "0x00bf475be37c67c2b2b400dc4433f162ea516c40bd6ac4fc0bc5452a6a61539f", + "RedeemRequest": "0x076b42bba1d387b1d4a91f4b3e660365b25e5e6f1a0a27b9417283e45e7034ed", + "AvnuMiddleware": "0x061991d4c071181b102b97df406d2804c08c63d443cb011552fa4a93947de8e9", + "Manager": "0x0743ae018195fc5208d05471999a8ee519a4aac9a715a049b367389bac24214f", + "PriceRouter": "0x0388513a8239b53c9dba7a19049e9159f73a9d196dcb10745489bdc7d65839e5", + "SimpleDecoderAndSanitizer": "0x036c29d91a5f1cf1eb62acad4c39803061fd184b0d16a78721b7fcd725756625", "AumProvider4626": "0x21aa3fcc7be5d6078d8551838e297e5e29f7e594c2f1338e3a27dbb11e127eb", - "VesuV2SpecificDecoderAndSanitizer": "0x2da0e885beb8200e0c1e2793cd5afecd4567c41b4f19940a962d77cd0b34f29" + "VesuV2SpecificDecoderAndSanitizer": "0x02da0e885beb8200e0c1e2793cd5afecd4567c41b4f19940a962d77cd0b34f29" }, "periphery": { "vesuSingleton": "0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160", diff --git a/sdk/package.json b/sdk/package.json index 02f0ac52..60d5e72b 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,5 +1,5 @@ { - "name": "@starknet-vault-kit/sdk", + "name": "@forge_yields/starknet_vault_kit_sdk", "version": "0.1.0", "description": "TypeScript SDK for Starknet Vault Kit - user and curator operations", "main": "dist/index.js", diff --git a/sdk/yarn.lock b/sdk/yarn.lock new file mode 100644 index 00000000..b80ab936 --- /dev/null +++ b/sdk/yarn.lock @@ -0,0 +1,2984 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.28.3", "@babel/generator@^7.7.2": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.2", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.3.3": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@noble/curves@1.7.0", "@noble/curves@~1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@noble/hashes@~1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@scure/base@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.1.tgz#dd0b2a533063ca612c17aa9ad26424a2ff5aa865" + integrity sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ== + +"@scure/starknet@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@scure/starknet/-/starknet-1.1.0.tgz#d1902e053d98196e161b9b2c3996b20999094e7a" + integrity sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ== + dependencies: + "@noble/curves" "~1.7.0" + "@noble/hashes" "~1.6.0" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@starknet-io/starknet-types-07@npm:@starknet-io/types-js@~0.7.10": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@starknet-io/types-js/-/types-js-0.7.10.tgz#d21dc973d0cd04d7b6293ce461f2f06a5873c760" + integrity sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w== + +"@starknet-io/starknet-types-08@npm:@starknet-io/types-js@~0.8.4": + version "0.8.4" + resolved "https://registry.yarnpkg.com/@starknet-io/types-js/-/types-js-0.8.4.tgz#bbc07422e89cb5bac45da28e8457f0f17535950d" + integrity sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.0.0": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "24.5.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446" + integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ== + dependencies: + undici-types "~7.12.0" + +"@types/node@^20.0.0": + version "20.19.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.17.tgz#41b52697373aef8a43b3b92f33b43f329b2d674b" + integrity sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ== + dependencies: + undici-types "~6.21.0" + +"@types/semver@^7.5.0": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^6.0.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.0.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== + dependencies: + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== + dependencies: + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +abi-wan-kanabi@2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-2.2.4.tgz#47ebbafbb7f8df81773efbdcca60cdda8008c821" + integrity sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg== + dependencies: + ansicolors "^0.3.2" + cardinal "^2.1.1" + fs-extra "^10.0.0" + yargs "^17.7.2" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansicolors@^0.3.2, ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +baseline-browser-mapping@^2.8.3: + version "2.8.5" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.5.tgz#3147fe6b01a0c49ce1952daebcfc2057fc43fedb" + integrity sha512-TiU4qUT9jdCuh4aVOG7H1QozyeI2sZRqoRPdqBIaslfNt4WUSanRBueAwl2x5jt4rXBMim3lIN2x6yT8PDi24Q== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.26.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.2.tgz#7db3b3577ec97f1140a52db4936654911078cef3" + integrity sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A== + dependencies: + baseline-browser-mapping "^2.8.3" + caniuse-lite "^1.0.30001741" + electron-to-chromium "^1.5.218" + node-releases "^2.0.21" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001741: + version "1.0.30001743" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz#50ff91a991220a1ee2df5af00650dd5c308ea7cd" + integrity sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw== + +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" + integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^17.2.2: + version "17.2.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.2.tgz#4010cfe1c2be4fc0f46fd3d951afb424bc067ac6" + integrity sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q== + +electron-to-chromium@^1.5.218: + version "1.5.221" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.221.tgz#bd98014b2a247701c4ebd713080448d539545d79" + integrity sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.0.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lossless-json@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-4.2.0.tgz#69841f29b673989980bfc0ce6e2b1db33533ce34" + integrity sha512-bsHH3x+7acZfqokfn9Ks/ej96yF/z6oGGw1aBmXesq4r3fAjhdG4uYuqzDgZMk5g1CZUd5w3kwwIp9K1LOYUiA== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.21: + version "2.0.21" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c" + integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== + dependencies: + esprima "~4.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +starknet@7.6.4: + version "7.6.4" + resolved "https://registry.yarnpkg.com/starknet/-/starknet-7.6.4.tgz#8ca2f3decbecde6316e7561b39f6a296a7fa33b5" + integrity sha512-FB20IaLCDbh/XomkB+19f5jmNxG+RzNdRO7QUhm7nfH81UPIt2C/MyWAlHCYkbv2wznSEb73wpxbp9tytokTgQ== + 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-07" "npm:@starknet-io/types-js@~0.7.10" + "@starknet-io/starknet-types-08" "npm:@starknet-io/types-js@~0.8.4" + abi-wan-kanabi "2.2.4" + lossless-json "^4.0.1" + pako "^2.0.4" + ts-mixer "^6.0.3" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.0.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + +ts-jest@^29.0.0: + version "29.4.3" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.3.tgz#23264489bebb5b3e2c7966dbf6788e960f244f7c" + integrity sha512-KTWbK2Wot8VXargsLoxhSoEQ9OyMdzQXQoUDeIulWu2Tf7gghuBHeg+agZqVLdTOHhQHVKAaeuctBDRkhWE7hg== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.8" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.2" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +ts-mixer@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" + integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +typescript@^5.0.0: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.12.0: + version "7.12.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb" + integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 9d3d2b46bdec93eb97a865df8918360ed50b6a6c Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Thu, 18 Sep 2025 22:58:27 +0530 Subject: [PATCH 54/54] feat: Add Defi spring (SNF Style) rewards decoder and sanitizer --- ...ring_snf_style_decoder_and_sanitizer.cairo | 34 +++++++++++++++++++ .../defi_spring_snf_style/interface.cairo | 12 +++++++ .../simple_decoder_and_sanitizer.cairo | 15 ++++++++ packages/vault_allocator/src/lib.cairo | 5 +++ 4 files changed, 66 insertions(+) create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/defi_spring_snf_style_decoder_and_sanitizer.cairo create mode 100644 packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/interface.cairo diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/defi_spring_snf_style_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/defi_spring_snf_style_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..7ac5067b --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/defi_spring_snf_style_decoder_and_sanitizer.cairo @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +// Helps claim rewards from Defi spring rewards and any possible rewards +// with similar claim contract structure +// This is called as SNFStyle because there can be other reward contracts (e.g. Ekubo) +// This is the default contract structure by SNF for rewards + +#[starknet::component] +pub mod DefiSpringSNFStyleDecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::defi_spring_snf_style::interface::IDefiSpringSNFStyleDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(DefiSpringSNFStyleDecoderAndSanitizerImpl)] + impl DefiSpringSNFStyleDecoderAndSanitizer< + TContractState, +HasComponent, + > of IDefiSpringSNFStyleDecoderAndSanitizer> { + fn claim( + self: @ComponentState, + amount: u128, + proof: Span, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/interface.cairo new file mode 100644 index 00000000..92609cfe --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/interface.cairo @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::interface] +pub trait IDefiSpringSNFStyleDecoderAndSanitizer { + fn claim( + self: @T, + amount: u128, + proof: Span, + ) -> Span; +} \ No newline at end of file diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo index f92ced6a..dc20d0f6 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo @@ -10,6 +10,7 @@ pub mod SimpleDecoderAndSanitizer { use vault_allocator::decoders_and_sanitizers::multiply_decoder_and_sanitizer::multiply_decoder_and_sanitizer::MultiplyDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::starknet_vault_kit_decoder_and_sanitizer::starknet_vault_kit_decoder_and_sanitizer::StarknetVaultKitDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::vesu_decoder_and_sanitizer::vesu_decoder_and_sanitizer::VesuDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::defi_spring_snf_style::defi_spring_snf_style_decoder_and_sanitizer::DefiSpringSNFStyleDecoderAndSanitizerComponent; component!( path: BaseDecoderAndSanitizerComponent, @@ -46,6 +47,11 @@ pub mod SimpleDecoderAndSanitizer { event: MultiplyDecoderAndSanitizerEvent, ); + component!( + path: DefiSpringSNFStyleDecoderAndSanitizerComponent, + storage: defi_spring_snf_style_decoder_and_sanitizer, + event: DefiSpringSNFStyleDecoderAndSanitizerEvent, + ); #[abi(embed_v0)] impl BaseDecoderAndSanitizerImpl = @@ -69,6 +75,11 @@ pub mod SimpleDecoderAndSanitizer { impl MultiplyDecoderAndSanitizerImpl = MultiplyDecoderAndSanitizerComponent::MultiplyDecoderAndSanitizerImpl; + #[abi(embed_v0)] + impl DefiSpringSNFStyleDecoderAndSanitizerImpl = + DefiSpringSNFStyleDecoderAndSanitizerComponent::DefiSpringSNFStyleDecoderAndSanitizerImpl< + ContractState, + >; #[storage] pub struct Storage { @@ -84,6 +95,8 @@ pub mod SimpleDecoderAndSanitizer { pub starknet_vault_kit_decoder_and_sanitizer: StarknetVaultKitDecoderAndSanitizerComponent::Storage, #[substorage(v0)] pub multiply_decoder_and_sanitizer: MultiplyDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub defi_spring_snf_style_decoder_and_sanitizer: DefiSpringSNFStyleDecoderAndSanitizerComponent::Storage, } #[event] @@ -101,5 +114,7 @@ pub mod SimpleDecoderAndSanitizer { StarknetVaultKitDecoderAndSanitizerEvent: StarknetVaultKitDecoderAndSanitizerComponent::Event, #[flat] MultiplyDecoderAndSanitizerEvent: MultiplyDecoderAndSanitizerComponent::Event, + #[flat] + DefiSpringSNFStyleDecoderAndSanitizerEvent: DefiSpringSNFStyleDecoderAndSanitizerComponent::Event, } } diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 5701d8b5..53d69041 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -73,6 +73,11 @@ pub mod decoders_and_sanitizers { pub mod interface; pub mod multiply_decoder_and_sanitizer; } + + pub mod defi_spring_snf_style { + pub mod defi_spring_snf_style_decoder_and_sanitizer; + pub mod interface; + } } pub mod mocks {